diff --git a/accounting/templates/accounting/transactions/transfer/form.html b/accounting/templates/accounting/transactions/transfer/form.html
index 5c56408..7bf3599 100644
--- a/accounting/templates/accounting/transactions/transfer/form.html
+++ b/accounting/templates/accounting/transactions/transfer/form.html
@@ -44,12 +44,7 @@ First written: 2020/7/23
{# TODO: To be done #}
diff --git a/accounting/utils.py b/accounting/utils.py
index 9dde5e8..ecfd6f2 100644
--- a/accounting/utils.py
+++ b/accounting/utils.py
@@ -18,11 +18,14 @@
"""The utilities of the accounting application.
"""
+import json
import re
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
-from django.db.models import Q, Sum, Case, When, F, Count, Max, Min
+from django.db.models import Q, Sum, Case, When, F, Count, Max, Min, Value, \
+ CharField
+from django.db.models.functions import StrIndex, Left
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import pgettext
@@ -319,6 +322,52 @@ def find_order_holes(records):
record.has_order_hole = record.transaction.date in holes
+def get_summary_categories():
+ """Finds and returns the summary categories and their corresponding account
+ hints.
+
+ Returns:
+ dict[str,str]: The summary categories and their account hints, by
+ their record types and category types.
+ """
+ filters = {
+ "general": Q(summary__contains="—") & ~Q(summary__regex=".+—.+→.+"),
+ "travel": Q(summary__regex=".+—.+→.+")
+ & ~Q(summary__regex=".+—.+—.+→.+"),
+ "bus": Q(summary__regex=".+—.+—.+→.+"),
+ }
+ categories = {
+ "credit": {},
+ "debit": {},
+ }
+ for cat_type in filters:
+ rows = Record.objects\
+ .filter(
+ ~Q(account__code__startswith="114"),
+ ~Q(account__code__startswith="214"),
+ ~Q(account__code__startswith="128"),
+ ~Q(account__code__startswith="228"),
+ filters[cat_type])\
+ .annotate(category=Left("summary",
+ StrIndex("summary", Value("—")) - 1,
+ output_field=CharField()))\
+ .values("category", "account__code", "is_credit")\
+ .annotate(count=Count("category"))\
+ .order_by("category", "is_credit", "-count", "account__code")
+ for row in rows:
+ rec_type = "credit" if row["is_credit"] else "debit"
+ if cat_type not in categories[rec_type]:
+ categories[rec_type][cat_type] = {}
+ if row["category"] not in categories[rec_type][cat_type]:
+ categories[rec_type][cat_type][row["category"]]\
+ = row["account__code"]
+ return {F"{r}-{t}": json.dumps(
+ [{"category": c, "account": categories[r][t][c]}
+ for c in categories[r][t]])
+ for r in categories
+ for t in categories[r]}
+
+
def fill_txn_from_post(txn_type, txn, post):
"""Fills the transaction from the POSTed data. The POSTed data must be
validated and clean at this moment.
diff --git a/accounting/views.py b/accounting/views.py
index 02e709c..fb5ab9f 100644
--- a/accounting/views.py
+++ b/accounting/views.py
@@ -42,7 +42,8 @@ from .models import Record, Transaction, Account
from .utils import ReportUrl, get_cash_accounts, get_ledger_accounts, \
find_imbalanced, find_order_holes, fill_txn_from_post, \
sort_post_txn_records, make_txn_form_from_status, \
- make_txn_form_from_model, make_txn_form_from_post, MonthlySummary
+ make_txn_form_from_model, make_txn_form_from_post, MonthlySummary, \
+ get_summary_categories
@method_decorator(require_GET, name="dispatch")
@@ -836,6 +837,7 @@ def txn_edit(request, txn_type, txn=None):
form = make_txn_form_from_model(txn_type, txn)
return render(request, F"accounting/transactions/{txn_type}/form.html", {
"item": form,
+ "summary_categories": get_summary_categories,
})