Example #1
0
    def add_total(group):
        global group_subtotals_added

        if not group.get("groups") and group.get("tasks"):
            items.row(
                "",
                b(
                    "{} {} - {}".format(_("Total"), group["code"],
                                        group["name"]), font),
                "",
                "",
                "",
                money(group["progress"]),
            )
            items.row_style("FONTNAME", 0, -1, font.bold)
            items.row_style("ALIGNMENT", -1, -1, "RIGHT")
            items.row_style("SPAN", 1, 4)
            items.row_style("VALIGN", 0, -1, "BOTTOM")
            items.row("")

            totals.row(
                b(
                    "{} {} - {}".format(_("Total"), group["code"],
                                        group["name"]), font),
                money(group["progress"]),
            )
            group_subtotals_added = True
Example #2
0
    def add_task(task):
        items.row(p(task["code"], font), p(task["name"], font))
        items.row_style("SPAN", 1, -2)

        if task["qty"] is not None:
            items.row(
                "",
                "",
                ubrdecimal(task["complete"]),
                p(task["unit"], font),
                money(task["price"]),
                money(task["progress"]),
            )
            items.row_style("ALIGNMENT", 1, -1, "RIGHT")
            items.keep_previous_n_rows_together(2)
        else:
            items.row("", p(task["description"], font))
            items.row_style("SPAN", 1, -1)
            for li in task["lineitems"]:
                items.row(
                    "",
                    p(li["name"], font),
                    ubrdecimal(li["qty"]),
                    p(li["unit"], font),
                    money(li["price"]),
                    money(li["estimate"]),
                )
                items.row_style("ALIGNMENT", 2, -1, "RIGHT")
Example #3
0
 def get_subtotal_context(self, group, **kwargs):
     if "total" not in kwargs:
         if isinstance(group["progress"], Amount):
             kwargs["total"] = money(group["progress"].net)
         else:
             kwargs["total"] = money(group["progress"])
     return super().get_subtotal_context(group, **kwargs)
Example #4
0
    def get_payments(self):

        table = create_invoice_table(self.invoice)[1:]
        for idx, row in enumerate(table):
            txn = row[4]

            if row[0] == "progress":
                title = _("Project progress")
            elif row[0] == "payment":
                pay_day = parse_date(txn["date"])
                title = _("Payment on {date}").format(date=pay_day)
            elif row[0] == "discount":
                title = _("Discount applied")
            elif row[0] == "unpaid":
                title = _("Open claim from prior invoices")
            elif row[0] == "debit":
                title = _("This Invoice")
            else:
                raise NotImplementedError()

            yield "normal", title, money(row[1]), money(row[2]), money(row[3])

            if self.payment_details and row[0] == "payment":
                for job in txn["jobs"].values():
                    yield "small", job["name"], money(
                        job["payment_applied_net"]), money(
                            job["payment_applied_tax"]), money(
                                job["payment_applied_gross"])

            if self.payment_details and row[0] == "discount":
                for job in txn["jobs"].values():
                    yield "small", job["name"], money(
                        job["discount_applied_net"]), money(
                            job["discount_applied_tax"]), money(
                                job["discount_applied_gross"])
Example #5
0
 def get_task_context(self, task, **kwargs):
     if task["qty"] is None:
         return super().get_task_context(task,
                                         qty=None,
                                         total=money(task["progress"]),
                                         show_description=True,
                                         **kwargs)
     else:
         return super().get_task_context(task,
                                         qty=ubrdecimal(task["complete"]),
                                         total=money(task["progress"]),
                                         show_description=False,
                                         **kwargs)
