Added the balance-before record and the sum record to the cash account report. Moved the SqlQuery utility from the accounting application to the Mia core application.

This commit is contained in:
依瑪貓 2020-07-13 19:54:27 +08:00
parent fa7416d0f3
commit 39c75f772a
4 changed files with 150 additions and 41 deletions

View File

@ -118,6 +118,8 @@ class Transaction(models.Model):
if self._has_order_hole is None: if self._has_order_hole is None:
orders = [x.ord for x in Transaction.objects.filter( orders = [x.ord for x in Transaction.objects.filter(
date=self.date)] date=self.date)]
if len(orders) == 0:
self._has_order_hole = False
if max(orders) != len(orders): if max(orders) != len(orders):
self._has_order_hole = True self._has_order_hole = True
elif min(orders) != 1: elif min(orders) != 1:
@ -198,23 +200,41 @@ class Record(models.Model):
db_column="updatedby", db_column="updatedby",
related_name="updated_accounting_records") related_name="updated_accounting_records")
_debit_amount = None
@property @property
def debit_amount(self): def debit_amount(self):
"""The debit amount of this accounting record""" """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 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 @property
def credit_amount(self): def credit_amount(self):
"""The credit amount of this accounting record""" """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 return self.amount if self.is_credit else None
@property @credit_amount.setter
def accumulative_balance(self): def credit_amount(self, value):
return self._accumulative_balance self._credit_amount = value
@accumulative_balance.setter _balance = None
def accumulative_balance(self, value):
self._accumulative_balance = value @property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
self._balance = value
def __str__(self): def __str__(self):
"""Returns the string representation of this accounting """Returns the string representation of this accounting

View File

@ -95,7 +95,7 @@ First written: 2020/7/1
<tr class="{% if not record.transaction.is_balanced or record.transaction.has_order_hole %} table-danger {% endif %}"> <tr class="{% if not record.transaction.is_balanced or record.transaction.has_order_hole %} table-danger {% endif %}">
<td>{{ record.transaction.date|smart_date }}</td> <td>{{ record.transaction.date|smart_date }}</td>
<td>{{ record.subject.title_zhtw }}</td> <td>{{ record.subject.title_zhtw }}</td>
<td>{{ record.summary }}{% if not record.transaction.is_balanced %} <td>{{ record.summary|default:"" }}{% if not record.transaction.is_balanced %}
<span class="badge badge-danger badge-pill"> <span class="badge badge-danger badge-pill">
{% trans "Unbalanced" context "Accounting|" as text %} {% trans "Unbalanced" context "Accounting|" as text %}
{{ text|force_escape }} {{ text|force_escape }}
@ -106,14 +106,16 @@ First written: 2020/7/1
{{ text|force_escape }} {{ text|force_escape }}
</span> </span>
{% endif %}</td> {% endif %}</td>
<td class="amount">{{ record.credit_amount|default:""|intcomma:False }}</td> <td class="amount">{% if record.credit_amount is not None %}{{ record.credit_amount|intcomma:False }}{% endif %}</td>
<td class="amount">{{ record.debit_amount|default:""|intcomma:False }}</td> <td class="amount">{% if record.debit_amount is not None %}{{ record.debit_amount|intcomma:False }}{% endif %}</td>
<td class="amount">{{ record.amount|intcomma:False }}</td> <td class="amount">{{ record.balance|intcomma:False }}</td>
<td class="actions"> <td class="actions">
<a href="{{ record.transaction.get_absolute_url }}" class="btn btn-info" role="button"> {% if record.sn is not None %}
<i class="fas fa-eye"></i> <a href="{{ record.transaction.get_absolute_url }}" class="btn btn-info" role="button">
<span class="d-none d-lg-inline">{% trans "View" context "Accounting|" as text %}{{ text|force_escape }}</span> <i class="fas fa-eye"></i>
</a> <span class="d-none d-lg-inline">{% trans "View" context "Accounting|" as text %}{{ text|force_escape }}</span>
</a>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -124,32 +126,47 @@ First written: 2020/7/1
<ul class="list-group d-md-none"> <ul class="list-group d-md-none">
{% for record in records %} {% for record in records %}
<li class="list-group-item {% if not record.transaction.is_balanced or record.transaction.has_order_hole %} list-group-item-danger {% endif %}"> <li class="list-group-item {% if not record.transaction.is_balanced or record.transaction.has_order_hole %} list-group-item-danger {% endif %}">
<a class="list-group-item-action" href="{{ record.transaction.get_absolute_url }}"> {% if record.sn is not None %}
<a class="list-group-item-action" href="{{ record.transaction.get_absolute_url }}">
<div class="date-subject-line d-flex justify-content-between align-items-center">
{{ record.transaction.date|smart_date }} {{ record.subject.title_zhtw }}
</div>
<div class="d-flex justify-content-between align-items-center">
<div>
{{ record.summary|default:"" }}
{% if not record.transaction.is_balanced %}
<span class="badge badge-danger badge-pill">
{% trans "Unbalanced" context "Accounting|" as text %}
{{ text|force_escape }}
</span>
{% endif %}
{% if record.transaction.has_order_hole %}
<span class="badge badge-danger badge-pill">
{% trans "Need Reorder" context "Accounting|" as text %}
{{ text|force_escape }}
</span>
{% endif %}
</div>
</div>
<div>
{% if record.credit_amount is not None %}<span class="badge badge-success badge-pill">{{ record.credit_amount|intcomma:False }}</span>{% endif %}
{% if record.debit_amount is not None %}<span class="badge badge-warning badge-pill">-{{ record.debit_amount|intcomma:False }}</span>{% endif %}
<span class="badge {% if record.balance < 0 %} badge-danger {% else %} badge-primary {% endif %} badge-pill">{{ record.balance|intcomma:False }}</span>
</div>
</a>
{% else %}
<div class="date-subject-line d-flex justify-content-between align-items-center"> <div class="date-subject-line d-flex justify-content-between align-items-center">
{{ record.transaction.date|smart_date }} {{ record.subject.title_zhtw }} {{ record.transaction.date|smart_date }} {{ record.subject.title_zhtw }}
</div> </div>
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<div> <div>{{ record.summary|default:"" }}</div>
{{ record.summary }}
{% if not record.transaction.is_balanced %}
<span class="badge badge-danger badge-pill">
{% trans "Unbalanced" context "Accounting|" as text %}
{{ text|force_escape }}
</span>
{% endif %}
{% if record.transaction.has_order_hole %}
<span class="badge badge-danger badge-pill">
{% trans "Need Reorder" context "Accounting|" as text %}
{{ text|force_escape }}
</span>
{% endif %}
</div>
</div> </div>
<div> <div>
<span class="badge {% if not record.is_credit %} badge-warning {% else %} badge-success {% endif %} badge-pill">{{ record.amount|intcomma:False }}</span> {% if record.credit_amount is not None %}<span class="badge badge-success badge-pill">{{ record.credit_amount|intcomma:False }}</span>{% endif %}
{% if record.debit_amount is not None %}<span class="badge badge-warning badge-pill">-{{ record.debit_amount|intcomma:False }}</span>{% endif %}
<span class="badge {% if record.balance < 0 %} badge-danger {% else %} badge-primary {% endif %} badge-pill">{{ record.balance|intcomma:False }}</span> <span class="badge {% if record.balance < 0 %} badge-danger {% else %} badge-primary {% endif %} badge-pill">{{ record.balance|intcomma:False }}</span>
</div> </div>
</a> {% endif %}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View File

@ -18,7 +18,9 @@
"""The view controllers of the accounting application. """The view controllers of the accounting application.
""" """
from datetime import timedelta
from django.db import connection
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
@ -31,7 +33,7 @@ from accounting.models import Record, Transaction, Subject
from mia import settings from mia import settings
from mia_core.digest_auth import digest_login_required from mia_core.digest_auth import digest_login_required
from mia_core.period import Period from mia_core.period import Period
from mia_core.utils import Pagination from mia_core.utils import Pagination, SqlQuery
@require_GET @require_GET
@ -65,6 +67,7 @@ def cash_home(request):
@digest_login_required @digest_login_required
def cash(request, subject_code, period_spec): def cash(request, subject_code, period_spec):
"""The cash account report.""" """The cash account report."""
# The period
first_txn = Transaction.objects.order_by("date").first() first_txn = Transaction.objects.order_by("date").first()
data_start = first_txn.date if first_txn is not None else None data_start = first_txn.date if first_txn is not None else None
last_txn = Transaction.objects.order_by("-date").first() last_txn = Transaction.objects.order_by("-date").first()
@ -72,13 +75,12 @@ def cash(request, subject_code, period_spec):
period = Period( period = Period(
get_language(), data_start, data_end, get_language(), data_start, data_end,
period_spec) period_spec)
# The list data # The SQL query
if subject_code == "0": if subject_code == "0":
subject = Subject(code="0") subject = Subject(code="0")
subject.title_zhtw = pgettext( subject.title_zhtw = pgettext(
"Accounting|", "Current Assets And Liabilities") "Accounting|", "Current Assets And Liabilities")
records = Record.objects.raw( select_records = """SELECT r.*
"""SELECT r.*
FROM accounting_records AS r FROM accounting_records AS r
INNER JOIN (SELECT INNER JOIN (SELECT
t1.sn AS sn, t1.sn AS sn,
@ -106,12 +108,20 @@ ORDER BY
t.date, t.date,
t.ord, t.ord,
CASE WHEN is_credit THEN 1 ELSE 2 END, CASE WHEN is_credit THEN 1 ELSE 2 END,
r.ord""", r.ord"""
sql_records = SqlQuery(
select_records,
[period.start, period.end]) [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: else:
subject = Subject.objects.filter(code=subject_code).first() subject = Subject.objects.filter(
records = Record.objects.raw( code=subject_code).first()
"""SELECT r.* select_records = """SELECT r.*
FROM accounting_records AS r FROM accounting_records AS r
INNER JOIN (SELECT INNER JOIN (SELECT
t1.sn AS sn, t1.sn AS sn,
@ -133,11 +143,55 @@ ORDER BY
t.date, t.date,
t.ord, t.ord,
CASE WHEN is_credit THEN 1 ELSE 2 END, CASE WHEN is_credit THEN 1 ELSE 2 END,
r.ord""", r.ord"""
sql_records = SqlQuery(
select_records,
[period.start, [period.start,
period.end, period.end,
subject.code + "%", subject.code + "%",
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) pagination = Pagination(request, records, True)
return render(request, "accounting/cash.html", { return render(request, "accounting/cash.html", {
"records": pagination.records, "records": pagination.records,

View File

@ -394,3 +394,21 @@ class PaginationException(Exception):
def __init__(self, url): def __init__(self, url):
self.url = 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