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

@ -142,14 +142,17 @@ class CashAccountConverter:
"""The path converter for the cash account.""" """The path converter for the cash account."""
regex = "0|(11|12|21|22)[1-9]{1,3}" regex = "0|(11|12|21|22)[1-9]{1,3}"
def to_python(self, value): def to_python(self, value: str) -> Account:
"""Returns the cash account by the account code. """Returns the cash account by the account code.
Args: Args:
value (str): The account code. value: The account code.
Returns: Returns:
Account: The account. The account.
Raises:
ValueError: When the value is invalid
""" """
if value == "0": if value == "0":
return Account( return Account(
@ -164,14 +167,14 @@ class CashAccountConverter:
raise ValueError raise ValueError
return account return account
def to_url(self, value): def to_url(self, value: Account) -> str:
"""Returns the code of an account. """Returns the code of an account.
Args: Args:
value (Account): The account. value: The account.
Returns: Returns:
str: The account code. The account code.
""" """
return value.code return value.code
@ -180,14 +183,17 @@ class LedgerAccountConverter:
"""The path converter for the ledger account.""" """The path converter for the ledger account."""
regex = "[1-9]{1,5}" regex = "[1-9]{1,5}"
def to_python(self, value): def to_python(self, value: str) -> Account:
"""Returns the ledger account by the account code. """Returns the ledger account by the account code.
Args: Args:
value (str): The account code. value: The account code.
Returns: Returns:
Account: The account. The account.
Raises:
ValueError: When the value is invalid
""" """
try: try:
account = Account.objects.get(code=value) account = Account.objects.get(code=value)
@ -197,14 +203,14 @@ class LedgerAccountConverter:
raise ValueError raise ValueError
return account return account
def to_url(self, value): def to_url(self, value: Account) -> str:
"""Returns the code of an account. """Returns the code of an account.
Args: Args:
value (Account): The account. value: The account.
Returns: Returns:
str: The account code. The account code.
""" """
return value.code return value.code
@ -213,27 +219,30 @@ class TransactionConverter:
"""The path converter for the accounting transactions.""" """The path converter for the accounting transactions."""
regex = "[1-9][0-9]{8}" regex = "[1-9][0-9]{8}"
def to_python(self, value): def to_python(self, value: str) -> Transaction:
"""Returns the transaction by the transaction ID. """Returns the transaction by the transaction ID.
Args: Args:
value (str): The transaction ID. value: The transaction ID.
Returns: Returns:
Transaction: The account. The account.
Raises:
ValueError: When the value is invalid
""" """
try: try:
return Transaction.objects.get(pk=value) return Transaction.objects.get(pk=value)
except Transaction.DoesNotExist: except Transaction.DoesNotExist:
raise ValueError raise ValueError
def to_url(self, value): def to_url(self, value: Transaction) -> str:
"""Returns the ID of an account. """Returns the ID of an account.
Args: Args:
value (Transaction): The transaction. value: The transaction.
Returns: Returns:
str: The transaction ID. The transaction ID.
""" """
return value.pk return value.pk

View File

@ -19,6 +19,7 @@
""" """
import re import re
from typing import Optional
from django import forms from django import forms
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
@ -67,11 +68,11 @@ class RecordForm(forms.Form):
self.txn_form = None self.txn_form = None
self.is_credit = None self.is_credit = None
def account_title(self): def account_title(self) -> Optional[str]:
"""Returns the title of the specified account, if any. """Returns the title of the specified account, if any.
Returns: Returns:
str: The title of the specified account, or None if the specified The title of the specified account, or None if the specified
account is not available. account is not available.
""" """
try: try:
@ -101,7 +102,7 @@ class RecordForm(forms.Form):
if errors: if errors:
raise forms.ValidationError(errors) raise forms.ValidationError(errors)
def _validate_transaction(self): def _validate_transaction(self) -> None:
"""Validates whether the transaction matches the transaction form. """Validates whether the transaction matches the transaction form.
Raises: Raises:
@ -122,7 +123,7 @@ class RecordForm(forms.Form):
_("This record is not for this transaction."), _("This record is not for this transaction."),
code="not_belong") code="not_belong")
def _validate_account_type(self): def _validate_account_type(self) -> None:
"""Validates whether the account is a correct debit or credit account. """Validates whether the account is a correct debit or credit account.
Raises: Raises:
@ -145,7 +146,7 @@ class RecordForm(forms.Form):
self.add_error("account", error) self.add_error("account", error)
raise error raise error
def _validate_is_credit(self): def _validate_is_credit(self) -> None:
"""Validates whether debit and credit records are submitted correctly """Validates whether debit and credit records are submitted correctly
as corresponding debit and credit records. as corresponding debit and credit records.
@ -250,7 +251,7 @@ class TransactionForm(forms.Form):
if errors: if errors:
raise forms.ValidationError(errors) raise forms.ValidationError(errors)
def _validate_has_debit_records(self): def _validate_has_debit_records(self) -> None:
"""Validates whether there is any debit record. """Validates whether there is any debit record.
Raises: Raises:
@ -268,7 +269,7 @@ class TransactionForm(forms.Form):
_("Please fill in accounting records."), _("Please fill in accounting records."),
code="has_debit_records") code="has_debit_records")
def _validate_has_credit_records(self): def _validate_has_credit_records(self) -> None:
"""Validates whether there is any credit record. """Validates whether there is any credit record.
Raises: Raises:
@ -286,7 +287,7 @@ class TransactionForm(forms.Form):
_("Please fill in accounting records."), _("Please fill in accounting records."),
code="has_debit_records") code="has_debit_records")
def _validate_balance(self): def _validate_balance(self) -> None:
"""Validates whether the total amount of debit and credit records are """Validates whether the total amount of debit and credit records are
consistent. consistent.
@ -301,20 +302,20 @@ class TransactionForm(forms.Form):
_("The total of the debit and credit amounts are inconsistent."), _("The total of the debit and credit amounts are inconsistent."),
code="balance") code="balance")
def is_valid(self): def is_valid(self) -> bool:
if not super(TransactionForm, self).is_valid(): if not super().is_valid():
return False return False
for x in self.debit_records + self.credit_records: for x in self.debit_records + self.credit_records:
if not x.is_valid(): if not x.is_valid():
return False return False
return True return True
def balance_error(self): def balance_error(self) -> Optional[str]:
"""Returns the error message when the transaction is imbalanced. """Returns the error message when the transaction is imbalanced.
Returns: Returns:
str: The error message when the transaction is imbalanced, or The error message when the transaction is imbalanced, or None
None otherwise. otherwise.
""" """
errors = [x for x in self.non_field_errors().data errors = [x for x in self.non_field_errors().data
if x.code == "balance"] if x.code == "balance"]
@ -322,20 +323,20 @@ class TransactionForm(forms.Form):
return errors[0].message return errors[0].message
return None return None
def debit_total(self): def debit_total(self) -> int:
"""Returns the total amount of the debit records. """Returns the total amount of the debit records.
Returns: Returns:
int: The total amount of the credit records. The total amount of the credit records.
""" """
return sum([int(x.data["amount"]) for x in self.debit_records return sum([int(x.data["amount"]) for x in self.debit_records
if "amount" in x.data and "amount" not in x.errors]) if "amount" in x.data and "amount" not in x.errors])
def credit_total(self): def credit_total(self) -> int:
"""Returns the total amount of the credit records. """Returns the total amount of the credit records.
Returns: Returns:
int: The total amount of the credit records. The total amount of the credit records.
""" """
return sum([int(x.data["amount"]) for x in self.credit_records return sum([int(x.data["amount"]) for x in self.credit_records
if "amount" in x.data and "amount" not in x.errors]) if "amount" in x.data and "amount" not in x.errors])
@ -366,7 +367,8 @@ class AccountForm(forms.Form):
self.account = None self.account = None
@property @property
def parent(self): def parent(self) -> Optional[Account]:
"""The parent account, or None if this is the topmost account."""
code = self["code"].value() code = self["code"].value()
if code is None or len(code) < 2: if code is None or len(code) < 2:
return None return None
@ -391,7 +393,7 @@ class AccountForm(forms.Form):
if errors: if errors:
raise forms.ValidationError(errors) raise forms.ValidationError(errors)
def _validate_code_not_under_myself(self): def _validate_code_not_under_myself(self) -> None:
"""Validates whether the code is under itself. """Validates whether the code is under itself.
Raises: Raises:
@ -411,7 +413,7 @@ class AccountForm(forms.Form):
self.add_error("code", error) self.add_error("code", error)
raise error raise error
def _validate_code_unique(self): def _validate_code_unique(self) -> None:
"""Validates whether the code is unique. """Validates whether the code is unique.
Raises: Raises:
@ -432,7 +434,7 @@ class AccountForm(forms.Form):
self.add_error("code", error) self.add_error("code", error)
raise error raise error
def _validate_code_parent_exists(self): def _validate_code_parent_exists(self) -> None:
"""Validates whether the parent account exists. """Validates whether the parent account exists.
Raises: Raises:
@ -452,7 +454,7 @@ class AccountForm(forms.Form):
raise error raise error
return return
def _validate_code_descendant_code_size(self): def _validate_code_descendant_code_size(self) -> None:
"""Validates whether the codes of the descendants will be too long. """Validates whether the codes of the descendants will be too long.
Raises: Raises:
@ -467,7 +469,7 @@ class AccountForm(forms.Form):
~Q(pk=self.account.pk))\ ~Q(pk=self.account.pk))\
.aggregate(max_len=Max(Length("code")))["max_len"] .aggregate(max_len=Max(Length("code")))["max_len"]
if cur_max_len is None: if cur_max_len is None:
return True return
new_max_len = cur_max_len - len(self.account.code)\ new_max_len = cur_max_len - len(self.account.code)\
+ len(self.data["code"]) + len(self.data["code"])
if new_max_len <= 5: if new_max_len <= 5:

View File

@ -62,7 +62,7 @@ class Command(BaseCommand):
user.save() user.save()
p = Populator(user) p = Populator(user)
p.add_accounts(( p.add_accounts([
(1, "資產", "assets", "资产"), (1, "資產", "assets", "资产"),
(2, "負債", "liabilities", "负债"), (2, "負債", "liabilities", "负债"),
(3, "業主權益", "owners equity", "业主权益"), (3, "業主權益", "owners equity", "业主权益"),
@ -124,7 +124,7 @@ class Command(BaseCommand):
"管理及总务费用"), "管理及总务费用"),
(6272, "伙食費", "meal (expenses)", "伙食费"), (6272, "伙食費", "meal (expenses)", "伙食费"),
(6273, "職工福利", "employee benefits/welfare", "职工福利"), (6273, "職工福利", "employee benefits/welfare", "职工福利"),
)) ])
income = random.randint(40000, 50000) income = random.randint(40000, 50000)
pension = 882 if income <= 40100\ pension = 882 if income <= 40100\
@ -143,49 +143,49 @@ class Command(BaseCommand):
month = (date.replace(day=1) - timezone.timedelta(days=1)).month month = (date.replace(day=1) - timezone.timedelta(days=1)).month
p.add_transfer_transaction( p.add_transfer_transaction(
date, date,
((1113, "薪資轉帳", savings), [(1113, "薪資轉帳", savings),
(1314, F"勞保{month}", pension), (1314, F"勞保{month}", pension),
(6262, F"健保{month}", insurance), (6262, F"健保{month}", insurance),
(1255, "代扣所得稅", tax)), (1255, "代扣所得稅", tax)],
((4611, F"{month}月份薪水", income),)) [(4611, F"{month}月份薪水", income)])
p.add_income_transaction( p.add_income_transaction(
-15, -15,
((1113, "ATM提款", 2000),)) [(1113, "ATM提款", 2000)])
p.add_transfer_transaction( p.add_transfer_transaction(
-14, -14,
((6254, "高鐵—台北→左營", 1490),), [(6254, "高鐵—台北→左營", 1490)],
((2141, "高鐵—台北→左營", 1490),)) [(2141, "高鐵—台北→左營", 1490)])
p.add_transfer_transaction( p.add_transfer_transaction(
-14, -14,
((6273, "電影—復仇者聯盟", 80),), [(6273, "電影—復仇者聯盟", 80)],
((2141, "電影—復仇者聯盟", 80),)) [(2141, "電影—復仇者聯盟", 80)])
p.add_transfer_transaction( p.add_transfer_transaction(
-13, -13,
((6273, "電影—2001太空漫遊", 80),), [(6273, "電影—2001太空漫遊", 80)],
((2141, "電影—2001太空漫遊", 80),)) [(2141, "電影—2001太空漫遊", 80)])
p.add_transfer_transaction( p.add_transfer_transaction(
-11, -11,
((2141, "電影—復仇者聯盟", 80),), [(2141, "電影—復仇者聯盟", 80)],
((1113, "電影—復仇者聯盟", 80),)) [(1113, "電影—復仇者聯盟", 80)])
p.add_expense_transaction( p.add_expense_transaction(
-13, -13,
((6273, "公車—262—民生社區→頂溪捷運站", 30),)) [(6273, "公車—262—民生社區→頂溪捷運站", 30)])
p.add_expense_transaction( p.add_expense_transaction(
-2, -2,
((6272, "午餐—排骨飯", random.randint(40, 200)), [(6272, "午餐—排骨飯", random.randint(40, 200)),
(6272, "飲料—紅茶", random.randint(40, 200)))) (6272, "飲料—紅茶", random.randint(40, 200))])
p.add_expense_transaction( p.add_expense_transaction(
-1, -1,
((6272, "午餐—牛肉麵", random.randint(40, 200)), ([(6272, "午餐—牛肉麵", random.randint(40, 200)),
(6272, "飲料—紅茶", random.randint(40, 200)))) (6272, "飲料—紅茶", random.randint(40, 200))]))
p.add_expense_transaction( p.add_expense_transaction(
-1, -1,
((6272, "午餐—排骨飯", random.randint(40, 200)), [(6272, "午餐—排骨飯", random.randint(40, 200)),
(6272, "飲料—冬瓜茶", random.randint(40, 200)))) (6272, "飲料—冬瓜茶", random.randint(40, 200))])
p.add_expense_transaction( p.add_expense_transaction(
0, 0,
((6272, "午餐—雞腿飯", random.randint(40, 200)), [(6272, "午餐—雞腿飯", random.randint(40, 200)),
(6272, "飲料—咖啡", random.randint(40, 200)))) (6272, "飲料—咖啡", random.randint(40, 200))])