Example #6
0
def collate_payments(invoice, available_width):

    t = TableFormatter([0, 1, 1, 1], available_width, debug=DEBUG_DOCUMENT)
    t.style.append(("LEFTPADDING", (0, 0), (0, -1), 0))
    t.style.append(("RIGHTPADDING", (-1, 0), (-1, -1), 0))
    t.style.append(("BOTTOMPADDING", (0, 1), (-1, -1), 3 * mm))
    t.style.append(("ALIGNMENT", (0, 0), (0, -1), "LEFT"))
    t.style.append(("ALIGNMENT", (1, 0), (-1, -1), "RIGHT"))
    t.style.append(("VALIGN", (0, 0), (-1, -1), "BOTTOM"))

    t.style.append(("LINEBELOW", (0, 0), (-1, 0), 0.25, colors.black))
    t.style.append(("LINEAFTER", (0, 0), (-2, -1), 0.25, colors.black))

    t.row("", _("gross pay"), _("consideration"), _("VAT"))
    t.row_style("FONTNAME", 0, -1, font.bold)

    if len(invoice["transactions"]) > 0:
        t.row(
            _("Job Performance"),
            money(invoice["total_gross"]),
            money(invoice["total_base"]),
            money(invoice["total_tax"]),
        )

    billable_amount = invoice["total_gross"]

    for payment in invoice["transactions"]:
        row = [
            "",
            money(payment["amount"]),
            money(payment["amount_base"]),
            money(payment["amount_tax"]),
        ]
        if payment["type"] == "payment":
            received_on = date_format(payment["transacted_on"], use_l10n=True)
            row[0] = Paragraph(
                _("Your Payment on") + " " + received_on,
                fonts["OpenSans"]["Normal"])
        elif payment["type"] == "discount":
            row[0] = _("Discount Applied")
        billable_amount += payment["amount"]
        t.row(*row)

    t.row(
        _("Billable Total"),
        money(billable_amount),
        money(billable_amount / (TAX_RATE + Decimal("1"))),
        money(billable_amount / (TAX_RATE + Decimal("1")) * TAX_RATE),
    )

    t.row_style("FONTNAME", 0, -1, font.bold)

    return t.get_table(ContinuationTable, repeatRows=1)
Example #7
0
 def get_lineitem_context(self, lineitem, **kwargs):
     return super().get_lineitem_context(
         lineitem,
         qty=ubrdecimal(lineitem["qty"]),
         total=money(lineitem["estimate"]),
         **kwargs
     )
Example #8
0
 def get_lineitem_context(self, lineitem, **kwargs):
     # lineitem.get("new_key", lineitem["old_key"]) is a workaround to support old JSON and fixed JSON
     return super().get_lineitem_context(
         lineitem,
         qty=ubrdecimal(lineitem.get("expended", lineitem["qty"])),
         total=money(lineitem.get("progress", lineitem["estimate"])),
         **kwargs)
Example #9
0
 def get_subtotal_context(self, group, **kwargs):
     total = group["estimate"]
     if isinstance(group["estimate"], Amount):
         total = group["estimate"].net
     return super().get_subtotal_context(group,
                                         total=money(total),
                                         **kwargs)
Example #10
0
 def subtotal_job(self, job):
     if job["invoiced"].net == job["progress"].net:
         yield from super().subtotal_job(job)
     else:
         yield self.render.subtotal_html, self.get_subtotal_context(
             job, total=money(job["invoiced"].net), offset=False
         )
Example #11
0
    def _show_table(self):
        from systori.lib.templatetags.customformatting import money

        for entry in self._entries:
            acct, value = entry[1].account, entry[1].value
            try:
                acct_name = acct.code + " " + acct.job.name
            except ObjectDoesNotExist:
                acct_name = acct.code + " " + acct.name
            if entry[0] == "debit":
                print(" {:<25} {:>10} {:>10}".format(acct_name, money(value), ""))
            else:
                print(" {:<25} {:>10} {:>10}".format(acct_name, "", money(value)))
        print(
            " {:>25} {:>10} {:>10}".format(
                "total:", money(self._total("debit")), money(self._total("credit"))
            )
        )
Example #12
0
 def get_lineitem_context(self, lineitem, **kwargs):
     kwargs.update(
         {
             "name": lineitem["name"],
             "unit": lineitem["unit"],
             "price": money(lineitem["price"]),
         }
     )
     return kwargs
Example #13
0
 def get_task_context(self, task, **kwargs):
     total = money(task["estimate"])
     if task["is_provisional"]:
         total = _("Optional")
     elif task.get("variant_group") and task["variant_serial"] != 0:
         total = _("Alternative")
     return super().get_task_context(
         task,
         qty=ubrdecimal(task["qty"]),
         total=total,
         show_description=not self.render.only_task_names,
         **kwargs)
Example #14
0
 def iterate_tasks(self, group):
     for task in group.get("tasks", []):
         task_context = self.get_task_context(task)
         yield self.render.group_html, task_context
         if task_context["qty"] is not None:
             yield self.render.lineitem_html, {
                 "name": "",
                 "qty": task_context["qty"],
                 "unit": task["unit"],
                 "price": money(task["price"]),
                 "total": task_context["total"],
             }
         else:
             for lineitem in task["lineitems"]:
                 yield self.render.lineitem_html, self.get_lineitem_context(lineitem)
Example #15
0
    def iterate_job(self, job):

        if job["invoiced"].net == job["progress"].net:
            yield from super().iterate_job(job)

        else:

            yield self.render.group_html, {
                "code": job["code"],
                "name": job["name"],
                "bold_name": True,
                "description": job["description"],
                "show_description": True,
            }

            debits = self.document["job_debits"].get(str(job["job.id"]), [])
            last_debit = None
            total = Decimal()
            for debit in debits:
                last_debit = debit
                total += debit["amount"].net
                entry_date = parse_date(debit["date"])
                if debit["entry_type"] == Entry.WORK_DEBIT:
                    title = _("Work completed on {date}").format(
                        date=entry_date)
                elif debit["entry_type"] == Entry.FLAT_DEBIT:
                    title = _("Flat invoice on {date}").format(date=entry_date)
                else:  # adjustment, etc
                    title = _("Adjustment on {date}").format(date=entry_date)
                # yield self.render.debit_html, {
                #    'title': title,
                #    'total': money(debit['amount'].net)
                # }
            if last_debit:
                entry_date = parse_date(last_debit["date"])
                yield self.render.debit_html, {
                    "title":
                    _("Performance until {date}").format(date=entry_date),
                    "total": money(total),
                }
