Example #1
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
Example #2
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
Example #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)
Example #4
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()
Example #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.")
         )
Example #6
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)
Example #7
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
Example #8
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")
Example #9
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
        )
Example #10
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()
Example #11
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
            )
Example #12
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
Example #13
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
Example #14
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
Example #15
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,
     )
Example #16
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
Example #17
0
class InvoiceFactory(factory.django.DjangoModelFactory):

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

    class Meta:
        model = Invoice
Example #18
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()