From 06b8d470fb89ff47a31ed4218c383a535286b9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Tue, 30 Jun 2020 20:59:24 +0800 Subject: [PATCH] Added simple views for the accounting cash report. --- accounting/models.py | 58 ++++++++++- accounting/templates/accounting/cash.html | 11 +++ accounting/urls.py | 31 ++++++ accounting/utils.py | 40 ++++++++ accounting/views.py | 115 +++++++++++++++++++++- 5 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 accounting/templates/accounting/cash.html create mode 100644 accounting/urls.py create mode 100644 accounting/utils.py diff --git a/accounting/models.py b/accounting/models.py index fe6c70f..40eb5f6 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -67,6 +67,40 @@ class Transaction(models.Model): updated_by_sn = models.IntegerField( default=923153018, db_column="updatedby") + @property + def debit_records(self): + """The debit records of this transaction.""" + return [x for x in self.record_set.all() if not x.is_credit] + + @property + def credit_records(self): + """The credit records of this transaction.""" + return [x for x in self.record_set.all() if x.is_credit] + + @property + def is_balanced(self): + """Whether the sum of the amounts of the debit records is the same as the sum of the amounts of the credit + records. """ + debit_sum = sum([x.amount for x in self.debit_records]) + credit_sum = sum([x.amount for x in self.credit_records]) + return debit_sum == credit_sum + + @property + def is_cash_income(self): + """Whether this transaction is a cash income transaction.""" + debit_records = self.debit_records + return (len(debit_records) == 1 + and debit_records[0].subject.code == "1111" + and debit_records[0].summary is None) + + @property + def is_cash_expense(self): + """Whether this transaction is a cash expense transaction.""" + credit_records = self.credit_records + return (len(credit_records) == 1 + and credit_records[0].subject.code == "1111" + and credit_records[0].summary is None) + def __str__(self): """Returns the string representation of this accounting transaction.""" @@ -98,12 +132,30 @@ class Record(models.Model): updated_by_sn = models.IntegerField( default=923153018, db_column="updatedby") + @property + def debit_amount(self): + """The debit amount of this accounting record""" + return self.amount if not self.is_credit else None + + @property + def credit_amount(self): + """The credit amount of this accounting record""" + return self.amount if self.is_credit else None + + @property + def accumulative_balance(self): + return self._accumulative_balance + + @accumulative_balance.setter + def accumulative_balance(self, value): + self._accumulative_balance = value + def __str__(self): """Returns the string representation of this accounting record.""" - return self.transaction.date.__str__() + " "\ - + self.subject.title_zhtw.__str__()\ - + " " + self.summary.__str__()\ + return self.transaction.date.__str__() + " " \ + + self.subject.title_zhtw.__str__() \ + + " " + self.summary.__str__() \ + " " + self.amount.__str__() class Meta: diff --git a/accounting/templates/accounting/cash.html b/accounting/templates/accounting/cash.html new file mode 100644 index 0000000..2a1c8f2 --- /dev/null +++ b/accounting/templates/accounting/cash.html @@ -0,0 +1,11 @@ +

Cash Report

+ +{% if records %} + +{% else %} +

No data.