View File

@ -18,6 +18,9 @@
"""The data models of the accounting application. """The data models of the accounting application.
""" """
import datetime
from typing import Dict, List, Optional
from dirtyfields import DirtyFieldsMixin from dirtyfields import DirtyFieldsMixin
from django.conf import settings from django.conf import settings
from django.db import models, transaction from django.db import models, transaction
@ -83,51 +86,44 @@ class Account(DirtyFieldsMixin, models.Model):
db_table = "accounting_accounts" db_table = "accounting_accounts"
@property @property
def title(self): def title(self) -> str:
"""The title in the current language."""
return get_multi_lingual_attr(self, "title") return get_multi_lingual_attr(self, "title")
@title.setter @title.setter
def title(self, value): def title(self, value: str) -> None:
set_multi_lingual_attr(self, "title", value) set_multi_lingual_attr(self, "title", value)
@property @property
def option_data(self): def option_data(self) -> Dict[str, str]:
"""The data as an option."""
return { return {
"code": self.code, "code": self.code,
"title": self.title, "title": self.title,
} }
@property @property
def is_parent_and_in_use(self): def is_parent_and_in_use(self) -> bool:
"""Whether this is a parent account and is in use. """Whether this is a parent account and is in use."""
Returns:
bool: True if this is a parent account and is in use, or false
otherwise
"""
if self._is_parent_and_in_use is None: if self._is_parent_and_in_use is None:
self._is_parent_and_in_use = self.child_set.count() > 0\ self._is_parent_and_in_use = self.child_set.count() > 0\
and self.record_set.count() > 0 and self.record_set.count() > 0
return self._is_parent_and_in_use return self._is_parent_and_in_use
@is_parent_and_in_use.setter @is_parent_and_in_use.setter
def is_parent_and_in_use(self, value): def is_parent_and_in_use(self, value: bool) -> None:
self._is_parent_and_in_use = value self._is_parent_and_in_use = value
@property @property
def is_in_use(self): def is_in_use(self) -> bool:
"""Whether this account is in use. """Whether this account is in use."""
Returns:
bool: True if this account is in use, or false otherwise.
"""
if self._is_in_use is None: if self._is_in_use is None:
self._is_in_use = self.child_set.count() > 0\ self._is_in_use = self.child_set.count() > 0\
or self.record_set.count() > 0 or self.record_set.count() > 0
return self._is_in_use return self._is_in_use
@is_in_use.setter @is_in_use.setter
def is_in_use(self, value): def is_in_use(self, value: bool) -> None:
self._is_in_use = value self._is_in_use = value
@ -157,7 +153,7 @@ class Transaction(DirtyFieldsMixin, models.Model):
transaction.""" transaction."""
return self.date.__str__() + " #" + self.ord.__str__() return self.date.__str__() + " #" + self.ord.__str__()
def get_absolute_url(self): def get_absolute_url(self) -> str:
"""Returns the URL to view this transaction.""" """Returns the URL to view this transaction."""
if self.is_cash_expense: if self.is_cash_expense:
return reverse( return reverse(
@ -169,13 +165,13 @@ class Transaction(DirtyFieldsMixin, models.Model):
return reverse( return reverse(
"accounting:transactions.detail", args=("transfer", self)) "accounting:transactions.detail", args=("transfer", self))
def is_dirty(self, check_relationship=False, check_m2m=None): def is_dirty(self, check_relationship=False, check_m2m=None) -> bool:
"""Returns whether the data of this transaction is changed and need """Returns whether the data of this transaction is changed and need
to be saved into the database. to be saved into the database.
Returns: Returns:
bool: True if the data of this transaction is changed and need True if the data of this transaction is changed and need to be
to be saved into the database, or False otherwise. saved into the database, or False otherwise.
""" """
if super().is_dirty(check_relationship=check_relationship, if super().is_dirty(check_relationship=check_relationship,
check_m2m=check_m2m): check_m2m=check_m2m):
@ -189,8 +185,9 @@ class Transaction(DirtyFieldsMixin, models.Model):
return True return True
return False return False
def save(self, current_user=None, old_date=None, force_insert=False, def save(self, current_user=None, old_date: datetime.date = None,
force_update=False, using=None, update_fields=None): force_insert=False, force_update=False, using=None,
update_fields=None):
# When the date is changed, the orders of the transactions in the same # When the date is changed, the orders of the transactions in the same
# day need to be reordered # day need to be reordered
txn_to_sort = [] txn_to_sort = []
@ -262,7 +259,7 @@ class Transaction(DirtyFieldsMixin, models.Model):
"""The records of the transaction. """The records of the transaction.
Returns: Returns:
list[Record]: The records. List[Record]: The records.
""" """
if self._records is None: if self._records is None:
self._records = list(self.record_set.all()) self._records = list(self.record_set.all())
@ -278,22 +275,18 @@ class Transaction(DirtyFieldsMixin, models.Model):
"""The debit records of this transaction. """The debit records of this transaction.
Returns: Returns:
list[Record]: The records. List[Record]: The records.
""" """
return [x for x in self.records if not x.is_credit] return [x for x in self.records if not x.is_credit]
def debit_total(self): def debit_total(self) -> int:
"""The total amount of the debit records.""" """The total amount of the debit records."""
return sum([x.amount for x in self.debit_records return sum([x.amount for x in self.debit_records
if isinstance(x.amount, int)]) if isinstance(x.amount, int)])
@property @property
def debit_summaries(self): def debit_summaries(self) -> List[str]:
"""The summaries of the debit records. """The summaries of the debit records."""
Returns:
list[str]: The summaries of the debit records.
"""
return [x.account.title if x.summary is None else x.summary return [x.account.title if x.summary is None else x.summary
for x in self.debit_records] for x in self.debit_records]
@ -302,36 +295,28 @@ class Transaction(DirtyFieldsMixin, models.Model):
"""The credit records of this transaction. """The credit records of this transaction.
Returns: Returns:
list[Record]: The records. List[Record]: The records.
""" """
return [x for x in self.records if x.is_credit] return [x for x in self.records if x.is_credit]
def credit_total(self): def credit_total(self) -> int:
"""The total amount of the credit records.""" """The total amount of the credit records."""
return sum([x.amount for x in self.credit_records return sum([x.amount for x in self.credit_records
if isinstance(x.amount, int)]) if isinstance(x.amount, int)])
@property @property
def credit_summaries(self): def credit_summaries(self) -> List[str]:
"""The summaries of the credit records. """The summaries of the credit records."""
Returns:
list[str]: The summaries of the credit records.
"""
return [x.account.title if x.summary is None else x.summary return [x.account.title if x.summary is None else x.summary
for x in self.credit_records] for x in self.credit_records]
@property @property
def amount(self): def amount(self) -> int:
"""The amount of this transaction. """The amount of this transaction."""
Returns:
int: The amount of this transaction.
"""
return self.debit_total() return self.debit_total()
@property @property
def is_balanced(self): def is_balanced(self) -> bool:
"""Whether the sum of the amounts of the debit records is the """Whether the sum of the amounts of the debit records is the
same as the sum of the amounts of the credit records. """ same as the sum of the amounts of the credit records. """
if self._is_balanced is None: if self._is_balanced is None:
@ -341,16 +326,16 @@ class Transaction(DirtyFieldsMixin, models.Model):
return self._is_balanced return self._is_balanced
@is_balanced.setter @is_balanced.setter
def is_balanced(self, value): def is_balanced(self, value: bool) -> None:
self._is_balanced = value self._is_balanced = value
def has_many_same_day(self): def has_many_same_day(self) -> bool:
"""whether there are more than one transactions at this day, """whether there are more than one transactions at this day,
so that the user can sort their orders. """ so that the user can sort their orders. """
return Transaction.objects.filter(date=self.date).count() > 1 return Transaction.objects.filter(date=self.date).count() > 1
@property @property
def has_order_hole(self): def has_order_hole(self) -> bool:
"""Whether the order of the transactions on this day is not """Whether the order of the transactions on this day is not
1, 2, 3, 4, 5..., and should be reordered. """ 1, 2, 3, 4, 5..., and should be reordered. """
if self._has_order_hole is None: if self._has_order_hole is None:
@ -369,11 +354,11 @@ class Transaction(DirtyFieldsMixin, models.Model):
return self._has_order_hole return self._has_order_hole
@has_order_hole.setter @has_order_hole.setter
def has_order_hole(self, value): def has_order_hole(self, value: bool) -> None:
self._has_order_hole = value self._has_order_hole = value
@property @property
def is_cash_income(self): def is_cash_income(self) -> bool:
"""Whether this transaction is a cash income transaction.""" """Whether this transaction is a cash income transaction."""
debit_records = self.debit_records debit_records = self.debit_records
return (len(debit_records) == 1 return (len(debit_records) == 1
@ -381,7 +366,7 @@ class Transaction(DirtyFieldsMixin, models.Model):
and debit_records[0].summary is None) and debit_records[0].summary is None)
@property @property
def is_cash_expense(self): def is_cash_expense(self) -> bool:
"""Whether this transaction is a cash expense transaction.""" """Whether this transaction is a cash expense transaction."""
credit_records = self.credit_records credit_records = self.credit_records
return (len(credit_records) == 1 return (len(credit_records) == 1
@ -389,7 +374,7 @@ class Transaction(DirtyFieldsMixin, models.Model):
and credit_records[0].summary is None) and credit_records[0].summary is None)
@property @property
def type(self): def type(self) -> str:
"""The transaction type.""" """The transaction type."""
if self.is_cash_expense: if self.is_cash_expense:
return "expense" return "expense"
@ -444,40 +429,40 @@ class Record(DirtyFieldsMixin, models.Model):
db_table = "accounting_records" db_table = "accounting_records"
@property @property
def debit_amount(self): def debit_amount(self) -> Optional[int]:
"""The debit amount of this accounting record.""" """The debit amount of this accounting record."""
if self._debit_amount is None: if self._debit_amount is None:
self._debit_amount = self.amount if not self.is_credit else None self._debit_amount = self.amount if not self.is_credit else None
return self._debit_amount return self._debit_amount
@debit_amount.setter @debit_amount.setter
def debit_amount(self, value): def debit_amount(self, value: Optional[int]) -> None:
self._debit_amount = value self._debit_amount = value
@property @property
def credit_amount(self): def credit_amount(self) -> Optional[int]:
"""The credit amount of this accounting record.""" """The credit amount of this accounting record."""
if self._credit_amount is None: if self._credit_amount is None:
self._credit_amount = self.amount if self.is_credit else None self._credit_amount = self.amount if self.is_credit else None
return self._credit_amount return self._credit_amount
@credit_amount.setter @credit_amount.setter
def credit_amount(self, value): def credit_amount(self, value: Optional[int]):
self._credit_amount = value self._credit_amount = value
@property @property
def is_balanced(self): def is_balanced(self) -> bool:
"""Whether the transaction of this record is balanced. """ """Whether the transaction of this record is balanced. """
if self._is_balanced is None: if self._is_balanced is None:
self._is_balanced = self.transaction.is_balanced self._is_balanced = self.transaction.is_balanced
return self._is_balanced return self._is_balanced
@is_balanced.setter @is_balanced.setter
def is_balanced(self, value): def is_balanced(self, value: bool) -> None:
self._is_balanced = value self._is_balanced = value
@property @property
def has_order_hole(self): def has_order_hole(self) -> bool:
"""Whether the order of the transactions on this day is not """Whether the order of the transactions on this day is not
1, 2, 3, 4, 5..., and should be reordered. """ 1, 2, 3, 4, 5..., and should be reordered. """
if self._has_order_hole is None: if self._has_order_hole is None:
@ -485,5 +470,5 @@ class Record(DirtyFieldsMixin, models.Model):
return self._has_order_hole return self._has_order_hole
@has_order_hole.setter @has_order_hole.setter
def has_order_hole(self, value): def has_order_hole(self, value: bool) -> None:
self._has_order_hole = value self._has_order_hole = value

View File

@ -19,6 +19,7 @@
""" """
import re import re
from typing import Union, Optional
from django import template from django import template
@ -30,7 +31,7 @@ register = template.Library()
@register.filter @register.filter
def accounting_amount(value): def accounting_amount(value: Union[str, int]) -> str:
if value is None: if value is None:
return "" return ""
if value == 0: if value == 0:
@ -47,13 +48,15 @@ def accounting_amount(value):
@register.simple_tag @register.simple_tag
def report_url(cash_account, ledger_account, period): def report_url(cash_account: Optional[Account],
ledger_account: Optional[Account],
period: Optional[Period]) -> ReportUrl:
"""Returns accounting report URL helper. """Returns accounting report URL helper.
Args: Args:
cash_account (Account): The current cash account. cash_account: The current cash account.
ledger_account (Account): The current ledger account. ledger_account: The current ledger account.
period (Period): The period. period: The period.
Returns: Returns:
ReportUrl: The accounting report URL helper. ReportUrl: The accounting report URL helper.

View File

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

View File

@ -25,11 +25,11 @@ from django.utils.translation import gettext as _
from .models import Account, Record from .models import Account, Record
def validate_record_id(value): def validate_record_id(value: str) -> None:
"""Validates the record ID. """Validates the record ID.
Args: Args:
value (str): The record ID. value: The record ID.
Raises: Raises:
ValidationError: When the validation fails. ValidationError: When the validation fails.
@ -41,11 +41,11 @@ def validate_record_id(value):
code="not_exist") code="not_exist")
def validate_record_account_code(value): def validate_record_account_code(value: str) -> None:
"""Validates an account code. """Validates an account code.
Args: Args:
value (str): The account code. value: The account code.
Raises: Raises:
ValidationError: When the validation fails. ValidationError: When the validation fails.