Example #16
0
def migrate_accounts(company):
    from systori.apps.project.models import Project
    from systori.apps.accounting.models import (
        Account,
        Transaction,
        Entry,
        create_account_for_job,
    )
    from systori.apps.accounting.constants import TAX_RATE, SKR03_INCOME_CODE
    from systori.apps.accounting.report import get_transactions_for_jobs
    from systori.apps.accounting import workflow
    from systori.apps.task.models import Job
    from systori.apps.document.models import Invoice

    from systori.lib.templatetags.customformatting import money

    for project in Project.objects.without_template():
        project.account.code = int(project.account.code) + 1000
        project.account.save()

    for job in Job.objects.all():
        job.account = create_account_for_job(job)
        job.save()

    for project in Project.objects.without_template().order_by("id"):

        if not project.invoices.exists():
            if project.account.entries.exists():
                if SHOW_SKIPPED:
                    print("Project #{} - {}".format(project.id, project.name))
                    print("  !!! Has entries but no invoices. !!!")
                    print("")
            else:
                if SHOW_SKIPPED:
                    print("Project #{} - {}".format(project.id, project.name))
                    print("  !!! No invoices and no entries. !!!")
                    print("")
            continue

        no_json = False
        for invoice in project.invoices.all():
            if not invoice.json:
                no_json = True
                if SHOW_SKIPPED:
                    print("Project #{} - {}".format(project.id, project.name))
                    print("  !!! Old invoices, no json. !!!")
                    print("")
                break

        if no_json:
            for invoice in project.invoices.all():
                if not invoice.json:
                    invoice.json = {
                        "debit_net": 0,
                        "debit_tax": 0,
                        "debit_gross": 0
                    }
                    invoice.save()
            continue

        print("\nProject #{} - {}".format(project.id, project.name))

        final_debits = []

        parent_invoice = None
        total_invoices = project.invoices.count()
        for i_invoice, invoice in enumerate(project.invoices.all()):

            if i_invoice < total_invoices - 1:
                invoice.status = invoice.SENT
            else:
                invoice.status = invoice.DRAFT

            if not parent_invoice:
                parent_invoice = invoice
            else:
                invoice.parent = parent_invoice

            print("\n Invoice #{} - {} - {}".format(
                invoice.id, invoice.invoice_no,
                invoice.document_date.isoformat()))
            invoice_amount = Decimal(0.0)
            pre_tax_invoice = Decimal(0.0)

            if invoice.id == 47:
                pass

            match_criteria = [
                {
                    "recorded_on__startswith":
                    invoice.created_on.isoformat(" ")[:19]
                },
                {
                    "recorded_on__startswith":
                    invoice.created_on.isoformat(" ")[:18]
                },
                # {'recorded_on__gte': invoice.created_on-timedelta(minutes=2),
                # 'recorded_on__lte': invoice.created_on+timedelta(minutes=2)},
            ]
            print("  Finding corresponding transaction...")
            print("  >{}".format(invoice.created_on.isoformat(" ")))
            old_transaction = None
            for criteria in match_criteria:
                for key, val in criteria.items():
                    print("  ?{} :{}".format(val, key))
                matches = (Transaction.objects.filter(
                    entries__account=project.account).filter(
                        **criteria).distinct())
                if matches.count() == 1:
                    old_transaction = matches.get()
                    print("  !{}".format(old_transaction.recorded_on))
                    break
                elif matches.count() == 0:
                    print("  No matches.")
                elif matches.count() > 1:
                    print("  Multiple matches:")
                    for match in matches:
                        print("   {}".format(match.recorded_on))

            invoice.json["is_final"] = False
            if old_transaction:
                for entry in old_transaction.entries.all():
                    if entry.account.code == SKR03_INCOME_CODE:
                        invoice.json["is_final"] = True
                        break

            invoice.json["debits"] = []
            debits = []
            for json_job in invoice.json.pop("jobs"):

                job = project.jobs.get(name=json_job["name"],
                                       job_code=int(json_job["code"]))
                json_job["job.id"] = job.id
                json_job["is_invoiced"] = True
                json_job["flat_amount"] = 0.0
                json_job["is_flat"] = False

                pre_tax_amount = Decimal(0.0)
                for json_taskgroup in json_job["taskgroups"]:
                    pre_tax_amount += Decimal(json_taskgroup["total"])

                amount = round(pre_tax_amount * Decimal(1.19), 2)
                already_debited = job.account.debits().total

                amount -= already_debited
                if amount < Decimal(0.0):
                    amount = Decimal(0.0)

                invoice_amount += amount
                pre_tax_invoice += round(amount / (1 + TAX_RATE), 2)

                json_job["debit_amount"] = amount
                json_job["debit_net"] = round(amount / (1 + TAX_RATE), 2)
                json_job["debit_tax"] = amount - json_job["debit_net"]
                json_job["debit_comment"] = ""
                json_job["debited"] = already_debited
                json_job["balance"] = 0.0  # we don't have payments yet
                json_job["estimate"] = round(
                    job.estimate_total * (1 + TAX_RATE), 2)
                json_job["itemized"] = round(
                    job.billable_total * (1 + TAX_RATE), 2)
                invoice.json["debits"].append(json_job)

                if amount > Decimal(0.0):
                    print("  {:<50} {:>15} {:>15}".format(
                        job.name, money(amount), money(pre_tax_amount)))

                if invoice.json["is_final"] or amount > Decimal(0.0):
                    debits.append((job, amount, False))

            print("  {:<50} {:>15} {:>15}".format("", "-" * 10, "-" * 10))
            print("  {:<50} {:>15} {:>15}".format("", money(invoice_amount),
                                                  money(pre_tax_invoice)))
            if round(invoice.amount, 2) != round(invoice_amount, 2):
                # i've manually checked these invoices - lex
                # for Demo project we just do the fix without checking
                if (invoice.id in [
                        31, 37, 38, 46, 47, 51, 54, 55, 56, 60, 62, 63, 67, 69,
                        70, 71
                ] or company.name == "Demo"):
                    # 37, 46, 54, 55 - rounding errors, off by one penny
                    # 56, 60, 63, 69, 71 - all these had the 'balance' remaining instead of how much was actually debited
                    # 31, 38 - debit was correct but invoice had wrong amount, not sure why
                    # 67, 70 - amounts slightly off
                    # 51 - not even sure what happened here but i think the new invoice_amount is correct
                    # 62 - off by $6, probably work completed was reduced since last invoice/payment
                    invoice.amount = invoice_amount
                else:
                    raise ArithmeticError("{} != {}".format(
                        money(round(invoice.amount, 2)),
                        money(round(invoice_amount, 2)),
                    ))

            if invoice.json["is_final"]:
                final_debits.append((invoice, debits))
            else:
                invoice.transaction = workflow.partial_debit(
                    debits, invoice.document_date)

            invoice.json["version"] = "1.2"
            invoice.json["id"] = invoice.id
            invoice.json["title"] = invoice.json.get("title", "")
            invoice.json["debit_gross"] = invoice_amount
            invoice.json["debit_net"] = pre_tax_invoice
            invoice.json["debit_tax"] = invoice_amount - pre_tax_invoice
            invoice.json["debited_gross"] = invoice.json.pop("total_gross")
            invoice.json["debited_net"] = invoice.json.pop("total_base")
            invoice.json["debited_tax"] = invoice.json.pop("total_tax")
            invoice.json["balance_net"] = invoice.json.pop("balance_base")

            invoice.save()

        for payment in project.account.payments().all():

            transaction = payment.transaction
            entries = transaction.entries.all()
            print("\n Converting Payment Transaction #{} (entries: {}) - {}".
                  format(transaction.id, len(entries),
                         money(abs(payment.amount))))

            bank_entry, project_entry, promised_entry, partial_entry, tax_entry, discount_entry, discount_promised_entry, cash_discount_entry = (
                (None, ) * 8)
            if len(entries) == 2:
                bank_entry = entries[0]
                project_entry = entries[1]
            elif len(entries) == 5:
                if entries[0].account.account_type == Account.ASSET:
                    if (entries[3].account.code == "8736"
                        ):  # payment + discount on final invoice
                        bank_entry = entries[0]
                        project_entry = entries[1]
                        discount_entry = entries[2]
                        cash_discount_entry = entries[3]
                        tax_entry = entries[4]
                    else:
                        bank_entry = entries[0]
                        project_entry = entries[1]
                        promised_entry = entries[2]
                        partial_entry = entries[3]
                        tax_entry = entries[4]
                else:  # old style
                    promised_entry = entries[0]
                    partial_entry = entries[1]
                    tax_entry = entries[2]
                    bank_entry = entries[3]
                    project_entry = entries[4]
            elif len(entries) == 7:
                if entries[0].account.account_type == Account.ASSET:
                    bank_entry = entries[0]
                    project_entry = entries[1]
                    promised_entry = entries[2]
                    partial_entry = entries[3]
                    tax_entry = entries[4]
                    discount_entry = entries[5]
                    discount_promised_entry = entries[6]
                else:  # old style
                    promised_entry = entries[0]
                    partial_entry = entries[1]
                    tax_entry = entries[2]
                    bank_entry = entries[3]
                    project_entry = entries[4]
                    discount_promised_entry = entries[5]
                    discount_entry = entries[6]
            else:
                raise NotImplementedError(
                    "Dono what to do with this many entries...")

            assert bank_entry.account.account_type == Account.ASSET
            assert project_entry.account.id == project.account.id
            assert promised_entry is None or promised_entry.account.code == "1710"
            assert partial_entry is None or partial_entry.account.code == "1718"
            assert (cash_discount_entry is None
                    or cash_discount_entry.account.code == "8736")
            assert tax_entry is None or tax_entry.account.code == "1776"
            assert (discount_entry is None
                    or discount_entry.account.id == project.account.id)
            assert (discount_promised_entry is None
                    or discount_promised_entry.account.code == "1710")

            if transaction.id == 121:
                # this payment is supposed to be a refund, or we're not sure
                # will need to be fixed later
                continue

            if transaction.id == 121:
                pass

            transaction.id = None  # start new transaction from previous
            transaction.transaction_type = transaction.PAYMENT
            transaction.debit(bank_entry.account, bank_entry.amount)

            # lets try paying previous invoice

            invoice = (Invoice.objects.filter(
                project=project,
                document_date__lt=transaction.transacted_on).order_by(
                    "-document_date").first())
            if invoice and invoice.amount >= (
                    bank_entry.amount +
                (-discount_entry.amount if discount_entry else 0) -
                    Decimal(.01)):

                print(
                    "\n   Applying payment to previous invoice #{} - {} - {} - {}..."
                    .format(
                        invoice.id,
                        invoice.invoice_no,
                        invoice.document_date,
                        money(invoice.amount),
                    ))

                remaining_payment_amount = payment_amount = Decimal(
                    abs(bank_entry.amount))
                remaining_discount_amount = discount_amount = Decimal(0.0)
                discount_percent = 0.0
                if discount_entry:
                    remaining_discount_amount = discount_amount = Decimal(
                        abs(discount_entry.amount))
                    discount_percent = round(
                        remaining_discount_amount /
                        (remaining_payment_amount + remaining_discount_amount),
                        3,
                    )

                print("\n   Discount: {} ({}%)\n".format(
                    money(discount_amount), round(discount_percent * 100, 1)))

                job_credits_sum = Decimal(0.0)

                non_zero_debits = [
                    debit for debit in invoice.json["debits"]
                    if debit["debit_amount"] > 0.0
                ]
                last_debit_idx = len(non_zero_debits) - 1
                for debit_idx, debit in enumerate(non_zero_debits):

                    job = Job.objects.get(id=debit["job.id"])

                    debit_amount = Decimal(debit["debit_amount"])
                    if (job.account.balance >=
                        (remaining_payment_amount + remaining_discount_amount)
                            <= debit_amount or last_debit_idx == debit_idx):
                        job_credit = remaining_payment_amount
                        job_discount = remaining_discount_amount
                    else:
                        if (debit_amount > job.account.balance <
                            (remaining_payment_amount +
                             remaining_discount_amount)):
                            job_credit = job.account.balance
                        else:
                            job_credit = debit_amount
                        if discount_entry:
                            job_discount = round(job_credit * discount_percent,
                                                 2)
                            job_credit -= job_discount

                    assert (job_credit + job_discount -
                            Decimal("0.01")) <= job.account.balance

                    job_income = round(job_credit / (1 + TAX_RATE), 2)

                    job_credits_sum += job_credit

                    transaction.credit(job.account,
                                       job_credit,
                                       entry_type=Entry.PAYMENT,
                                       job=job)
                    transaction.debit(Account.objects.get(code="1710"),
                                      job_credit,
                                      job=job)
                    transaction.credit(Account.objects.get(code="1718"),
                                       job_income,
                                       job=job)
                    transaction.credit(
                        Account.objects.get(code="1776"),
                        round(job_credit - job_income, 2),
                        job=job,
                    )

                    if discount_entry:
                        transaction.credit(
                            job.account,
                            job_discount,
                            entry_type=Entry.DISCOUNT,
                            job=job,
                        )
                        transaction.debit(Account.objects.get(code="1710"),
                                          job_discount,
                                          job=job)

                    remaining_payment_amount -= job_credit
                    remaining_discount_amount -= job_discount

                    if not remaining_payment_amount:
                        break

            else:

                # paying previous invoice didn't work, so lets go back to splitting the payment

                project_balance = project.balance
                sorted_jobs = [(
                    round(job.account.balance / project_balance, 3),
                    job.account.balance,
                    job,
                ) for job in project.jobs.all()
                               if job.account.balance > Decimal(0.0)]
                sorted_jobs.sort()
                print("\n   Splitting payment into job accounts...")
                percent_total = Decimal(0.0)
                for job in sorted_jobs:
                    percent_total += job[0]
                    print("    {:>5}% {}".format(round(job[0] * 100, 1),
                                                 job[2].name))
                print("    {:>5}".format("-" * 6))
                print("    {:>5}%\n".format(round(percent_total * 100, 1)))

                remaining_payment_amount = payment_amount = Decimal(
                    abs(project_entry.amount))
                remaining_discount_amount = discount_amount = Decimal(0.0)
                discount_percent = 0.0
                if discount_entry:
                    remaining_discount_amount = discount_amount = Decimal(
                        abs(discount_entry.amount))
                    discount_percent = round(
                        remaining_discount_amount /
                        (remaining_payment_amount + remaining_discount_amount),
                        3,
                    )

                print("\n   Discount: {} ({}%)\n".format(
                    money(discount_amount), round(discount_percent * 100, 1)))

                job_credits_sum = Decimal(0.0)

                last_job_idx = len(sorted_jobs) - 1
                for idx, (job_percent, job_balance,
                          job) in enumerate(sorted_jobs):

                    # TODO: Add support final payments.

                    if idx == last_job_idx:  # use whatever is left on last job
                        assert job_balance >= (remaining_payment_amount +
                                               remaining_discount_amount)
                        job_credit = remaining_payment_amount
                        job_discount = remaining_discount_amount
                    else:
                        job_credit = round(payment_amount * job_percent, 2)
                        job_discount = round(discount_amount * job_percent, 2)
                        assert (round(
                            1 - job_credit / (job_credit + job_discount),
                            3) == discount_percent)

                    job_income = round(job_credit / (1 + TAX_RATE), 2)

                    job_credits_sum += job_credit

                    transaction.credit(job.account,
                                       job_credit,
                                       entry_type=Entry.PAYMENT,
                                       job=job)
                    transaction.debit(Account.objects.get(code="1710"),
                                      job_credit,
                                      job=job)
                    transaction.credit(Account.objects.get(code="1718"),
                                       job_income,
                                       job=job)
                    transaction.credit(
                        Account.objects.get(code="1776"),
                        round(job_credit - job_income, 2),
                        job=job,
                    )

                    if discount_entry:
                        transaction.credit(
                            job.account,
                            job_discount,
                            entry_type=Entry.DISCOUNT,
                            job=job,
                        )
                        transaction.debit(Account.objects.get(code="1710"),
                                          job_discount,
                                          job=job)

                    remaining_payment_amount -= job_credit
                    remaining_discount_amount -= job_discount

            print("\n   {:<70} {:>15} {:>15}".format(
                "New transaction entries...", "debits", "credits"))
            for entry in transaction._entries:

                entry_title = entry[1].account.code + " "
                if entry[1].entry_type in [Entry.PAYMENT, Entry.DISCOUNT]:
                    entry_title += entry[1].account.job.name
                else:
                    entry_title += entry[1].account.name

                if entry[1].is_debit():
                    print("   {:<70} {:>15} {:>15}".format(
                        entry_title, money(abs(entry[1].amount)), ""))
                else:
                    print("   {:<70} {:>15} {:>15}".format(
                        entry_title, "", money(abs(entry[1].amount))))
            print("   {:<70} {:>15} {:>15}".format("", "-" * 10, "-" * 10))
            print("   {:<70} {:>15} {:>15}".format(
                "",
                money(transaction._total("debit")),
                money(transaction._total("credit")),
            ))

            # lets make sure we exactly used up the entire amount
            assert remaining_payment_amount == 0.0
            assert remaining_discount_amount == 0.0

            # other checks...
            assert transaction._total(
                "debit") == payment_amount * 2 + discount_amount
            assert job_credits_sum == payment_amount

            # save() also checks that all debits == all credits
            transaction.save()

        for invoice, debits in final_debits:
            invoice.transaction = workflow.final_debit(debits,
                                                       invoice.document_date)
            invoice.save()

        # Now that we have invoices and payments migrated to the new system we can
        # generate the new transaction history tables...
        print("\n\n   Calculating transaction histories....")
        project = Project.objects.get(id=project.id)
        for invoice in project.invoices.all():
            jobs = Job.objects.filter(
                id__in=[debit["job.id"] for debit in invoice.json["debits"]])
            invoice.json["transactions"] = get_transactions_for_jobs(
                jobs, invoice.document_date)
            invoice.save()

    for project in Project.objects.without_template():
        account = project.account
        project.account = None
        project.save()
        account.delete()