+{% endif %} diff --git a/accounting/urls.py b/accounting/urls.py new file mode 100644 index 0000000..51b17ee --- /dev/null +++ b/accounting/urls.py @@ -0,0 +1,31 @@ +# The accounting application of the Mia project. +# by imacat , 2020/6/30 + +# Copyright (c) 2020 imacat. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""The route settings of the accounting application. + +""" + +from django.urls import path + +from . import views + +app_name = "accounting" +urlpatterns = [ + path("", views.home, name="home"), + path("cash", views.cash_home, name="cash.home"), + path("cash//", views.CashReportView.as_view(), name="cash"), +] diff --git a/accounting/utils.py b/accounting/utils.py new file mode 100644 index 0000000..d5dbf2b --- /dev/null +++ b/accounting/utils.py @@ -0,0 +1,40 @@ +# The accounting application of the Mia project. +# by imacat , 2020/6/30 + +# Copyright (c) 2020 imacat. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""The utilities of the accounting application. + +""" + + +class PeriodParser: + """The period parser. + + Attributes: + start: The start of the period. + end: The end of the period. + """ + start = None + end = None + + def __init__(self, period_spec): + """Constructs a new period parser. + + Args: + period_spec (str): The period specification. + """ + self.start = period_spec + "-01" + self.end = period_spec + "-30" diff --git a/accounting/views.py b/accounting/views.py index 91ea44a..5068023 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -1,3 +1,116 @@ +# The accounting application of the Mia project. +# by imacat , 2020/6/30 + +# Copyright (c) 2020 imacat. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""The view controllers of the accounting application. + +""" + +from django.http import HttpResponseRedirect from django.shortcuts import render -# Create your views here. +from django.urls import reverse +from django.utils import dateformat, timezone +from django.views import generic +from django.views.decorators.http import require_http_methods + +from accounting.models import Record +from accounting.utils import PeriodParser +from mia import settings + + +@require_http_methods(["GET"]) +def home(request): + """The accounting home page. + + Returns: + The redirection to the default accounting report. + """ + return HttpResponseRedirect(reverse("accounting:cash.home")) + + +@require_http_methods(["GET"]) +def cash_home(request): + """The accounting cash report home page. + + Returns: + The redirection to the default subject and month. + """ + subject_code = settings.ACCOUNTING["DEFAULT_CASH_SUBJECT"] + period_spec = dateformat.format(timezone.now(), "Y-m") + return HttpResponseRedirect( + reverse("accounting:cash", args=(subject_code, period_spec))) + + +class CashReportView(generic.ListView): + """The accounting cash report.""" + http_method_names = ["get"] + template_name = "accounting/cash.html" + context_object_name = "records" + + def get_queryset(self): + """Return the records for the accounting cash report.""" + period = PeriodParser(self.kwargs["period_spec"]) + if self.kwargs["subject_code"] == "0": + records = Record.objects.raw( + """SELECT r.* +FROM accounting_records AS r + INNER JOIN (SELECT + t1.sn AS sn, + t1.date AS date, + t1.ord AS ord + FROM accounting_records AS r1 + LEFT JOIN accounting_transactions AS t1 ON r1.transaction_sn=t1.sn + LEFT JOIN accounting_subjects AS s1 ON r1.subject_sn = s1.sn + WHERE (s1.code LIKE '11%%' + OR s1.code LIKE '12%%' + OR s1.code LIKE '21%%' + OR s1.code LIKE '22%%') + AND t1.date >= %s + AND t1.date <= %s + GROUP BY t1.sn) AS t + ON r.transaction_sn=t.sn + LEFT JOIN accounting_subjects AS s ON r.subject_sn = s.sn +WHERE s.code NOT LIKE '11%%' + AND s.code NOT LIKE '12%%' + AND s.code NOT LIKE '21%%' + AND s.code NOT LIKE '22%%' +ORDER BY t.date, t.ord, CASE WHEN is_credit THEN 1 ELSE 2 END, r.ord""", + [period.start, period.end]) + else: + records = Record.objects.raw( + """SELECT r.* +FROM accounting_records AS r + INNER JOIN (SELECT + t1.sn AS sn, + t1.date AS date, + t1.ord AS ord + FROM accounting_records AS r1 + LEFT JOIN accounting_transactions AS t1 ON r1.transaction_sn=t1.sn + LEFT JOIN accounting_subjects AS s1 ON r1.subject_sn = s1.sn + WHERE t1.date >= %s + AND t1.date <= %s + AND s1.code LIKE %s + GROUP BY t1.sn) AS t + ON r.transaction_sn=t.sn + LEFT JOIN accounting_subjects AS s ON r.subject_sn = s.sn +WHERE s.code NOT LIKE %s +ORDER BY t.date, t.ord, CASE WHEN is_credit THEN 1 ELSE 2 END, r.ord""", + [period.start, + period.end, + self.kwargs["subject_code"] + "%", + self.kwargs["subject_code"] + "%"]) + return records