View File

@ -18,6 +18,7 @@
"""The view controllers of the accounting application. """The view controllers of the accounting application.
""" """
import datetime
import json import json
import re import re
@ -26,7 +27,8 @@ from django.db import transaction
from django.db.models import Sum, Case, When, F, Q, Count, BooleanField, \ from django.db.models import Sum, Case, When, F, Q, Count, BooleanField, \
ExpressionWrapper ExpressionWrapper
from django.db.models.functions import TruncMonth, Coalesce from django.db.models.functions import TruncMonth, Coalesce
from django.http import JsonResponse, HttpResponseRedirect, Http404 from django.http import JsonResponse, HttpResponseRedirect, Http404, \
HttpRequest, HttpResponse
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.urls import reverse from django.urls import reverse
@ -62,16 +64,17 @@ class CashDefaultView(RedirectView):
@require_GET @require_GET
@login_required @login_required
def cash(request, account, period): def cash(request: HttpRequest, account: Account,
period: Period) -> HttpResponse:
"""The cash account. """The cash account.
Args: Args:
request (HttpRequest) The request. request: The request.
account (Account): The account. account: The account.
period (Period): The period. period: The period.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The accounting records # The accounting records
if account.code == "0": if account.code == "0":
@ -180,15 +183,15 @@ class CashSummaryDefaultView(RedirectView):
@require_GET @require_GET
@login_required @login_required
def cash_summary(request, account): def cash_summary(request: HttpRequest, account: Account) -> HttpResponse:
"""The cash account summary. """The cash account summary.
Args: Args:
request (HttpRequest) The request. request: The request.
account (Account): The account. account: The account.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The account # The account
accounts = utils.get_cash_accounts() accounts = utils.get_cash_accounts()
@ -278,16 +281,17 @@ class LedgerDefaultView(RedirectView):
@require_GET @require_GET
@login_required @login_required
def ledger(request, account, period): def ledger(request: HttpRequest, account: Account,
period: Period) -> HttpResponse:
"""The ledger. """The ledger.
Args: Args:
request (HttpRequest) The request. request: The request.
account (Account): The account. account: The account.
period (Period): The period. period: The period.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The accounting records # The accounting records
records = list( records = list(
@ -354,15 +358,15 @@ class LedgerSummaryDefaultView(RedirectView):
@require_GET @require_GET
@login_required @login_required
def ledger_summary(request, account): def ledger_summary(request: HttpRequest, account: Account) -> HttpResponse:
"""The ledger summary report. """The ledger summary report.
Args: Args:
request (HttpRequest) The request. request: The request.
account (Account): The account. account: The account.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The month summaries # The month summaries
months = [utils.MonthlySummary(**x) for x in Record.objects months = [utils.MonthlySummary(**x) for x in Record.objects
@ -416,15 +420,15 @@ class JournalDefaultView(RedirectView):
@require_GET @require_GET
@login_required @login_required
def journal(request, period): def journal(request: HttpRequest, period: Period) -> HttpResponse:
"""The journal. """The journal.
Args: Args:
request (HttpRequest) The request. request: The request.
period (Period): The period. period: The period.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The accounting records # The accounting records
records = Record.objects \ records = Record.objects \
@ -498,15 +502,15 @@ class TrialBalanceDefaultView(RedirectView):
@require_GET @require_GET
@login_required @login_required
def trial_balance(request, period): def trial_balance(request: HttpRequest, period: Period) -> HttpResponse:
"""The trial balance. """The trial balance.
Args: Args:
request (HttpRequest) The request. request: The request.
period (Period): The period. period: The period.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The accounts # The accounts
nominal = list( nominal = list(
@ -601,15 +605,15 @@ class IncomeStatementDefaultView(RedirectView):
@require_GET @require_GET
@login_required @login_required
def income_statement(request, period): def income_statement(request: HttpRequest, period: Period) -> HttpResponse:
"""The income statement. """The income statement.
Args: Args:
request (HttpRequest) The request. request: The request.
period (Period): The period. period: The period.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The accounts # The accounts
accounts = list( accounts = list(
@ -676,15 +680,15 @@ class BalanceSheetDefaultView(RedirectView):
@require_GET @require_GET
@login_required @login_required
def balance_sheet(request, period): def balance_sheet(request: HttpRequest, period: Period) -> HttpResponse:
"""The balance sheet. """The balance sheet.
Args: Args:
request (HttpRequest) The request. request: The request.
period (Period): The period. period: The period.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The accounts # The accounts
accounts = list( accounts = list(
@ -764,14 +768,14 @@ def balance_sheet(request, period):
@require_GET @require_GET
@login_required @login_required
def search(request): def search(request: HttpRequest) -> HttpResponse:
"""The search. """The search.
Args: Args:
request (HttpRequest) The request. request: The request.
Returns: Returns:
HttpResponse: The response. The response.
""" """
# The accounting records # The accounting records
query = request.GET.get("q") query = request.GET.get("q")
@ -809,16 +813,17 @@ class TransactionView(DetailView):
@require_GET @require_GET
@login_required @login_required
def txn_form(request, txn_type, txn=None): def txn_form(request: HttpRequest, txn_type: str,
txn: Transaction = None) -> HttpResponse:
"""The view to edit an accounting transaction. """The view to edit an accounting transaction.
Args: Args:
request (HttpRequest): The request. request: The request.
txn_type (str): The transaction type. txn_type: The transaction type.
txn (Transaction): The transaction. txn: The transaction.
Returns: Returns:
HttpResponse: The response. The response.
""" """
previous_post = stored_post.get_previous_post(request) previous_post = stored_post.get_previous_post(request)
if previous_post is not None: if previous_post is not None:
@ -847,16 +852,17 @@ def txn_form(request, txn_type, txn=None):
@require_POST @require_POST
@login_required @login_required
def txn_store(request, txn_type, txn=None): def txn_store(request: HttpRequest, txn_type: str,
txn: Transaction = None) -> HttpResponseRedirect:
"""The view to store an accounting transaction. """The view to store an accounting transaction.
Args: Args:
request (HttpRequest): The request. request: The request.
txn_type (str): The transaction type. txn_type: The transaction type.
txn (Transaction): The transaction. txn: The transaction.
Returns: Returns:
HttpResponse: The response. The response.
""" """
post = request.POST.dict() post = request.POST.dict()
strip_post(post) strip_post(post)
@ -900,15 +906,15 @@ class TransactionDeleteView(DeleteView):
@login_required @login_required
def txn_sort(request, date): def txn_sort(request: HttpRequest, date: datetime.date) -> HttpResponse:
"""The view for the form to sort the transactions in a same day. """The view for the form to sort the transactions in a same day.
Args: Args:
request (HttpRequest): The request. request: The request.
date (datetime.date): The day. date: The day.
Returns: Returns:
HttpResponse: The response. The response.
Raises: Raises:
Http404: When there are less than two transactions in this day. Http404: When there are less than two transactions in this day.
@ -977,15 +983,16 @@ class AccountView(DetailView):
@require_GET @require_GET
@login_required @login_required
def account_form(request, account=None): def account_form(request: HttpRequest,
account: Account = None) -> HttpResponse:
"""The view to edit an accounting transaction. """The view to edit an accounting transaction.
Args: Args:
request (HttpRequest): The request. request: The request.
account (Account): The account. account: The account.
Returns: Returns:
HttpResponse: The response. The response.
""" """
previous_post = stored_post.get_previous_post(request) previous_post = stored_post.get_previous_post(request)
if previous_post is not None: if previous_post is not None:
@ -1005,15 +1012,16 @@ def account_form(request, account=None):
@require_POST @require_POST
@login_required @login_required
def account_store(request, account=None): def account_store(request: HttpRequest,
account: Account = None) -> HttpResponseRedirect:
"""The view to store an account. """The view to store an account.
Args: Args:
request (HttpRequest): The request. request: The request.
account (Account): The account. account: The account.
Returns: Returns:
HttpResponseRedirect: The response. The response.
""" """
post = request.POST.dict() post = request.POST.dict()
strip_post(post) strip_post(post)
@ -1040,7 +1048,8 @@ def account_store(request, account=None):
@require_POST @require_POST
@login_required @login_required
def account_delete(request, account): def account_delete(request: HttpRequest,
account: Account) -> HttpResponseRedirect:
"""The view to delete an account. """The view to delete an account.
Args: Args:
@ -1062,28 +1071,28 @@ def account_delete(request, account):
@require_GET @require_GET
@login_required @login_required
def api_account_list(request): def api_account_list(request: HttpRequest) -> JsonResponse:
"""The API view to return all the accounts. """The API view to return all the accounts.
Args: Args:
request (HttpRequest): The request. request: The request.
Returns: Returns:
JsonResponse: The response. The response.
""" """
return JsonResponse({x.code: x.title for x in Account.objects.all()}) return JsonResponse({x.code: x.title for x in Account.objects.all()})
@require_GET @require_GET
@login_required @login_required
def api_account_options(request): def api_account_options(request: HttpRequest) -> JsonResponse:
"""The API view to return the account options. """The API view to return the account options.
Args: Args:
request (HttpRequest): The request. request: The request.
Returns: Returns:
JsonResponse: The response. The response.
""" """
accounts = Account.objects\ accounts = Account.objects\
.annotate(children_count=Count("child_set"))\ .annotate(children_count=Count("child_set"))\