예제 #1
0
 def init_amount(self, name):
     initial_amount = self.initial.get(name, Amount.zero())
     self.initial[name + "_net"] = initial_amount.net
     self.initial[name + "_tax"] = initial_amount.tax
     initial_or_updated_amount = Amount(
         convert_field_to_value(self[name + "_net"]),
         convert_field_to_value(self[name + "_tax"]),
     )
     setattr(self, name + "_amount", initial_or_updated_amount)
예제 #2
0
    def set_payment(self, splits):

        self.json["payment"] = Amount.zero()

        for job in self.json["jobs"]:

            credit = Amount.zero()

            for row in splits:
                if job["job.id"] == row["job.id"]:
                    credit = row["credit"]
                    break

            job["payment"] = credit
            self.json["payment"] += credit
예제 #3
0
    def calculate_totals(self, totals, condition=lambda form: True):

        for total in totals:
            total_field = (
                total[:-4] + "total_diff_amount"
                if total.endswith("_diff")
                else total + "_total_amount"
            )
            total_field = total_field.replace(".", "_")
            setattr(self, total_field, Amount.zero())

        for form in self.formset:
            if condition(form):
                for total in totals:
                    total_field = (
                        total[:-4] + "total_diff_amount"
                        if total.endswith("_diff")
                        else total + "_total_amount"
                    )
                    if "." in total:
                        state_name, attr_name = total.split(".")
                        state = getattr(form, state_name)
                        form_amount = getattr(state, attr_name + "_amount")
                    else:
                        form_amount = getattr(form, total + "_amount")
                    total_field = total_field.replace(".", "_")
                    total_amount = getattr(self, total_field)
                    setattr(self, total_field, total_amount + form_amount)
예제 #4
0
 def calculate_initial_refund(self):
     refund_total = Amount.zero()
     for form in self.formset:
         refund_total += form.refund_amount
     for form in self.formset:
         refund_total = form.consume_refund(refund_total)
     return refund_total
예제 #5
0
 def clean(self):
     splits = Amount.zero()
     for form in self.formset:
         splits += form.split_amount
     if splits.gross != self.payment_value:
         raise forms.ValidationError(
             _("The sum of splits must equal the payment amount.")
         )
예제 #6
0
    def calculate_accounting_state(self, state):

        state.paid_amount = self.job.account.paid.negate

        state.invoiced_amount = self.job.account.invoiced
        state.invoiced_diff_amount = state.invoiced_amount - state.paid_amount

        state.progress_amount = Amount.from_net(self.job.progress, TAX_RATE)
        state.progress_diff_amount = state.progress_amount - state.invoiced_amount
예제 #7
0
    def calculate_accounting_state(self, state):

        state.estimate_amount = Amount.from_net(self.job.estimate, TAX_RATE)

        state.progress_amount = Amount.from_net(self.job.progress, TAX_RATE)
        state.progress_percent = self.job.progress_percent

        state.invoiced_amount = self.job.account.invoiced
        state.invoiced_percent = 0
        if state.progress_amount.net > 0:
            state.invoiced_percent = (
                state.invoiced_amount.net / state.progress_amount.net * 100
            )

        state.balance_amount = self.job.account.balance

        state.itemized_amount = state.progress_amount - state.invoiced_amount
        if state.itemized_amount.gross < 0:
            state.itemized_amount = Amount.zero()
예제 #8
0
    def calculate_accounting_state(self, state):
        # Paid Column
        state.paid_amount = self.job.account.paid.negate

        # Invoiced Column
        state.invoiced_amount = self.job.account.invoiced
        state.invoiced_diff_amount = state.invoiced_amount - state.paid_amount

        # Billable Column
        state.progress_amount = Amount.from_net(self.job.progress, TAX_RATE)
        state.progress_diff_amount = state.progress_amount - state.invoiced_amount
예제 #9
0
    def set_adjustments(self, adjustments):

        self.json["adjustment"] = Amount.zero()
        self.json["corrected"] = Amount.zero()

        for job in self.json["jobs"]:

            adjustment = Amount.zero()
            corrected = Amount.zero()

            for row in adjustments:
                if job["job.id"] == row["job.id"]:
                    adjustment = row["adjustment"]
                    corrected = row["corrected"]
                    break

            job["adjustment"] = adjustment
            self.json["adjustment"] += adjustment

            job["corrected"] = corrected
            self.json["corrected"] += corrected
예제 #10
0
def migrate_invoices(apps, schema_editor):
    from systori.apps.company.models import Company
    from systori.apps.document.models import Invoice
    from systori.apps.task.models import Job
    from systori.apps.accounting.report import create_invoice_report
    from systori.apps.accounting.constants import TAX_RATE
    from systori.lib.accounting.tools import Amount

    for company in Company.objects.all():
        company.activate()

        for invoice in Invoice.objects.all():

            invoice.json["debit"] = Amount(invoice.json["debit_net"],
                                           invoice.json["debit_tax"])

            if "debits" not in invoice.json:
                invoice.json["jobs"] = []
                invoice.save()
                continue

            invoice.json["jobs"] = invoice.json["debits"]
            del invoice.json["debits"]

            jobs = Job.objects.filter(
                id__in=[job["job.id"] for job in invoice.json["jobs"]])
            tdate = date(
                *map(int, invoice.json["transactions"][-1]["date"].split("-")))
            new_json = create_invoice_report(invoice.transaction, jobs, tdate)
            if (company.schema == "mehr_handwerk"
                    and invoice.id not in [86, 111]) or (
                        company.schema == "montageservice_grad"
                        and invoice.id not in [1]):
                assert new_json["debit"].gross == invoice.json["debit"].gross
                assert new_json["invoiced"].gross == invoice.json[
                    "debited_gross"]
            invoice.json.update(new_json)

            for job in invoice.json["jobs"]:

                taskgroup_total = Decimal("0.00")
                for taskgroup in job["taskgroups"]:
                    taskgroup_total += taskgroup["total"]
                job["progress"] = Amount.from_net(taskgroup_total, TAX_RATE)

                invoiced_total = Amount.zero()
                for debit in invoice.json["job_debits"].get(job["job.id"], []):
                    invoiced_total += debit["amount"]
                job["invoiced"] = invoiced_total

                job["debit"] = Amount(job["amount_net"], job["amount_tax"])

                job["balance"] = Amount(job["balance_net"], job["balance_tax"])
                job["estimate"] = Amount.from_net(job["estimate_net"],
                                                  TAX_RATE)

                job["is_itemized"] = job["invoiced"].gross == job[
                    "progress"].gross

            invoice.save()
예제 #11
0
    def calculate_initial_values(self):

        if "invoiced" in self.initial:
            self.pre_txn.invoiced_amount = self.initial["invoiced"]
            self.pre_txn.invoiced_diff_amount = Amount.zero()

        if "corrected" not in self.initial:
            self.initial["corrected"] = self.pre_txn.invoiced_amount
        self.init_amount("corrected")

        if "adjustment" not in self.initial:
            self.initial["adjustment"] = (
                self.corrected_amount - self.pre_txn.invoiced_amount
            )
        self.init_amount("adjustment")
예제 #12
0
    def calculate_initial_values(self):

        self.balance_amount = self.pre_txn.balance_amount
        if "invoiced" in self.initial:
            # when paying an invoice, the balance is taken from invoice instead of accouting system
            self.balance_amount = self.initial["invoiced"]

        self.init_amount("split")
        self.init_amount("discount")
        self.init_amount("adjustment")

        if self.balance_amount.gross < 0:
            self.balance_amount = Amount.zero()

        self.credit_amount = (
            self.split_amount + self.discount_amount + self.adjustment_amount
        )
예제 #13
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, formset_class=RefundFormSet, **kwargs)

        if not self.instance.id:
            self.calculate_initial_refund()

        self.calculate_totals(
            [
                "pre_txn.paid",
                "pre_txn.invoiced",
                "pre_txn.invoiced_diff",
                "pre_txn.progress",
                "pre_txn.progress_diff",
                "refund",
                "credit",
            ]
        )

        self.customer_refund_amount = Amount.zero()
        if self.refund_total_amount.gross > self.credit_total_amount.gross:
            self.customer_refund_amount = (
                self.refund_total_amount - self.credit_total_amount
            )
