Added the ledger in the accounting application.
This commit is contained in:
		| @@ -279,6 +279,32 @@ class Record(models.Model): | |||||||
|     def has_order_hole(self, value): |     def has_order_hole(self, value): | ||||||
|         self._has_order_hole = value |         self._has_order_hole = value | ||||||
|  |  | ||||||
|  |     _is_credit_card_paid = None | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def is_credit_card_paid(self): | ||||||
|  |         # TODO: To be done | ||||||
|  |         if self._is_credit_card_paid is None: | ||||||
|  |             self._is_credit_card_paid = True | ||||||
|  |         return self._is_credit_card_paid | ||||||
|  |  | ||||||
|  |     @is_credit_card_paid.setter | ||||||
|  |     def is_credit_card_paid(self, value): | ||||||
|  |         self._is_credit_card_paid = value | ||||||
|  |  | ||||||
|  |     _is_existing_equipment = None | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def is_existing_equipment(self): | ||||||
|  |         # TODO: To be done | ||||||
|  |         if self._is_existing_equipment is None: | ||||||
|  |             self._is_existing_equipment = False | ||||||
|  |         return self._is_existing_equipment | ||||||
|  |  | ||||||
|  |     @is_existing_equipment.setter | ||||||
|  |     def is_existing_equipment(self, value): | ||||||
|  |         self._is_existing_equipment = value | ||||||
|  |  | ||||||
|     def __str__(self): |     def __str__(self): | ||||||
|         """Returns the string representation of this accounting |         """Returns the string representation of this accounting | ||||||
|         record.""" |         record.""" | ||||||
|   | |||||||
							
								
								
									
										230
									
								
								accounting/templates/accounting/ledger.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								accounting/templates/accounting/ledger.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | |||||||