Example #17
0
def collate_itemized_listing(invoice, font, available_width):

    # Itemized Listing Table
    items = TableFormatter([1, 0, 1, 1, 1, 1],
                           available_width,
                           font,
                           debug=DEBUG_DOCUMENT)
    items.style.append(("LEFTPADDING", (0, 0), (-1, -1), 0))
    items.style.append(("RIGHTPADDING", (-1, 0), (-1, -1), 0))
    items.style.append(("VALIGN", (0, 0), (-1, -1), "TOP"))
    items.style.append(("LINEABOVE", (0, "splitfirst"), (-1, "splitfirst"),
                        0.25, colors.black))

    items.row(_("Pos."), _("Description"), _("Amount"), "", _("Price"),
              _("Total"))
    items.row_style("ALIGNMENT", 2, -1, "RIGHT")

    # Totals Table
    totals = TableFormatter([0, 1],
                            available_width,
                            font,
                            debug=DEBUG_DOCUMENT)
    totals.style.append(("RIGHTPADDING", (-1, 0), (-1, -1), 0))
    totals.style.append(("LEFTPADDING", (0, 0), (0, -1), 0))
    totals.style.append(("FONTNAME", (0, 0), (-1, -1), font.bold.fontName))
    totals.style.append(("ALIGNMENT", (0, 0), (-1, -1), "RIGHT"))

    if DEBUG_DOCUMENT:
        items.style.append(("GRID", (0, 0), (-1, -1), 0.5, colors.grey))
        totals.style.append(("GRID", (0, 0), (-1, -1), 0.5, colors.grey))

    group_subtotals_added = False

    def add_total(group):
        global group_subtotals_added

        if not group.get("groups") and group.get("tasks"):
            items.row(
                "",
                b(
                    "{} {} - {}".format(_("Total"), group["code"],
                                        group["name"]), font),
                "",
                "",
                "",
                money(group["progress"]),
            )
            items.row_style("FONTNAME", 0, -1, font.bold)
            items.row_style("ALIGNMENT", -1, -1, "RIGHT")
            items.row_style("SPAN", 1, 4)
            items.row_style("VALIGN", 0, -1, "BOTTOM")
            items.row("")

            totals.row(
                b(
                    "{} {} - {}".format(_("Total"), group["code"],
                                        group["name"]), font),
                money(group["progress"]),
            )
            group_subtotals_added = True

    def add_task(task):
        items.row(p(task["code"], font), p(task["name"], font))
        items.row_style("SPAN", 1, -2)

        if task["qty"] is not None:
            items.row(
                "",
                "",
                ubrdecimal(task["complete"]),
                p(task["unit"], font),
                money(task["price"]),
                money(task["progress"]),
            )
            items.row_style("ALIGNMENT", 1, -1, "RIGHT")
            items.keep_previous_n_rows_together(2)
        else:
            items.row("", p(task["description"], font))
            items.row_style("SPAN", 1, -1)
            for li in task["lineitems"]:
                items.row(
                    "",
                    p(li["name"], font),
                    ubrdecimal(li["qty"]),
                    p(li["unit"], font),
                    money(li["price"]),
                    money(li["estimate"]),
                )
                items.row_style("ALIGNMENT", 2, -1, "RIGHT")

    def traverse(parent, depth):
        items.row(b(parent["code"], font), b(parent["name"], font))
        items.row_style("SPAN", 1, -1)
        items.keep_next_n_rows_together(2)

        for group in parent.get("groups", []):
            traverse(group, depth + 1)
            add_total(group)

        for task in parent.get("tasks", []):
            add_task(task)

        add_total(parent)

    for job in invoice["jobs"]:

        items.row(b(job["code"], font), b(job["name"], font))
        items.row_style("SPAN", 1, -1)

        if job["invoiced"].net == job["progress"].net:

            for group in job.get("groups", []):
                traverse(group, 1)

            for task in job.get("tasks", []):
                add_task(task)

        else:

            debits = invoice["job_debits"].get(str(job["job.id"]), [])
            for debit in debits:
                entry_date = date_format(
                    date(*map(int, debit["date"].split("-"))), use_l10n=True)
                if debit["entry_type"] == Entry.WORK_DEBIT:
                    title = _("Work completed on {date}").format(
                        date=entry_date)
                elif debit["entry_type"] == Entry.FLAT_DEBIT:
                    title = _("Flat invoice on {date}").format(date=entry_date)
                else:  # adjustment, etc
                    title = _("Adjustment on {date}").format(date=entry_date)
                items.row("", title, "", "", "", money(debit["amount"].net))
                items.row_style("SPAN", 1, -2)
                items.row_style("ALIGNMENT", -1, -1, "RIGHT")

        if not group_subtotals_added:
            # taskgroup subtotals are added if there is only 1 job *and* it is itemized
            # in all other cases we're going to show the job total
            totals.row(
                b("{} {} - {}".format(_("Total"), job["code"], job["name"]),
                  font),
                money(job["invoiced"].net),
            )

    totals.row(_("Total without VAT"), money(invoice["invoiced"].net))
    totals.row_style("LINEABOVE", 0, 1, 0.25, colors.black)

    return [
        items.get_table(ContinuationTable, repeatRows=1),
        Spacer(0, 4 * mm),
        totals.get_table(),
    ]