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): | ||||
|         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): | ||||
|         """Returns the string representation of this accounting | ||||
|         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", | ||||
|          mia_core_views.todo, name="ledger.home"), | ||||
|     path("ledger/<str:subject_code>/<str:period_spec>", | ||||
|          mia_core_views.todo, name="ledger"), | ||||
|          views.ledger, name="ledger"), | ||||
|     path("ledger-summary", | ||||
|          mia_core_views.todo, name="ledger-summary.home"), | ||||
|     path("ledger-summary/<str:subject_code>", | ||||
|   | ||||
| @@ -18,6 +18,7 @@ | ||||
| """The view controllers of the accounting application. | ||||
|  | ||||
| """ | ||||
| import re | ||||
| from datetime import timedelta | ||||
|  | ||||
| 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"]], | ||||
|     } | ||||
|     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