|  | {% extends "base.html" %} | ||||||
|  | {% comment %} | ||||||
|  | The Mia Accounting Application | ||||||
|  | cash.html: The template for the ledger reports | ||||||
|  |  | ||||||
|  |  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. | ||||||
|  |  | ||||||
|  | Author: imacat@mail.imacat.idv.tw (imacat) | ||||||
|  | First written: 2020/7/16 | ||||||
|  | {% endcomment %} | ||||||
|  | {% load i18n %} | ||||||
|  | {% load humanize %} | ||||||
|  | {% load accounting %} | ||||||
|  |  | ||||||
|  | {% block settings %} | ||||||
|  |   {% blocktrans asvar title with subject=current_subject.title|title period=period.description context "Accounting|" %}Ledger for {{ subject }} in {{ period }}{% endblocktrans %} | ||||||
|  |   {% setvar "title" title %} | ||||||
|  |   {% setvar "use_period_chooser" True %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  |  | ||||||
|  | <div class="btn-group btn-actions"> | ||||||
|  |   <div class="btn-group"> | ||||||
|  |     <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown"> | ||||||
|  |       <i class="fas fa-edit"></i> | ||||||
|  |       {% trans "New" context "Accounting|" as text %} | ||||||
|  |       {{ text|force_escape }} | ||||||
|  |     </button> | ||||||
|  |     <div class="dropdown-menu"> | ||||||
|  |       {% url "accounting:transaction.create" "expense" as url %} | ||||||
|  |       <a class="dropdown-item" href="{% url_query url r=request.get_full_path %}"> | ||||||
|  |         {% trans "Cash Expense" context "Accounting|" as text %} | ||||||
|  |         {{ text|force_escape }} | ||||||
|  |       </a> | ||||||
|  |       {% url "accounting:transaction.create" "income" as url %} | ||||||
|  |       <a class="dropdown-item" href="{% url_query url r=request.get_full_path %}"> | ||||||
|  |         {% trans "Cash Income" context "Accounting|" as text %} | ||||||
|  |         {{ text|force_escape }} | ||||||
|  |       </a> | ||||||
|  |       {% url "accounting:transaction.create" "transfer" as url %} | ||||||
|  |       <a class="dropdown-item" href="{% url_query url r=request.get_full_path %}"> | ||||||
|  |         {% trans "Transfer" context "Accounting|" as text %} | ||||||
|  |         {{ text|force_escape }} | ||||||
|  |       </a> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   {% with current_report_icon="fas fa-file-invoice-dollar" %} | ||||||
|  |     {% trans "Ledger" context "Accounting|" as current_report_title %} | ||||||
|  |     {% include "accounting/include/report-chooser.html" %} | ||||||
|  |   {% endwith %} | ||||||
|  |   <div class="btn-group"> | ||||||
|  |     <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown"> | ||||||
|  |       <span class="d-none d-md-inline">{{ current_subject.title|title }}</span> | ||||||
|  |       <span class="d-md-none">{% trans "Subject" context "Accounting|" as text %}{{ text|force_escape }}</span> | ||||||
|  |     </button> | ||||||
|  |     <div class="dropdown-menu subject-picker"> | ||||||
|  |       {% for subject in subjects %} | ||||||
|  |         <a class="dropdown-item {% if subject.code == current_subject.code %} active {% endif %}" href="{% url "accounting:ledger" subject.code period.spec %}"> | ||||||
|  |           {{ subject.code }} {{ subject.title|title }} | ||||||
|  |         </a> | ||||||
|  |       {% endfor %} | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  |   <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#period-modal"> | ||||||
|  |     <i class="far fa-calendar-alt"></i> | ||||||
|  |     <span class="d-none d-md-inline">{{ period.description }}</span> | ||||||
|  |     <span class="d-md-none">{% trans "Period" context "Period|" as text %}{{ text|force_escape }}</span> | ||||||
|  |   </button> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  | {% include "mia_core/include/period-chooser.html" %} | ||||||
|  |  | ||||||
|  | {% if records %} | ||||||
|  |   {% include "mia_core/include/pagination.html" %} | ||||||
|  |  | ||||||
|  |   {# The table for large screens #} | ||||||
|  |   <table class="table table-striped table-hover d-none d-md-table general-journal-table"> | ||||||
|  |     <thead> | ||||||
|  |       <tr> | ||||||
|  |         <th scope="col">{% trans "Date" context "Accounting|" as text %}{{ text|force_escape }}</th> | ||||||
|  |         <th scope="col">{% trans "Subject" context "Accounting|" as text %}{{ text|force_escape }}</th> | ||||||
|  |         <th scope="col">{% trans "Summary" context "Accounting|" as text %}{{ text|force_escape }}</th> | ||||||
|  |         <th class="amount" scope="col">{% trans "Debit" context "Accounting|" as text %}{{ text|force_escape }}</th> | ||||||
|  |         <th class="amount" scope="col">{% trans "Credit" context "Accounting|" as text %}{{ text|force_escape }}</th> | ||||||
|  |         <th class="amount" scope="col">{% trans "Balance" context "Accounting|" as text %}{{ text|force_escape }}</th> | ||||||
|  |         <th class="actions" scope="col">{% trans "View" context "Accounting|" as text %}{{ text|force_escape }}</th> | ||||||
|  |       </tr> | ||||||
|  |     </thead> | ||||||
|  |     <tbody> | ||||||
|  |       {% for record in records %} | ||||||
|  |         <tr class="{% if not record.is_balanced or record.has_order_hole or not record.is_credit_card_paid %} table-danger {% endif %}{% if record.is_existing_equipment %} table-info {% endif %}"> | ||||||
|  |           <td>{{ record.transaction.date|smart_date }}</td> | ||||||
|  |           <td>{{ record.subject.title|title }}</td> | ||||||
|  |           <td>{{ record.summary|default:"" }}{% if not record.is_balanced %} | ||||||
|  |             <span class="badge badge-danger badge-pill"> | ||||||
|  |               {% trans "Unbalanced" context "Accounting|" as text %} | ||||||
|  |               {{ text|force_escape }} | ||||||
|  |             </span> | ||||||
|  |           {% endif %}{% if record.has_order_hole %} | ||||||
|  |             <span class="badge badge-danger badge-pill"> | ||||||
|  |               {% trans "Need Reorder" context "Accounting|" as text %} | ||||||
|  |               {{ text|force_escape }} | ||||||
|  |             </span> | ||||||
|  |           {% endif %}{% if not record.is_credit_card_paid %} | ||||||
|  |             <span class="badge badge-danger badge-pill"> | ||||||
|  |               {% trans "Unpaid" context "Accounting|" as text %} | ||||||
|  |               {{ text|force_escape }} | ||||||
|  |             </span> | ||||||
|  |           {% endif %}{% if record.is_existing_equipment %} | ||||||
|  |             <span class="badge badge-info badge-pill"> | ||||||
|  |               {% trans "Existing" context "Accounting|" as text %} | ||||||
|  |               {{ text|force_escape }} | ||||||
|  |             </span> | ||||||
|  |           {% endif %}</td> | ||||||
|  |           <td class="amount">{{ record.debit_amount|accounting_amount }}</td> | ||||||
|  |           <td class="amount">{{ record.credit_amount|accounting_amount }}</td> | ||||||
|  |           <td class="amount {% if record.balance < 0 %} text-danger {% endif %}">{{ record.balance|accounting_amount }}</td> | ||||||
|  |           <td class="actions"> | ||||||
|  |             {% if record.sn is not None %} | ||||||
|  |               <a href="{{ record.transaction.get_absolute_url }}" class="btn btn-info" role="button"> | ||||||
|  |                 <i class="fas fa-eye"></i> | ||||||
|  |                 <span class="d-none d-lg-inline">{% trans "View" context "Accounting|" as text %}{{ text|force_escape }}</span> | ||||||
|  |               </a> | ||||||
|  |             {% endif %} | ||||||
|  |           </td> | ||||||
|  |         </tr> | ||||||
|  |       {% endfor %} | ||||||
|  |     </tbody> | ||||||
|  |   </table> | ||||||
|  |  | ||||||
|  |   {# The list for small screens #} | ||||||
|  |   <ul class="list-group d-md-none"> | ||||||
|  |     {% for record in records %} | ||||||
|  |       <li class="list-group-item {% if not record.is_balanced or record.has_order_hole or not record.is_credit_card_paid %} list-group-item-danger {% endif %}{% if record.is_existing_equipment %} list-group-item-info {% endif %}"> | ||||||
|  |         {% if record.sn is not None %} | ||||||
|  |           <a class="list-group-item-action" href="{{ record.transaction.get_absolute_url }}"> | ||||||
|  |             <div class="date-subject-line"> | ||||||
|  |               {{ record.transaction.date|smart_date }} {{ record.subject.title|title }} | ||||||
|  |             </div> | ||||||
|  |             <div class="d-flex justify-content-between align-items-center"> | ||||||
|  |               <div> | ||||||
|  |                 {{ record.summary|default:"" }} | ||||||
|  |                 {% if not record.is_balanced %} | ||||||
|  |                   <span class="badge badge-danger badge-pill"> | ||||||
|  |                     {% trans "Unbalanced" context "Accounting|" as text %} | ||||||
|  |                     {{ text|force_escape }} | ||||||
|  |                   </span> | ||||||
|  |                 {% endif %} | ||||||
|  |                 {% if record.has_order_hole %} | ||||||
|  |                   <span class="badge badge-danger badge-pill"> | ||||||
|  |                     {% trans "Need Reorder" context "Accounting|" as text %} | ||||||
|  |                     {{ text|force_escape }} | ||||||
|  |                   </span> | ||||||
|  |                 {% endif %} | ||||||
|  |                 {% if not record.is_credit_card_paid %} | ||||||
|  |                   <span class="badge badge-danger badge-pill"> | ||||||
|  |                     {% trans "Unpaid" context "Accounting|" as text %} | ||||||
|  |                     {{ text|force_escape }} | ||||||
|  |                   </span> | ||||||
|  |                 {% endif %} | ||||||
|  |                 {% if record.is_existing_equipment %} | ||||||
|  |                   <span class="badge badge-info badge-pill"> | ||||||
|  |                     {% trans "Existing" context "Accounting|" as text %} | ||||||
|  |                     {{ text|force_escape }} | ||||||
|  |                   </span> | ||||||
|  |                 {% endif %} | ||||||
|  |               </div> | ||||||
|  |               <div> | ||||||
|  |                 {% if record.debit_amount is not None %} | ||||||
|  |                   <span class="badge badge-success badge-pill"> | ||||||
|  |                     {{ record.debit_amount|intcomma:False }} | ||||||
|  |                   </span> | ||||||
|  |                 {% endif %} | ||||||
|  |                 {% if record.credit_amount is not None %} | ||||||
|  |                   <span class="badge badge-warning badge-pill"> | ||||||
|  |                     {{ record.credit_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> | ||||||
|  |             </div> | ||||||
|  |           </a> | ||||||
|  |         {% else %} | ||||||
|  |           <div class="date-subject-line"> | ||||||
|  |             {{ record.transaction.date|smart_date }} {{ record.subject.title|title }} | ||||||
|  |           </div> | ||||||
|  |           <div class="d-flex justify-content-between align-items-center"> | ||||||
|  |             <div> | ||||||
|  |               {{ record.summary|default:"" }} | ||||||
|  |             </div> | ||||||
|  |             <div> | ||||||
|  |               {% if record.debit_amount is not None %} | ||||||
|  |                 <span class="badge badge-success badge-pill"> | ||||||
|  |                   {{ record.debit_amount|intcomma:False }} | ||||||
|  |                 </span> | ||||||
|  |               {% endif %} | ||||||
|  |               {% if record.credit_amount is not None %} | ||||||
|  |                 <span class="badge badge-warning badge-pill"> | ||||||
|  |                   {{ record.credit_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> | ||||||
|  |           </div> | ||||||
|  |         {% endif %} | ||||||
|  |       </li> | ||||||
|  |     {% endfor %} | ||||||
|  |   </ul> | ||||||
|  | {% else %} | ||||||
|  |   <p>{{ _("There is currently no data.")|force_escape }}</p> | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
|  | {% endblock %} | ||||||
| @@ -51,7 +51,7 @@ urlpatterns = [ | |||||||
|     path("ledger", |     path("ledger", | ||||||
|          mia_core_views.todo, name="ledger.home"), |          mia_core_views.todo, name="ledger.home"), | ||||||
|     path("ledger/<str:subject_code>/<str:period_spec>", |     path("ledger/<str:subject_code>/<str:period_spec>", | ||||||
|          mia_core_views.todo, name="ledger"), |          views.ledger, name="ledger"), | ||||||
|     path("ledger-summary", |     path("ledger-summary", | ||||||
|          mia_core_views.todo, name="ledger-summary.home"), |          mia_core_views.todo, name="ledger-summary.home"), | ||||||
|     path("ledger-summary/<str:subject_code>", |     path("ledger-summary/<str:subject_code>", | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ | |||||||
| """The view controllers of the accounting application. | """The view controllers of the accounting application. | ||||||
|  |  | ||||||
| """ | """ | ||||||
|  | import re | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
|  |  | ||||||
| from django.db import connection | from django.db import connection | ||||||
| @@ -357,3 +358,95 @@ ORDER BY month""", | |||||||
|         "all_subjects": [x for x in subjects if x.code not in settings.ACCOUNTING["CASH_SHORTCUT_SUBJECTS"]], |         "all_subjects": [x for x in subjects if x.code not in settings.ACCOUNTING["CASH_SHORTCUT_SUBJECTS"]], | ||||||
|     } |     } | ||||||
|     return render(request, "accounting/cash_summary.html", params) |     return render(request, "accounting/cash_summary.html", params) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _ledger_subjects(): | ||||||
|  |     """Returns the subjects for the ledger reports. | ||||||
|  |  | ||||||
|  |     Returns: | ||||||
|  |         list[Subject]: The subjects for the ledger reports. | ||||||
|  |     """ | ||||||
|  |     return list(Subject.objects.raw("""SELECT s.* | ||||||
|  |   FROM accounting_subjects AS s | ||||||
|  |   WHERE s.code IN (SELECT s.code | ||||||
|  |     FROM accounting_subjects AS s | ||||||
|  |       INNER JOIN (SELECT s.code | ||||||
|  |         FROM accounting_subjects AS s | ||||||
|  |          INNER JOIN accounting_records AS r ON r.subject_sn = s.sn | ||||||
|  |         GROUP BY s.code) AS u | ||||||
|  |       ON u.code LIKE s.code || '%' | ||||||
|  |     GROUP BY s.code) | ||||||
|  |   ORDER BY s.code""")) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @require_GET | ||||||
|  | @digest_login_required | ||||||
|  | def ledger(request, subject_code, period_spec): | ||||||
|  |     """The ledger 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() | ||||||
|  |     data_end = last_txn.date if last_txn is not None else None | ||||||
|  |     period = Period(period_spec, data_start, data_end) | ||||||
|  |     # The subject | ||||||
|  |     subjects = _ledger_subjects() | ||||||
|  |     current_subject = None | ||||||
|  |     for subject in subjects: | ||||||
|  |         if subject.code == subject_code: | ||||||
|  |             current_subject = subject | ||||||
|  |     if current_subject is None: | ||||||
|  |         raise Http404() | ||||||
|  |     # The SQL query | ||||||
|  |     select_records = """SELECT r.* | ||||||
|  |   FROM accounting_records AS r | ||||||
|  |     INNER JOIN accounting_transactions AS t ON r.transaction_sn = t.sn | ||||||
|  |     INNER JOIN accounting_subjects AS s ON r.subject_sn = s.sn | ||||||
|  |   WHERE t.date >= %s AND t.date <= %s AND s.code LIKE %s | ||||||
|  |   ORDER BY t.date, t.ord, | ||||||
|  |     CASE WHEN r.is_credit THEN 1 ELSE 2 END, r.ord""" | ||||||
|  |     records = list(Record.objects.raw( | ||||||
|  |         select_records, | ||||||
|  |         [period.start, period.end, current_subject.code + "%"])) | ||||||
|  |     if re.match("^[1-3]", current_subject.code) is not None: | ||||||
|  |         select_balance_before = """SELECT | ||||||
|  |     SUM(CASE WHEN is_credit THEN -1 ELSE 1 END * amount) | ||||||
|  |   FROM (%s)""" % select_records | ||||||
|  |         with connection.cursor() as cursor: | ||||||
|  |             cursor.execute( | ||||||
|  |                 select_balance_before, | ||||||
|  |                 [data_start, | ||||||
|  |                  period.start - timedelta(days=1), | ||||||
|  |                  current_subject.code + "%"]) | ||||||
|  |             row = cursor.fetchone() | ||||||
|  |         balance = row[0] | ||||||
|  |         record_brought_forward = Record( | ||||||
|  |             transaction=Transaction(date=records[-1].transaction.date), | ||||||
|  |             subject=current_subject, | ||||||
|  |             summary=pgettext("Accounting|", "Brought Forward"), | ||||||
|  |             is_credit=balance<0, | ||||||
|  |             amount=abs(balance), | ||||||
|  |             balance=balance, | ||||||
|  |         ) | ||||||
|  |     else: | ||||||
|  |         balance = 0 | ||||||
|  |         record_brought_forward = None | ||||||
|  |     for record in records: | ||||||
|  |         balance = balance + \ | ||||||
|  |                   (-1 if record.is_credit else 1) * record.amount | ||||||
|  |         record.balance = balance | ||||||
|  |     if record_brought_forward is not None: | ||||||
|  |         records.insert(0, record_brought_forward) | ||||||
|  |     pagination = Pagination(request, records, True) | ||||||
|  |     records = pagination.records | ||||||
|  |     _find_imbalanced(records) | ||||||
|  |     _find_order_holes(records) | ||||||
|  |     params = { | ||||||
|  |         "records": records, | ||||||
|  |         "pagination": pagination, | ||||||
|  |         "current_subject": current_subject, | ||||||
|  |         "period": period, | ||||||
|  |         "reports": ReportUrl(ledger=current_subject, period=period), | ||||||
|  |         "subjects": subjects, | ||||||
|  |     } | ||||||
|  |     return render(request, "accounting/ledger.html", params) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user