diff --git a/accounting/models.py b/accounting/models.py index e8dd323..ce0e95d 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -118,6 +118,8 @@ class Transaction(models.Model): if self._has_order_hole is None: orders = [x.ord for x in Transaction.objects.filter( date=self.date)] + if len(orders) == 0: + self._has_order_hole = False if max(orders) != len(orders): self._has_order_hole = True elif min(orders) != 1: @@ -198,23 +200,41 @@ class Record(models.Model): db_column="updatedby", related_name="updated_accounting_records") + _debit_amount = None + @property def debit_amount(self): """The debit amount of this accounting record""" + if self._debit_amount is not None: + return self._debit_amount return self.amount if not self.is_credit else None + @debit_amount.setter + def debit_amount(self, value): + self._debit_amount = value + + _credit_amount = None + @property def credit_amount(self): """The credit amount of this accounting record""" + if self._credit_amount is not None: + return self._credit_amount return self.amount if self.is_credit else None - @property - def accumulative_balance(self): - return self._accumulative_balance + @credit_amount.setter + def credit_amount(self, value): + self._credit_amount = value - @accumulative_balance.setter - def accumulative_balance(self, value): - self._accumulative_balance = value + _balance = None + + @property + def balance(self): + return self._balance + + @balance.setter + def balance(self, value): + self._balance = value def __str__(self): """Returns the string representation of this accounting diff --git a/accounting/templates/accounting/cash.html b/accounting/templates/accounting/cash.html index 12bacd4..54814b5 100644 --- a/accounting/templates/accounting/cash.html +++ b/accounting/templates/accounting/cash.html @@ -95,7 +95,7 @@ First written: 2020/7/1 {{ record.transaction.date|smart_date }} {{ record.subject.title_zhtw }} - {{ record.summary }}{% if not record.transaction.is_balanced %} + {{ record.summary|default:"" }}{% if not record.transaction.is_balanced %} {% trans "Unbalanced" context "Accounting|" as text %} {{ text|force_escape }} @@ -106,14 +106,16 @@ First written: 2020/7/1 {{ text|force_escape }} {% endif %} - {{ record.credit_amount|default:""|intcomma:False }} - {{ record.debit_amount|default:""|intcomma:False }} - {{ record.amount|intcomma:False }} + {% if record.credit_amount is not None %}{{ record.credit_amount|intcomma:False }}{% endif %} + {% if record.debit_amount is not None %}{{ record.debit_amount|intcomma:False }}{% endif %} + {{ record.balance|intcomma:False }} - - - {% trans "View" context "Accounting|" as text %}{{ text|force_escape }} - + {% if record.sn is not None %} + + + {% trans "View" context "Accounting|" as text %}{{ text|force_escape }} + + {% endif %} {% endfor %} @@ -124,32 +126,47 @@ First written: 2020/7/1 diff --git a/accounting/views/__init__.py b/accounting/views/__init__.py index 5c914d0..fe5f609 100644 --- a/accounting/views/__init__.py +++ b/accounting/views/__init__.py @@ -18,7 +18,9 @@ """The view controllers of the accounting application. """ +from datetime import timedelta +from django.db import connection from django.http import HttpResponseRedirect from django.shortcuts import render from django.urls import reverse @@ -31,7 +33,7 @@ from accounting.models import Record, Transaction, Subject from mia import settings from mia_core.digest_auth import digest_login_required from mia_core.period import Period -from mia_core.utils import Pagination +from mia_core.utils import Pagination, SqlQuery @require_GET @@ -65,6 +67,7 @@ def cash_home(request): @digest_login_required def cash(request, subject_code, period_spec): """The cash account report.""" + # The period first_txn = Transaction.objects.order_by("date").first() data_start = first_txn.date if first_txn is not None else None last_txn = Transaction.objects.order_by("-date").first() @@ -72,13 +75,12 @@ def cash(request, subject_code, period_spec): period = Period( get_language(), data_start, data_end, period_spec) - # The list data + # The SQL query if subject_code == "0": subject = Subject(code="0") subject.title_zhtw = pgettext( "Accounting|", "Current Assets And Liabilities") - records = Record.objects.raw( - """SELECT r.* + select_records = """SELECT r.* FROM accounting_records AS r INNER JOIN (SELECT t1.sn AS sn, @@ -106,12 +108,20 @@ ORDER BY t.date, t.ord, CASE WHEN is_credit THEN 1 ELSE 2 END, - r.ord""", + r.ord""" + sql_records = SqlQuery( + select_records, [period.start, period.end]) + select_balance_before = """SELECT + SUM(CASE WHEN is_credit THEN 1 ELSE -1 END * amount) AS amount + FROM (%s) AS b""" % select_records + sql_balance_before = SqlQuery( + select_balance_before, + [data_start, period.start - timedelta(days=1)]) else: - subject = Subject.objects.filter(code=subject_code).first() - records = Record.objects.raw( - """SELECT r.* + subject = Subject.objects.filter( + code=subject_code).first() + select_records = """SELECT r.* FROM accounting_records AS r INNER JOIN (SELECT t1.sn AS sn, @@ -133,11 +143,55 @@ ORDER BY t.date, t.ord, CASE WHEN is_credit THEN 1 ELSE 2 END, - r.ord""", + r.ord""" + sql_records = SqlQuery( + select_records, [period.start, period.end, subject.code + "%", subject.code + "%"]) + select_balance_before = """SELECT + SUM(CASE WHEN is_credit THEN 1 ELSE -1 END * amount) AS amount + FROM (%s) AS b""" % select_records + sql_balance_before = SqlQuery( + select_balance_before, + [data_start, + period.start - timedelta(days=1), + subject.code + "%", + subject.code + "%"]) + # The list data + records = list(Record.objects.raw( + sql_records.sql, + sql_records.params)) + with connection.cursor() as cursor: + cursor.execute( + sql_balance_before.sql, sql_balance_before.params) + row = cursor.fetchone() + balance_before = row[0] + if balance_before is None: + balance_before = 0 + balance = balance_before + for record in records: + sign = 1 if record.is_credit else -1 + balance = balance + sign * record.amount + record.balance = balance + record_sum = Record( + transaction=Transaction(date=records[-1].transaction.date), + subject=subject, + summary=pgettext("Accounting|", "Total"), + balance=balance + ) + record_sum.credit_amount = sum([ + x.amount for x in records if x.is_credit]) + record_sum.debit_amount = sum([ + x.amount for x in records if not x.is_credit]) + records.insert(0, Record( + transaction=Transaction(date=period.start), + subject=Subject.objects.filter(code="3351").first(), + is_credit=balance_before >= 0, + amount=abs(balance_before), + balance=balance_before)) + records.append(record_sum) pagination = Pagination(request, records, True) return render(request, "accounting/cash.html", { "records": pagination.records, diff --git a/mia_core/utils.py b/mia_core/utils.py index 8cfb623..7480b5a 100644 --- a/mia_core/utils.py +++ b/mia_core/utils.py @@ -394,3 +394,21 @@ class PaginationException(Exception): def __init__(self, url): self.url = url + + +class SqlQuery: + """A SQL query statement with its parameters. + + Args: + sql (str): The SQL statement. + params (list[str]): the parameters. + + Attributes: + sql (str): The SQL statement. + params (list[str]): the parameters. + """ + sql = "" + params = [] + def __init__(self, sql, params): + self.sql = sql + self.params = params