예제 #14
0
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["status_filter"] = self.status_filter

        query = self.get_queryset()
        query = (query.prefetch_related("project").prefetch_related(
            "parent").filter(document_date__gte=date(2015, 9, 1)).order_by(
                "-document_date", "invoice_no"))

        months = OrderedDict()
        for invoice in query:
            doc_date = date(invoice.document_date.year,
                            invoice.document_date.month, 1)
            month = months.setdefault(doc_date, {
                "invoices": [],
                "debit": Amount.zero()
            })
            month["debit"] += invoice.json["debit"]
            month["invoices"].append(invoice)

        context["invoice_groups"] = months

        return context
예제 #15
0
def create_refund_report(txn):

    refund = {"txn": txn}

    jobs = {}

    for entry in txn.entries.all():

        if not entry.account.is_receivable:
            continue

        job = jobs.setdefault(
            entry.job.id,
            {
                "job.id": entry.job.id,
                "code": entry.job.code,
                "name": entry.job.name,
                "amount": Amount.zero(),
            },
        )

        job["amount"] += entry.amount

    return refund
예제 #16
0
    def calculate_initial_values(self):

        # on first load BooleanField.value() comes from self.initial and is an actual bool() type
        # on subsequent submits, value() returns a string from self.data, this makes
        # testing for truthyness difficult, so lets just always use the string version
        # unless you have self.cleaned_data available which is best
        self.is_invoiced_str = str(self["is_invoiced"].value())
        self.is_override_str = str(self["is_override"].value())

        if self.is_override_str == "False":
            if self.pre_txn.itemized_amount.net > 0:
                self.initial["debit"] = self.pre_txn.itemized_amount

        if "debit" not in self.initial:
            self.initial["debit"] = Amount.zero()
        self.init_amount("debit")

        self.post_txn = SimpleNamespace()
        self.post_txn.invoiced_amount = self.pre_txn.invoiced_amount + self.debit_amount
        self.post_txn.balance_amount = self.pre_txn.balance_amount + self.debit_amount

        self.is_itemized = True
        if self.post_txn.invoiced_amount != self.pre_txn.progress_amount:
            self.is_itemized = False
예제 #17
0
def create_invoice_report(invoice_txn, jobs, transacted_on_or_before=None):
    """
    :param invoice_txn: transaction that should be excluded from 'open claims' (unpaid amount)
    :param jobs: limit transaction details to specific set of jobs
    :param transacted_on_or_before: limit transactions up to and including a certain date
    :return: serializable data structure
    """

    txns_query = (
        Transaction.objects.filter(entries__job__in=jobs)
        .prefetch_related("entries__job__project")
        .prefetch_related("entries__account")
        .distinct()
    )

    if transacted_on_or_before:
        txns_query = txns_query.filter(transacted_on__lte=transacted_on_or_before)

    transactions = list(txns_query)
    transactions.sort(key=_transaction_sort_key)

    report = {
        "invoiced": Amount.zero(),
        "paid": Amount.zero(),
        "payments": [],
        "job_debits": {},
        "unpaid": Amount.zero(),
        "debit": Amount.zero(),
    }

    for txn in transactions:

        txn_dict = {
            "id": txn.id,
            "type": txn.transaction_type,
            "date": txn.transacted_on,
            "payment": Amount.zero(),
            "discount": Amount.zero(),
            "total": Amount.zero(),
            "jobs": {},
        }

        job_debits = {}

        for entry in txn.entries.all():

            # we only work with receivable accounts from here on out
            if not entry.account.is_receivable:
                continue

            if entry.job not in jobs:
                # skip jobs we're not interested in
                continue

            job_dict = txn_dict["jobs"].setdefault(
                entry.job.id,
                {
                    "job.id": entry.job.id,
                    "code": entry.job.code,
                    "name": entry.job.name,
                    "payment": Amount.zero(),
                    "discount": Amount.zero(),
                    "total": Amount.zero(),
                },
            )

            job_debit = job_debits.setdefault(
                entry.job.id,
                {
                    "transaction_type": txn.transaction_type,
                    "entry_type": None,
                    "date": txn.transacted_on,
                    "amount": Amount.zero(),
                },
            )

            if entry.entry_type == entry.PAYMENT:
                txn_dict["payment"] += entry.amount
                job_dict["payment"] += entry.amount

            elif entry.entry_type == entry.DISCOUNT:
                txn_dict["discount"] += entry.amount
                job_dict["discount"] += entry.amount

            if entry.entry_type in (entry.PAYMENT, entry.DISCOUNT):
                txn_dict["total"] += entry.amount
                job_dict["total"] += entry.amount

            if entry.entry_type in entry.TYPES_FOR_INVOICED_SUM:
                report["invoiced"] += entry.amount
                if txn.id == invoice_txn.id:
                    report["debit"] += entry.amount
                assert job_debit["entry_type"] in (
                    None,
                    entry.entry_type,
                    entry.ADJUSTMENT,
                )
                job_debit["entry_type"] = entry.entry_type
                job_debit["amount"] += entry.amount

            if entry.entry_type in entry.TYPES_FOR_PAID_SUM:
                report["paid"] += entry.amount

        if txn.transaction_type == txn.PAYMENT:
            report["payments"].append(txn_dict)

        for job_id, debit_dict in job_debits.items():
            if debit_dict["amount"].gross != 0:
                debits = report["job_debits"].setdefault(job_id, [])
                debits.append(debit_dict)

    total_unpaid_balance = (
        report["invoiced"] + report["paid"]
    )  # report['paid'] is a negative number

    report["unpaid"] = Amount.zero()
    if total_unpaid_balance.gross > report["debit"].gross:
        # There is more owed on the account than this invoice is trying to solicit.
        # This means we need to show the difference between what is being sought and what is owed
        # as unpaid amount above the debit line on invoice.
        report["unpaid"] = (
            report["debit"] - total_unpaid_balance
        )  # big from small to get negative number

    return report
