Added the ledger in the accounting application.

This commit is contained in:
依瑪貓 2020-07-16 22:12:59 +08:00
parent 9c053148d4
commit 847f8dc402
4 changed files with 350 additions and 1 deletions

View File

@ -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."""

View 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 %}

View File

@ -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>",

View File

@ -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)