Added the ledger in the accounting application.
This commit is contained in:
parent
9c053148d4
commit
847f8dc402
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user