예제 #18
0
    def test_project_143_having_open_claim_2016_04_06(self):

        txn = debit_jobs(
            [
                (self.job, Amount.from_net(D("22721.38"),
                                           TAX_RATE), Entry.WORK_DEBIT),
                (self.job2, Amount.from_net(D("3400.05"),
                                            TAX_RATE), Entry.WORK_DEBIT),
            ],
            transacted_on=days_ago(8),
        )
        self.assertEqual(
            self.tbl(txn, [self.job, self.job2]),
            [
                ("", "net", "tax", "gross"),
                ("progress", "26121.43", "4963.07", "31084.50"),
                ("debit", "26121.43", "4963.07", "31084.50"),
            ],
        )

        credit_jobs(
            [
                (self.job, A("26227.29"), A("811.15"), A()),
                (self.job2, A("1572.71"), A("48.64"), A("2424.71")),
            ],
            D("27800.00"),
            transacted_on=days_ago(7),
        )

        txn = debit_jobs(
            [
                (self.job, Amount.from_net(D("8500"),
                                           TAX_RATE), Entry.WORK_DEBIT),
                (self.job2, Amount.from_net(D("4000"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job3, Amount.from_net(D("1000"),
                                            TAX_RATE), Entry.WORK_DEBIT),
            ],
            transacted_on=days_ago(6),
        )
        self.assertEqual(
            self.tbl(txn, [self.job, self.job2, self.job3]),
            [
                ("", "net", "tax", "gross"),
                ("progress", "37583.86", "7140.93", "44724.79"),
                ("payment", "-23361.35", "-4438.65", "-27800.00"),
                ("discount", "-722.51", "-137.28", "-859.79"),
                ("debit", "13500.00", "2565.00", "16065.00"),
            ],
        )

        credit_jobs(
            [
                (self.job, A("9811.55"), A("303.45"), A()),
                (self.job2, A("3188.45"), A("98.61"), A("1472.94")),
            ],
            D("13000.00"),
            transacted_on=days_ago(5),
        )

        txn = debit_jobs(
            [
                (self.job2, Amount.from_net(D("11895.04"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job3, Amount.from_net(D("1628.86"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job4, Amount.from_net(D("358.31"),
                                            TAX_RATE), Entry.WORK_DEBIT),
            ],
            transacted_on=days_ago(4),
        )
        self.assertEqual(
            self.tbl(txn, [self.job, self.job2, self.job3, self.job4]),
            [
                ("", "net", "tax", "gross"),
                ("progress", "50228.31", "9543.37", "59771.68"),
                ("payment", "-23361.35", "-4438.65", "-27800.00"),
                ("discount", "-722.51", "-137.28", "-859.79"),
                ("payment", "-10924.37", "-2075.63", "-13000.00"),
                ("discount", "-337.87", "-64.19", "-402.06"),
                ("unpaid", "-1000.00", "-190.00", "-1190.00"),
                ("debit", "13882.21", "2637.62", "16519.83"),
            ],
        )

        credit_jobs(
            [
                (self.job2, A("6585.27"), A("203.67"), A("7366.16")),
                (self.job3, A("1938.34"), A("59.95"), A()),
                (self.job4, A("426.39"), A("13.19"), A()),
            ],
            D("8950.00"),
            transacted_on=days_ago(3),
        )

        # Initial Case: Due to an underpaid job, invoice shows 'open claim'.

        dtxn = debit_jobs(
            [
                (self.job2, Amount.from_net(D("17790.25"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job5, Amount.from_net(D("6377.68"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job3, Amount.from_net(D("2034.90"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job4, Amount.from_net(D("716.62"),
                                            TAX_RATE), Entry.WORK_DEBIT),
            ],
            transacted_on=days_ago(1),
        )
        self.assertEqual(
            self.tbl(dtxn,
                     [self.job, self.job2, self.job3, self.job4, self.job5]),
            [
                ("", "net", "tax", "gross"),
                ("progress", "70957.71", "13481.96", "84439.67"),
                ("payment", "-23361.35", "-4438.65", "-27800.00"),
                ("discount", "-722.51", "-137.28", "-859.79"),
                ("payment", "-10924.37", "-2075.63", "-13000.00"),
                ("discount", "-337.87", "-64.19", "-402.06"),
                ("payment", "-7521.01", "-1428.99", "-8950.00"),
                ("discount", "-232.61", "-44.20", "-276.81"),
                ("unpaid", "-938.54", "-178.32", "-1116.86"),  # <-- open claim
                ("debit", "26919.45", "5114.70", "32034.15"),
            ],
        )
        dtxn.delete()

        # Adjusted Case: We adjust two jobs, no open claim, but progress is high due to over invoiced job.

        atxn = adjust_jobs(
            [
                (self.job3, A(n="-949.62", t="-180.43")),
                (self.job4, A(n="11.08", t="2.11")),
            ],
            transacted_on=days_ago(1),
        )

        dtxn = debit_jobs(
            [
                (self.job2, Amount.from_net(D("17790.25"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job5, Amount.from_net(D("6377.68"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job3, Amount.from_net(D("2984.52"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job4, Amount.from_net(D("705.54"),
                                            TAX_RATE), Entry.WORK_DEBIT),
            ],
            transacted_on=days_ago(3),
        )
        self.assertEqual(
            self.tbl(dtxn,
                     [self.job, self.job2, self.job3, self.job4, self.job5]),
            [
                ("", "net", "tax", "gross"),
                ("progress", "70957.71", "13481.96", "84439.67"),
                ("payment", "-23361.35", "-4438.65", "-27800.00"),
                ("discount", "-722.51", "-137.28", "-859.79"),
                ("payment", "-10924.37", "-2075.63", "-13000.00"),
                ("discount", "-337.87", "-64.19", "-402.06"),
                ("payment", "-7521.01", "-1428.99", "-8950.00"),
                ("discount", "-232.61", "-44.20", "-276.81"),
                # ('unpaid',    '-938.54',  '-178.32',  '-1116.86'), <-- consumed into debit below
                ("debit", "27857.99", "5293.02", "33151.01"),
            ],
        )
        atxn.delete()
        dtxn.delete()

        # Adjusted Case II: We adjust three jobs, now invoice progress is correct and there is no open claim.

        atxn = adjust_jobs(
            [
                (self.job, A(n="-4678.55", t="-888.92")),
                (self.job3, A(n="-949.62", t="-180.43")),
                (self.job4, A(n="11.08", t="2.11")),
            ],
            transacted_on=days_ago(1),
        )

        dtxn = debit_jobs(
            [
                (self.job2, Amount.from_net(D("17790.25"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job5, Amount.from_net(D("6377.68"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job3, Amount.from_net(D("2984.52"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job4, Amount.from_net(D("705.54"),
                                            TAX_RATE), Entry.WORK_DEBIT),
            ],
            transacted_on=days_ago(3),
        )
        self.assertEqual(
            self.tbl(dtxn,
                     [self.job, self.job2, self.job3, self.job4, self.job5]),
            [
                ("", "net", "tax", "gross"),
                ("progress", "66279.16", "12593.04", "78872.20"),
                ("payment", "-23361.35", "-4438.65", "-27800.00"),
                ("discount", "-722.51", "-137.28", "-859.79"),
                ("payment", "-10924.37", "-2075.63", "-13000.00"),
                ("discount", "-337.87", "-64.19", "-402.06"),
                ("payment", "-7521.01", "-1428.99", "-8950.00"),
                ("discount", "-232.61", "-44.20", "-276.81"),
                ("debit", "27857.99", "5293.02", "33151.01"),
            ],
        )
        atxn.delete()
        dtxn.delete()

        # Adjusted & Refund Case: We adjust three jobs as before but also issue a refund.
        #                         Just making sure refund does not change the invoice in anyway.

        atxn = adjust_jobs(
            [
                (self.job, A(n="-4678.55", t="-888.92")),
                (self.job3, A(n="-949.62", t="-180.43")),
                (self.job4, A(n="11.08", t="2.11")),
            ],
            transacted_on=days_ago(1),
        )

        rtxn = refund_jobs(
            [
                (self.job, A(n="4678.55", t="888.92"), A()),
                (self.job2, A(), A(n="4678.55", t="888.92")),
            ],
            transacted_on=days_ago(1),
        )

        dtxn = debit_jobs(
            [
                (self.job2, Amount.from_net(D("17790.25"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job5, Amount.from_net(D("6377.68"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job3, Amount.from_net(D("2984.52"),
                                            TAX_RATE), Entry.WORK_DEBIT),
                (self.job4, Amount.from_net(D("705.54"),
                                            TAX_RATE), Entry.WORK_DEBIT),
            ],
            transacted_on=days_ago(3),
        )
        self.assertEqual(
            self.tbl(dtxn,
                     [self.job, self.job2, self.job3, self.job4, self.job5]),
            [
                ("", "net", "tax", "gross"),
                ("progress", "66279.16", "12593.04", "78872.20"),
                ("payment", "-23361.35", "-4438.65", "-27800.00"),
                ("discount", "-722.51", "-137.28", "-859.79"),
                ("payment", "-10924.37", "-2075.63", "-13000.00"),
                ("discount", "-337.87", "-64.19", "-402.06"),
                ("payment", "-7521.01", "-1428.99", "-8950.00"),
                ("discount", "-232.61", "-44.20", "-276.81"),
                ("debit", "27857.99", "5293.02", "33151.01"),
            ],
        )
        atxn.delete()
        rtxn.delete()
        dtxn.delete()
예제 #19
0
class InvoiceFactory(factory.django.DjangoModelFactory):

    json = {"debit": Amount.zero()}

    class Meta:
        model = Invoice
예제 #20
0
def migrate_proposals(apps, schema_editor):
    from systori.apps.company.models import Company
    from systori.apps.task.models import Job
    from systori.apps.document.models import Proposal
    from systori.apps.accounting.constants import TAX_RATE
    from systori.lib.accounting.tools import Amount

    for company in Company.objects.all():
        company.activate()

        for proposal in Proposal.objects.all():

            try:
                if "total_base" not in proposal.json and "jobs" not in proposal.json:
                    proposal.json["date"] = proposal.document_date
                    proposal.json["estimate_total"] = Amount(
                        proposal.json["total_net"],
                        proposal.json["total_gross"] -
                        proposal.json["total_net"],
                    )
                    proposal.json["jobs"] = []

                else:
                    proposal.json["estimate_total"] = Amount(
                        proposal.json["total_base"],
                        proposal.json["total_tax"])
                    del proposal.json["total_base"], proposal.json[
                        "total_tax"], proposal.json["total_gross"]

                proposal.json["id"] = proposal.id
                proposal.json["title"] = _("Proposal")

                for job in proposal.json["jobs"]:

                    if "id" in job:
                        job["job.id"] = job["id"]
                        del job["id"]

                    else:
                        job_obj = Job.objects.filter(project=proposal.project,
                                                     name=job["name"]).get()
                        job["job.id"] = job_obj.id

                    job_estimate_net = Decimal("0.00")
                    for group in job["taskgroups"]:
                        group["estimate_net"] = group["total"]
                        del group["total"]
                        job_estimate_net += Decimal(group["estimate_net"])
                        for task in group["tasks"]:
                            task["estimate_net"] = task["total"]
                            del task["total"]
                    job["estimate"] = Amount.from_net(job_estimate_net,
                                                      TAX_RATE)

                proposal.save()

            except:

                if proposal.status == proposal.DECLINED:
                    proposal.delete()
                else:
                    raise
예제 #21
0
def migrate_payments(apps, schema_editor):
    from systori.apps.company.models import Company
    from systori.apps.document.models import Payment, DocumentSettings
    from systori.apps.task.models import Job
    from systori.apps.accounting.models import Transaction
    from systori.lib.accounting.tools import Amount

    print("payment migration..")

    for company in Company.objects.all():
        company.activate()

        for t in Transaction.objects.filter(
                transaction_type=Transaction.PAYMENT):

            job = None
            job_dict = None

            jobs = {}
            split_t = Amount.zero()
            discount_t = Amount.zero()
            adjustment_t = Amount.zero()
            credit_t = Amount.zero()

            if t.id in [9, 154, 297, 307]:
                entry = t.entries.first()
                print(
                    "Company: ",
                    company.schema,
                    ", Transaction ID:",
                    t.id,
                    ", Date:",
                    t.transacted_on,
                    ", Bank:",
                    entry.account.name,
                    ", Amount:",
                    entry.amount.gross,
                    ", Entries: ",
                    t.entries.count(),
                )
                continue

            bank_account = None
            for entry in t.entries.all():

                if (entry.account.is_bank
                        or entry.account.name == "VR Bank Rhein-Neckar eG"):
                    bank_account = entry.account
                    continue

                job = entry.job
                if job:
                    job_dict = jobs.setdefault(
                        job.id,
                        {
                            "job.id": job.id,
                            "name": job.name,
                            "split": Amount.zero(),
                            "discount": Amount.zero(),
                            "adjustment": Amount.zero(),
                            "credit": Amount.zero(),
                        },
                    )

                    if entry.entry_type == entry.PAYMENT:
                        job_dict["split"] += entry.amount.negate
                        split_t += entry.amount

                    elif entry.entry_type == entry.DISCOUNT:
                        job_dict["discount"] += entry.amount.negate
                        discount_t += entry.amount

                    elif entry.entry_type == entry.ADJUSTMENT:
                        job_dict["adjustment"] += entry.amount.negate
                        adjustment_t += entry.amount

                    if entry.entry_type in (
                            entry.PAYMENT,
                            entry.DISCOUNT,
                            entry.ADJUSTMENT,
                    ):
                        job_dict["credit"] += entry.amount.negate
                        credit_t += entry.amount

            assert job
            assert bank_account

            letterhead = DocumentSettings.get_for_language(
                "de").invoice_letterhead
            payment = Payment(
                project=job.project,
                document_date=t.transacted_on,
                transaction=t,
                letterhead=letterhead,
            )
            payment.json.update({
                "bank_account": bank_account.id,
                "date": t.transacted_on,
                "payment": credit_t.negate.gross,
                "discount": Decimal("0.00"),
                "split_total": split_t.negate,
                "discount_total": discount_t.negate,
                "adjustment_total": adjustment_t.negate,
                "credit_total": credit_t.negate,
                "jobs": jobs.values(),
            })
            payment.save()
예제 #22
0
 def test_serialize(self):
     invoice = Invoice.objects.create(project=self.project,
                                      letterhead=self.letterhead)
     invoice.json = {"jobs": [{"job": self.job}], "add_terms": False}
     pdf_type.invoice.serialize(invoice)
     self.maxDiff = None
     self.assertEqual(
         {
             "jobs": [{
                 "tasks": [],
                 "groups": [{
                     "group.id":
                     2,
                     "code":
                     "01.01",
                     "name":
                     self.group.name,
                     "description":
                     "",
                     "progress":
                     Decimal("0.00"),
                     "estimate":
                     Decimal("0.0000"),
                     "tasks": [{
                         "task.id":
                         1,
                         "code":
                         "01.01.001",
                         "name":
                         self.task.name,
                         "description":
                         "",
                         "is_provisional":
                         False,
                         "variant_group":
                         0,
                         "variant_serial":
                         0,
                         "qty":
                         Decimal("0.0000"),
                         "complete":
                         Decimal("0.0000"),
                         "unit":
                         "",
                         "price":
                         Decimal("0.0000"),
                         "progress":
                         Decimal("0.00"),
                         "estimate":
                         Decimal("0.0000"),
                         "lineitems": [{
                             "lineitem.id": 1,
                             "name": self.lineitem.name,
                             "qty": Decimal("0.0000"),
                             "unit": "",
                             "price": Decimal("0.0000"),
                             "estimate": Decimal("0.0000"),
                         }],
                     }],
                     "groups": [],
                 }],
             }],
             "add_terms":
             False,
             "debit":
             Amount.zero(),
             "invoiced":
             Amount.zero(),
             "paid":
             Amount.zero(),
             "unpaid":
             Amount.zero(),
             "payments": [],
             "job_debits": {},
         },
         invoice.json,
     )
예제 #23
0
    def test_happy_path_create_update_and_render(self):

        response = self.client.get(
            reverse("refund.create", args=[self.project.id]))
        self.assertEqual(200, response.status_code)

        data = {
            "title": "Refund #1",
            "header": "new header",
            "footer": "new footer",
            "document_date": "2015-01-01",
            "job-0-job_id": self.job.id,
            "job-0-refund_net": "1",
            "job-0-refund_tax": "1",
            "job-0-credit_net": "0",
            "job-0-credit_tax": "0",
            "job-1-job_id": self.job2.id,
            "job-1-refund_net": "0",
            "job-1-refund_tax": "0",
            "job-1-credit_net": "1",
            "job-1-credit_tax": "1",
        }
        data.update(self.make_management_form())
        response = self.client.post(
            reverse("refund.create", args=[self.project.id]), data)
        self.assertEqual(302, response.status_code)

        response = self.client.get(
            reverse("refund.pdf",
                    args=[self.project.id, "print",
                          Refund.objects.first().id]))
        self.assertEqual(200, response.status_code)

        refund = Refund.objects.order_by("id").first()
        self.assertEqual(refund.document_date, date(2015, 1, 1))
        self.assertEqual(refund.json["refund_total"],
                         Amount(Decimal("1.0"), Decimal("1.0")))
        self.assertEqual(refund.json["credit_total"],
                         Amount(Decimal("1.0"), Decimal("1.0")))

        data = {
            "title": "Refund #1",
            "header": "updated header",
            "footer": "updated footer",
            "document_date": "2015-07-28",
            "job-0-job_id": self.job.id,
            "job-0-refund_net": "2",
            "job-0-refund_tax": "2",
            "job-0-credit_net": "0",
            "job-0-credit_tax": "0",
            "job-1-job_id": self.job2.id,
            "job-1-refund_net": "0",
            "job-1-refund_tax": "0",
            "job-1-credit_net": "2",
            "job-1-credit_tax": "2",
        }
        data.update(self.make_management_form())
        response = self.client.post(
            reverse("refund.update", args=[self.project.id, refund.id]), data)
        self.assertEqual(302, response.status_code)
        self.assertRedirects(response,
                             reverse("project.view", args=[self.project.id]))

        refund.refresh_from_db()
        self.assertEqual(refund.document_date, date(2015, 7, 28))
        self.assertEqual(refund.json["refund_total"],
                         Amount(Decimal("2.0"), Decimal("2.0")))
        self.assertEqual(refund.json["credit_total"],
                         Amount(Decimal("2.0"), Decimal("2.0")))
예제 #24
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.estimate_amount = Amount.from_net(self.job.estimate, TAX_RATE)