Added type hints to the accounting application.

This commit is contained in:
2020-08-13 07:25:35 +08:00
parent 9cb6f25ee5
commit d4e7458117
8 changed files with 302 additions and 287 deletions

View File

@ -21,6 +21,7 @@
import datetime
import json
import re
from typing import Union, Tuple, List, Optional, Iterable, Mapping, Dict
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
@ -37,6 +38,9 @@ from mia_core.utils import new_pk
from .forms import TransactionForm, RecordForm
from .models import Account, Transaction, Record
AccountData = Tuple[Union[str, int], str, str, str]
RecordData = Tuple[Union[str, int], Optional[str], int]
DEFAULT_CASH_ACCOUNT = "1111"
CASH_SHORTCUT_ACCOUNTS = ["0", "1111"]
DEFAULT_LEDGER_ACCOUNT = "1111"
@ -48,12 +52,12 @@ class MonthlySummary:
"""A summary record.
Args:
month (datetime.date): The month.
label (str): The text label.
credit (int): The credit amount.
debit (int): The debit amount.
balance (int): The balance.
cumulative_balance (int): The cumulative balance.
month: The month.
label: The text label.
credit: The credit amount.
debit: The debit amount.
balance: The balance.
cumulative_balance: The cumulative balance.
Attributes:
month (datetime.date): The month.
@ -64,8 +68,9 @@ class MonthlySummary:
cumulative_balance (int): The cumulative balance.
"""
def __init__(self, month=None, label=None, credit=None, debit=None,
balance=None, cumulative_balance=None):
def __init__(self, month: datetime.date = None, label: str = None,
credit: int = None, debit: int = None, balance: int = None,
cumulative_balance: int = None):
self.month = month
self.label = label
self.credit = credit
@ -80,43 +85,42 @@ class ReportUrl:
"""The URL of the accounting reports.
Args:
cash (Account): The currently-specified account of the
cash: The currently-specified account of the
cash account or cash summary.
ledger (Account): The currently-specified account of the
ledger: The currently-specified account of the
ledger or leger summary.
period (Period): The currently-specified period.
period: The currently-specified period.
"""
def __init__(self, cash=None, ledger=None, period=None):
def __init__(self, cash: Account = None, ledger: Account = None,
period: Period = None):
self._period = Period() if period is None else period
self._cash = get_default_cash_account() if cash is None else cash
self._ledger = get_default_ledger_account()\
if ledger is None else ledger
def cash(self):
return reverse(
"accounting:cash", args=(self._cash, self._period))
def cash(self) -> str:
return reverse("accounting:cash", args=(self._cash, self._period))
def cash_summary(self):
def cash_summary(self) -> str:
return reverse("accounting:cash-summary", args=(self._cash,))
def ledger(self):
return reverse(
"accounting:ledger", args=(self._ledger, self._period))
def ledger(self) -> str:
return reverse("accounting:ledger", args=(self._ledger, self._period))
def ledger_summary(self):
def ledger_summary(self) -> str:
return reverse("accounting:ledger-summary", args=(self._ledger,))
def journal(self):
def journal(self) -> str:
return reverse("accounting:journal", args=(self._period,))
def trial_balance(self):
def trial_balance(self) -> str:
return reverse("accounting:trial-balance", args=(self._period,))
def income_statement(self):
def income_statement(self) -> str:
return reverse("accounting:income-statement", args=(self._period,))
def balance_sheet(self):
def balance_sheet(self) -> str:
return reverse("accounting:balance-sheet", args=(self._period,))
@ -124,7 +128,7 @@ class Populator:
"""The helper to populate the accounting data.
Args:
user (User): The user in action.
user: The user in action.
Attributes:
user (User): The user in action.
@ -133,7 +137,7 @@ class Populator:
def __init__(self, user):
self.user = user
def add_accounts(self, accounts):
def add_accounts(self, accounts: List[AccountData]) -> None:
"""Adds accounts.
Args:
@ -152,16 +156,16 @@ class Populator:
title_zh_hans=data[3],
created_by=self.user, updated_by=self.user).save()
def add_transfer_transaction(self, date, debit, credit):
def add_transfer_transaction(self, date: Union[datetime.date, int],
debit: List[RecordData],
credit: List[RecordData]) -> None:
"""Adds a transfer transaction.
Args:
date (datetime.date|int): The date, or the number of days from
date: The date, or the number of days from
today.
debit (tuple[tuple[any]]): Tuples of (account, summary, amount)
of the debit records.
credit (tuple[tuple[any]]): Tuples of (account, summary, amount)
of the credit records.
debit: Tuples of (account, summary, amount) of the debit records.
credit: Tuples of (account, summary, amount) of the credit records.
"""
if isinstance(date, int):
date = timezone.localdate() + timezone.timedelta(days=date)
@ -196,38 +200,36 @@ class Populator:
updated_by=self.user)
order = order + 1
def add_income_transaction(self, date, credit):
def add_income_transaction(self, date: Union[datetime.date, int],
credit: List[RecordData]) -> None:
"""Adds a cash income transaction.
Args:
date (datetime.date|int): The date, or the number of days from
today.
credit (tuple[tuple[any]]): Tuples of (account, summary, amount) of
the credit records.
date: The date, or the number of days from today.
credit: Tuples of (account, summary, amount) of the credit records.
"""
amount = sum([x[2] for x in credit])
self.add_transfer_transaction(
date, ((Account.CASH, None, amount),), credit)
date, [(Account.CASH, None, amount)], credit)
def add_expense_transaction(self, date, debit):
def add_expense_transaction(self, date: Union[datetime.date, int],
debit: List[RecordData]) -> None:
"""Adds a cash income transaction.
Args:
date (datetime.date|int): The date, or the number of days from
today.
debit (tuple[tuple[any]]): Tuples of (account, summary, amount) of
the debit records.
date: The date, or the number of days from today.
debit: Tuples of (account, summary, amount) of the debit records.
"""
amount = sum([x[2] for x in debit])
self.add_transfer_transaction(
date, debit, ((Account.CASH, None, amount),))
date, debit, [(Account.CASH, None, amount)])
def get_cash_accounts():
def get_cash_accounts() -> List[Account]:
"""Returns the cash accounts.
Returns:
list[Account]: The cash accounts.
The cash accounts.
"""
accounts = list(
Account.objects
@ -247,11 +249,11 @@ def get_cash_accounts():
return accounts
def get_default_cash_account():
def get_default_cash_account() -> Account:
"""Returns the default cash account.
Returns:
Account: The default cash account.
The default cash account.
"""
try:
code = settings.ACCOUNTING["DEFAULT_CASH_ACCOUNT"]
@ -274,11 +276,11 @@ def get_default_cash_account():
return Account(code="0", title=_("current assets and liabilities"))
def get_cash_shortcut_accounts():
def get_cash_shortcut_accounts() -> List[str]:
"""Returns the codes of the shortcut cash accounts.
Returns:
list[str]: The codes of the shortcut cash accounts.
The codes of the shortcut cash accounts.
"""
try:
accounts = settings.ACCOUNTING["CASH_SHORTCUT_ACCOUNTS"]
@ -293,11 +295,11 @@ def get_cash_shortcut_accounts():
return accounts
def get_ledger_accounts():
def get_ledger_accounts() -> List[Account]:
"""Returns the accounts for the ledger.
Returns:
list[Account]: The accounts for the ledger.
The accounts for the ledger.
"""
"""
For SQL one-liner:
@ -315,18 +317,19 @@ SELECT s.*
"""
codes = {}
for code in [x.code for x in Account.objects
.annotate(Count("record")).filter(record__count__gt=0)]:
.annotate(Count("record"))
.filter(record__count__gt=0)]:
while len(code) > 0:
codes[code] = True
code = code[:-1]
return Account.objects.filter(code__in=codes).order_by("code")
def get_default_ledger_account():
def get_default_ledger_account() -> Optional[Account]:
"""Returns the default ledger account.
Returns:
Account: The default ledger account.
The default ledger account.
"""
try:
code = settings.ACCOUNTING["DEFAULT_CASH_ACCOUNT"]
@ -347,12 +350,12 @@ def get_default_ledger_account():
return None
def find_imbalanced(records):
def find_imbalanced(records: Iterable[Record]) -> None:
""""Finds the records with imbalanced transactions, and sets their
is_balanced attribute.
Args:
records (list[Record]): The accounting records.
records: The accounting records.
"""
imbalanced = [x.pk for x in Transaction.objects
.annotate(
@ -364,13 +367,13 @@ def find_imbalanced(records):
record.is_balanced = record.transaction.pk not in imbalanced
def find_order_holes(records):
def find_order_holes(records: Iterable[Record]) -> None:
""""Finds whether the order of the transactions on this day is not
1, 2, 3, 4, 5..., and should be reordered, and sets their
has_order_holes attributes.
Args:
records (list[Record]): The accounting records.
records: The accounting records.
"""
holes = [x["date"] for x in Transaction.objects
.values("date")
@ -387,12 +390,12 @@ def find_order_holes(records):
and record.transaction.date in holes
def find_payable_records(account, records):
def find_payable_records(account: Account, records: Iterable[Record]) -> None:
"""Finds and sets the whether the payable record is paid.
Args:
account (Account): The current ledger account.
records (list[Record]): The accounting records.
account: The current ledger account.
records: The accounting records.
"""
try:
payable_accounts = settings.ACCOUNTING["PAYABLE_ACCOUNTS"]
@ -418,16 +421,17 @@ def find_payable_records(account, records):
keys = ["%s-%s" % (x["account__code"], x["summary"]) for x in rows]
for x in [x for x in records
if x.pk is not None
and F"{x.account.code}-{x.summary}" in keys]:
and F"{x.account.code}-{x.summary}" in keys]:
x.is_payable = True
def find_existing_equipments(account, records):
def find_existing_equipments(account: Account,
records: Iterable[Record]) -> None:
"""Finds and sets the equipments that still exist.
Args:
account (Account): The current ledger account.
records (list[Record]): The accounting records.
account: The current ledger account.
records: The accounting records.
"""
try:
equipment_accounts = settings.ACCOUNTING["EQUIPMENT_ACCOUNTS"]
@ -453,17 +457,17 @@ def find_existing_equipments(account, records):
keys = ["%s-%s" % (x["account__code"], x["summary"]) for x in rows]
for x in [x for x in records
if x.pk is not None
and F"{x.account.code}-{x.summary}" in keys]:
and F"{x.account.code}-{x.summary}" in keys]:
x.is_existing_equipment = True
def get_summary_categories():
def get_summary_categories() -> str:
"""Finds and returns the summary categories and their corresponding account
hints.
hints as JSON.
Returns:
dict[str,str]: The summary categories and their account hints, by
their record types and category types.
The summary categories and their account hints, by their record types
and category types.
"""
rows = Record.objects\
.filter(Q(summary__contains=""),
@ -507,14 +511,15 @@ def get_summary_categories():
return json.dumps(categories)
def fill_txn_from_post(txn_type, txn, post):
def fill_txn_from_post(txn_type: str, txn: Transaction,
post: Mapping[str, str]) -> None:
"""Fills the transaction from the POSTed data. The POSTed data must be
validated and clean at this moment.
Args:
txn_type (str): The transaction type.
txn (Transaction): The transaction.
post (dict): The POSTed data.
txn_type: The transaction type.
txn: The transaction.
post: The POSTed data.
"""
m = re.match("^([0-9]{4})-([0-9]{2})-([0-9]{2})$", post["date"])
txn.date = datetime.date(
@ -565,12 +570,12 @@ def fill_txn_from_post(txn_type, txn, post):
txn.records = records
def sort_post_txn_records(post):
def sort_post_txn_records(post: Dict[str, str]) -> None:
"""Sorts the records in the form by their specified order, so that the
form can be used to populate the data to return to the user.
Args:
post (dict): The POSTed form.
post: The POSTed form.
"""
# Collects the available record numbers
record_no = {
@ -618,15 +623,16 @@ def sort_post_txn_records(post):
post[key] = new_post[key]
def make_txn_form_from_model(txn_type, txn):
def make_txn_form_from_model(txn_type: str,
txn: Transaction) -> TransactionForm:
"""Converts a transaction data model to a transaction form.
Args:
txn_type (str): The transaction type.
txn (Transaction): The transaction data model.
txn_type: The transaction type.
txn: The transaction data model.
Returns:
TransactionForm: The transaction form.
The transaction form.
"""
form = TransactionForm(
{x: str(getattr(txn, x)) for x in ["date", "notes"]
@ -658,7 +664,8 @@ def make_txn_form_from_model(txn_type, txn):
return form
def _find_max_record_no(txn_type, post):
def _find_max_record_no(txn_type: str,
post: Mapping[str, str]) -> Dict[str, int]:
"""Finds the max debit and record numbers from the POSTed form.
Args: