Added type hints to the accounting application.
This commit is contained in:
parent
9cb6f25ee5
commit
d4e7458117
@ -142,14 +142,17 @@ class CashAccountConverter:
|
||||
"""The path converter for the cash account."""
|
||||
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.
|
||||
|
||||
Args:
|
||||
value (str): The account code.
|
||||
value: The account code.
|
||||
|
||||
Returns:
|
||||
Account: The account.
|
||||
The account.
|
||||
|
||||
Raises:
|
||||
ValueError: When the value is invalid
|
||||
"""
|
||||
if value == "0":
|
||||
return Account(
|
||||
@ -164,14 +167,14 @@ class CashAccountConverter:
|
||||
raise ValueError
|
||||
return account
|
||||
|
||||
def to_url(self, value):
|
||||
def to_url(self, value: Account) -> str:
|
||||
"""Returns the code of an account.
|
||||
|
||||
Args:
|
||||
value (Account): The account.
|
||||
value: The account.
|
||||
|
||||
Returns:
|
||||
str: The account code.
|
||||
The account code.
|
||||
"""
|
||||
return value.code
|
||||
|
||||
@ -180,14 +183,17 @@ class LedgerAccountConverter:
|
||||
"""The path converter for the ledger account."""
|
||||
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.
|
||||
|
||||
Args:
|
||||
value (str): The account code.
|
||||
value: The account code.
|
||||
|
||||
Returns:
|
||||
Account: The account.
|
||||
The account.
|
||||
|
||||
Raises:
|
||||
ValueError: When the value is invalid
|
||||
"""
|
||||
try:
|
||||
account = Account.objects.get(code=value)
|
||||
@ -197,14 +203,14 @@ class LedgerAccountConverter:
|
||||
raise ValueError
|
||||
return account
|
||||
|
||||
def to_url(self, value):
|
||||
def to_url(self, value: Account) -> str:
|
||||
"""Returns the code of an account.
|
||||
|
||||
Args:
|
||||
value (Account): The account.
|
||||
value: The account.
|
||||
|
||||
Returns:
|
||||
str: The account code.
|
||||
The account code.
|
||||
"""
|
||||
return value.code
|
||||
|
||||
@ -213,27 +219,30 @@ class TransactionConverter:
|
||||
"""The path converter for the accounting transactions."""
|
||||
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.
|
||||
|
||||
Args:
|
||||
value (str): The transaction ID.
|
||||
value: The transaction ID.
|
||||
|
||||
Returns:
|
||||
Transaction: The account.
|
||||
The account.
|
||||
|
||||
Raises:
|
||||
ValueError: When the value is invalid
|
||||
"""
|
||||
try:
|
||||
return Transaction.objects.get(pk=value)
|
||||
except Transaction.DoesNotExist:
|
||||
raise ValueError
|
||||
|
||||
def to_url(self, value):
|
||||
def to_url(self, value: Transaction) -> str:
|
||||
"""Returns the ID of an account.
|
||||
|
||||
Args:
|
||||
value (Transaction): The transaction.
|
||||
value: The transaction.
|
||||
|
||||
Returns:
|
||||
str: The transaction ID.
|
||||
The transaction ID.
|
||||
"""
|
||||
return value.pk
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
"""
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from django import forms
|
||||
from django.core.validators import RegexValidator
|
||||
@ -67,11 +68,11 @@ class RecordForm(forms.Form):
|
||||
self.txn_form = 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:
|
||||
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.
|
||||
"""
|
||||
try:
|
||||
@ -101,7 +102,7 @@ class RecordForm(forms.Form):
|
||||
if errors:
|
||||
raise forms.ValidationError(errors)
|
||||
|
||||
def _validate_transaction(self):
|
||||
def _validate_transaction(self) -> None:
|
||||
"""Validates whether the transaction matches the transaction form.
|
||||
|
||||
Raises:
|
||||
@ -122,7 +123,7 @@ class RecordForm(forms.Form):
|
||||
_("This record is not for this transaction."),
|
||||
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.
|
||||
|
||||
Raises:
|
||||
@ -145,7 +146,7 @@ class RecordForm(forms.Form):
|
||||
self.add_error("account", error)
|
||||
raise error
|
||||
|
||||
def _validate_is_credit(self):
|
||||
def _validate_is_credit(self) -> None:
|
||||
"""Validates whether debit and credit records are submitted correctly
|
||||
as corresponding debit and credit records.
|
||||
|
||||
@ -250,7 +251,7 @@ class TransactionForm(forms.Form):
|
||||
if 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.
|
||||
|
||||
Raises:
|
||||
@ -268,7 +269,7 @@ class TransactionForm(forms.Form):
|
||||
_("Please fill in accounting 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.
|
||||
|
||||
Raises:
|
||||
@ -286,7 +287,7 @@ class TransactionForm(forms.Form):
|
||||
_("Please fill in accounting 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
|
||||
consistent.
|
||||
|
||||
@ -301,20 +302,20 @@ class TransactionForm(forms.Form):
|
||||
_("The total of the debit and credit amounts are inconsistent."),
|
||||
code="balance")
|
||||
|
||||
def is_valid(self):
|
||||
if not super(TransactionForm, self).is_valid():
|
||||
def is_valid(self) -> bool:
|
||||
if not super().is_valid():
|
||||
return False
|
||||
for x in self.debit_records + self.credit_records:
|
||||
if not x.is_valid():
|
||||
return False
|
||||
return True
|
||||
|
||||
def balance_error(self):
|
||||
def balance_error(self) -> Optional[str]:
|
||||
"""Returns the error message when the transaction is imbalanced.
|
||||
|
||||
Returns:
|
||||
str: The error message when the transaction is imbalanced, or
|
||||
None otherwise.
|
||||
The error message when the transaction is imbalanced, or None
|
||||
otherwise.
|
||||
"""
|
||||
errors = [x for x in self.non_field_errors().data
|
||||
if x.code == "balance"]
|
||||
@ -322,20 +323,20 @@ class TransactionForm(forms.Form):
|
||||
return errors[0].message
|
||||
return None
|
||||
|
||||
def debit_total(self):
|
||||
def debit_total(self) -> int:
|
||||
"""Returns the total amount of the debit records.
|
||||
|
||||
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
|
||||
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:
|
||||
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
|
||||
if "amount" in x.data and "amount" not in x.errors])
|
||||
@ -366,7 +367,8 @@ class AccountForm(forms.Form):
|
||||
self.account = None
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
def parent(self) -> Optional[Account]:
|
||||
"""The parent account, or None if this is the topmost account."""
|
||||
code = self["code"].value()
|
||||
if code is None or len(code) < 2:
|
||||
return None
|
||||
@ -391,7 +393,7 @@ class AccountForm(forms.Form):
|
||||
if 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.
|
||||
|
||||
Raises:
|
||||
@ -411,7 +413,7 @@ class AccountForm(forms.Form):
|
||||
self.add_error("code", error)
|
||||
raise error
|
||||
|
||||
def _validate_code_unique(self):
|
||||
def _validate_code_unique(self) -> None:
|
||||
"""Validates whether the code is unique.
|
||||
|
||||
Raises:
|
||||
@ -432,7 +434,7 @@ class AccountForm(forms.Form):
|
||||
self.add_error("code", error)
|
||||
raise error
|
||||
|
||||
def _validate_code_parent_exists(self):
|
||||
def _validate_code_parent_exists(self) -> None:
|
||||
"""Validates whether the parent account exists.
|
||||
|
||||
Raises:
|
||||
@ -452,7 +454,7 @@ class AccountForm(forms.Form):
|
||||
raise error
|
||||
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.
|
||||
|
||||
Raises:
|
||||
@ -467,7 +469,7 @@ class AccountForm(forms.Form):
|
||||
~Q(pk=self.account.pk))\
|
||||
.aggregate(max_len=Max(Length("code")))["max_len"]
|
||||
if cur_max_len is None:
|
||||
return True
|
||||
return
|
||||
new_max_len = cur_max_len - len(self.account.code)\
|
||||
+ len(self.data["code"])
|
||||
if new_max_len <= 5:
|
||||
|
@ -62,7 +62,7 @@ class Command(BaseCommand):
|
||||
user.save()
|
||||
|
||||
p = Populator(user)
|
||||
p.add_accounts((
|
||||
p.add_accounts([
|
||||
(1, "資產", "assets", "资产"),
|
||||
(2, "負債", "liabilities", "负债"),
|
||||
(3, "業主權益", "owners’ equity", "业主权益"),
|
||||
@ -124,7 +124,7 @@ class Command(BaseCommand):
|
||||
"管理及总务费用"),
|
||||
(6272, "伙食費", "meal (expenses)", "伙食费"),
|
||||
(6273, "職工福利", "employee benefits/welfare", "职工福利"),
|
||||
))
|
||||
])
|
||||
|
||||
income = random.randint(40000, 50000)
|
||||
pension = 882 if income <= 40100\
|
||||
@ -143,49 +143,49 @@ class Command(BaseCommand):
|
||||
month = (date.replace(day=1) - timezone.timedelta(days=1)).month
|
||||
p.add_transfer_transaction(
|
||||
date,
|
||||
((1113, "薪資轉帳", savings),
|
||||
[(1113, "薪資轉帳", savings),
|
||||
(1314, F"勞保{month}月", pension),
|
||||
(6262, F"健保{month}月", insurance),
|
||||
(1255, "代扣所得稅", tax)),
|
||||
((4611, F"{month}月份薪水", income),))
|
||||
(1255, "代扣所得稅", tax)],
|
||||
[(4611, F"{month}月份薪水", income)])
|
||||
|
||||
p.add_income_transaction(
|
||||
-15,
|
||||
((1113, "ATM提款", 2000),))
|
||||
[(1113, "ATM提款", 2000)])
|
||||
p.add_transfer_transaction(
|
||||
-14,
|
||||
((6254, "高鐵—台北→左營", 1490),),
|
||||
((2141, "高鐵—台北→左營", 1490),))
|
||||
[(6254, "高鐵—台北→左營", 1490)],
|
||||
[(2141, "高鐵—台北→左營", 1490)])
|
||||
p.add_transfer_transaction(
|
||||
-14,
|
||||
((6273, "電影—復仇者聯盟", 80),),
|
||||
((2141, "電影—復仇者聯盟", 80),))
|
||||
[(6273, "電影—復仇者聯盟", 80)],
|
||||
[(2141, "電影—復仇者聯盟", 80)])
|
||||
p.add_transfer_transaction(
|
||||
-13,
|
||||
((6273, "電影—2001太空漫遊", 80),),
|
||||
((2141, "電影—2001太空漫遊", 80),))
|
||||
[(6273, "電影—2001太空漫遊", 80)],
|
||||
[(2141, "電影—2001太空漫遊", 80)])
|
||||
p.add_transfer_transaction(
|
||||
-11,
|
||||
((2141, "電影—復仇者聯盟", 80),),
|
||||
((1113, "電影—復仇者聯盟", 80),))
|
||||
[(2141, "電影—復仇者聯盟", 80)],
|
||||
[(1113, "電影—復仇者聯盟", 80)])
|
||||
|
||||
p.add_expense_transaction(
|
||||
-13,
|
||||
((6273, "公車—262—民生社區→頂溪捷運站", 30),))
|
||||
[(6273, "公車—262—民生社區→頂溪捷運站", 30)])
|
||||
|
||||
p.add_expense_transaction(
|
||||
-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(
|
||||
-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(
|
||||
-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(
|
||||
0,
|
||||
((6272, "午餐—雞腿飯", random.randint(40, 200)),
|
||||
(6272, "飲料—咖啡", random.randint(40, 200))))
|
||||
[(6272, "午餐—雞腿飯", random.randint(40, 200)),
|
||||
(6272, "飲料—咖啡", random.randint(40, 200))])
|
||||
|
@ -18,6 +18,9 @@
|
||||
"""The data models of the accounting application.
|
||||
|
||||
"""
|
||||
import datetime
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from dirtyfields import DirtyFieldsMixin
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
@ -83,51 +86,44 @@ class Account(DirtyFieldsMixin, models.Model):
|
||||
db_table = "accounting_accounts"
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
def title(self) -> str:
|
||||
"""The title in the current language."""
|
||||
return get_multi_lingual_attr(self, "title")
|
||||
|
||||
@title.setter
|
||||
def title(self, value):
|
||||
def title(self, value: str) -> None:
|
||||
set_multi_lingual_attr(self, "title", value)
|
||||
|
||||
@property
|
||||
def option_data(self):
|
||||
def option_data(self) -> Dict[str, str]:
|
||||
"""The data as an option."""
|
||||
return {
|
||||
"code": self.code,
|
||||
"title": self.title,
|
||||
}
|
||||
|
||||
@property
|
||||
def is_parent_and_in_use(self):
|
||||
"""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
|
||||
"""
|
||||
def is_parent_and_in_use(self) -> bool:
|
||||
"""Whether this is a parent account and is in use."""
|
||||
if self._is_parent_and_in_use is None:
|
||||
self._is_parent_and_in_use = self.child_set.count() > 0\
|
||||
and self.record_set.count() > 0
|
||||
return self._is_parent_and_in_use
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def is_in_use(self):
|
||||
"""Whether this account is in use.
|
||||
|
||||
Returns:
|
||||
bool: True if this account is in use, or false otherwise.
|
||||
"""
|
||||
def is_in_use(self) -> bool:
|
||||
"""Whether this account is in use."""
|
||||
if self._is_in_use is None:
|
||||
self._is_in_use = self.child_set.count() > 0\
|
||||
or self.record_set.count() > 0
|
||||
return self._is_in_use
|
||||
|
||||
@is_in_use.setter
|
||||
def is_in_use(self, value):
|
||||
def is_in_use(self, value: bool) -> None:
|
||||
self._is_in_use = value
|
||||
|
||||
|
||||
@ -157,7 +153,7 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
transaction."""
|
||||
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."""
|
||||
if self.is_cash_expense:
|
||||
return reverse(
|
||||
@ -169,13 +165,13 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
return reverse(
|
||||
"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
|
||||
to be saved into the database.
|
||||
|
||||
Returns:
|
||||
bool: True if the data of this transaction is changed and need
|
||||
to be saved into the database, or False otherwise.
|
||||
True if the data of this transaction is changed and need to be
|
||||
saved into the database, or False otherwise.
|
||||
"""
|
||||
if super().is_dirty(check_relationship=check_relationship,
|
||||
check_m2m=check_m2m):
|
||||
@ -189,8 +185,9 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
return True
|
||||
return False
|
||||
|
||||
def save(self, current_user=None, old_date=None, force_insert=False,
|
||||
force_update=False, using=None, update_fields=None):
|
||||
def save(self, current_user=None, old_date: datetime.date = 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
|
||||
# day need to be reordered
|
||||
txn_to_sort = []
|
||||
@ -262,7 +259,7 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
"""The records of the transaction.
|
||||
|
||||
Returns:
|
||||
list[Record]: The records.
|
||||
List[Record]: The records.
|
||||
"""
|
||||
if self._records is None:
|
||||
self._records = list(self.record_set.all())
|
||||
@ -278,22 +275,18 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
"""The debit records of this transaction.
|
||||
|
||||
Returns:
|
||||
list[Record]: The records.
|
||||
List[Record]: The records.
|
||||
"""
|
||||
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."""
|
||||
return sum([x.amount for x in self.debit_records
|
||||
if isinstance(x.amount, int)])
|
||||
|
||||
@property
|
||||
def debit_summaries(self):
|
||||
"""The summaries of the debit records.
|
||||
|
||||
Returns:
|
||||
list[str]: The summaries of the debit records.
|
||||
"""
|
||||
def debit_summaries(self) -> List[str]:
|
||||
"""The summaries of the debit records."""
|
||||
return [x.account.title if x.summary is None else x.summary
|
||||
for x in self.debit_records]
|
||||
|
||||
@ -302,36 +295,28 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
"""The credit records of this transaction.
|
||||
|
||||
Returns:
|
||||
list[Record]: The records.
|
||||
List[Record]: The records.
|
||||
"""
|
||||
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."""
|
||||
return sum([x.amount for x in self.credit_records
|
||||
if isinstance(x.amount, int)])
|
||||
|
||||
@property
|
||||
def credit_summaries(self):
|
||||
"""The summaries of the credit records.
|
||||
|
||||
Returns:
|
||||
list[str]: The summaries of the credit records.
|
||||
"""
|
||||
def credit_summaries(self) -> List[str]:
|
||||
"""The summaries of the credit records."""
|
||||
return [x.account.title if x.summary is None else x.summary
|
||||
for x in self.credit_records]
|
||||
|
||||
@property
|
||||
def amount(self):
|
||||
"""The amount of this transaction.
|
||||
|
||||
Returns:
|
||||
int: The amount of this transaction.
|
||||
"""
|
||||
def amount(self) -> int:
|
||||
"""The amount of this transaction."""
|
||||
return self.debit_total()
|
||||
|
||||
@property
|
||||
def is_balanced(self):
|
||||
def is_balanced(self) -> bool:
|
||||
"""Whether the sum of the amounts of the debit records is the
|
||||
same as the sum of the amounts of the credit records. """
|
||||
if self._is_balanced is None:
|
||||
@ -341,16 +326,16 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
return self._is_balanced
|
||||
|
||||
@is_balanced.setter
|
||||
def is_balanced(self, value):
|
||||
def is_balanced(self, value: bool) -> None:
|
||||
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,
|
||||
so that the user can sort their orders. """
|
||||
return Transaction.objects.filter(date=self.date).count() > 1
|
||||
|
||||
@property
|
||||
def has_order_hole(self):
|
||||
def has_order_hole(self) -> bool:
|
||||
"""Whether the order of the transactions on this day is not
|
||||
1, 2, 3, 4, 5..., and should be reordered. """
|
||||
if self._has_order_hole is None:
|
||||
@ -369,11 +354,11 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
return self._has_order_hole
|
||||
|
||||
@has_order_hole.setter
|
||||
def has_order_hole(self, value):
|
||||
def has_order_hole(self, value: bool) -> None:
|
||||
self._has_order_hole = value
|
||||
|
||||
@property
|
||||
def is_cash_income(self):
|
||||
def is_cash_income(self) -> bool:
|
||||
"""Whether this transaction is a cash income transaction."""
|
||||
debit_records = self.debit_records
|
||||
return (len(debit_records) == 1
|
||||
@ -381,7 +366,7 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
and debit_records[0].summary is None)
|
||||
|
||||
@property
|
||||
def is_cash_expense(self):
|
||||
def is_cash_expense(self) -> bool:
|
||||
"""Whether this transaction is a cash expense transaction."""
|
||||
credit_records = self.credit_records
|
||||
return (len(credit_records) == 1
|
||||
@ -389,7 +374,7 @@ class Transaction(DirtyFieldsMixin, models.Model):
|
||||
and credit_records[0].summary is None)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
def type(self) -> str:
|
||||
"""The transaction type."""
|
||||
if self.is_cash_expense:
|
||||
return "expense"
|
||||
@ -444,40 +429,40 @@ class Record(DirtyFieldsMixin, models.Model):
|
||||
db_table = "accounting_records"
|
||||
|
||||
@property
|
||||
def debit_amount(self):
|
||||
def debit_amount(self) -> Optional[int]:
|
||||
"""The debit amount of this accounting record."""
|
||||
if self._debit_amount is None:
|
||||
self._debit_amount = self.amount if not self.is_credit else None
|
||||
return self._debit_amount
|
||||
|
||||
@debit_amount.setter
|
||||
def debit_amount(self, value):
|
||||
def debit_amount(self, value: Optional[int]) -> None:
|
||||
self._debit_amount = value
|
||||
|
||||
@property
|
||||
def credit_amount(self):
|
||||
def credit_amount(self) -> Optional[int]:
|
||||
"""The credit amount of this accounting record."""
|
||||
if self._credit_amount is None:
|
||||
self._credit_amount = self.amount if self.is_credit else None
|
||||
return self._credit_amount
|
||||
|
||||
@credit_amount.setter
|
||||
def credit_amount(self, value):
|
||||
def credit_amount(self, value: Optional[int]):
|
||||
self._credit_amount = value
|
||||
|
||||
@property
|
||||
def is_balanced(self):
|
||||
def is_balanced(self) -> bool:
|
||||
"""Whether the transaction of this record is balanced. """
|
||||
if self._is_balanced is None:
|
||||
self._is_balanced = self.transaction.is_balanced
|
||||
return self._is_balanced
|
||||
|
||||
@is_balanced.setter
|
||||
def is_balanced(self, value):
|
||||
def is_balanced(self, value: bool) -> None:
|
||||
self._is_balanced = value
|
||||
|
||||
@property
|
||||
def has_order_hole(self):
|
||||
def has_order_hole(self) -> bool:
|
||||
"""Whether the order of the transactions on this day is not
|
||||
1, 2, 3, 4, 5..., and should be reordered. """
|
||||
if self._has_order_hole is None:
|
||||
@ -485,5 +470,5 @@ class Record(DirtyFieldsMixin, models.Model):
|
||||
return self._has_order_hole
|
||||
|
||||
@has_order_hole.setter
|
||||
def has_order_hole(self, value):
|
||||
def has_order_hole(self, value: bool) -> None:
|
||||
self._has_order_hole = value
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
"""
|
||||
import re
|
||||
from typing import Union, Optional
|
||||
|
||||
from django import template
|
||||
|
||||
@ -30,7 +31,7 @@ register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def accounting_amount(value):
|
||||
def accounting_amount(value: Union[str, int]) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
if value == 0:
|
||||
@ -47,13 +48,15 @@ def accounting_amount(value):
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
cash_account (Account): The current cash account.
|
||||
ledger_account (Account): The current ledger account.
|
||||
period (Period): The period.
|
||||
cash_account: The current cash account.
|
||||
ledger_account: The current ledger account.
|
||||
period: The period.
|
||||
|
||||
Returns:
|
||||
ReportUrl: The accounting report URL helper.
|
||||
|
@ -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"]
|
||||
@ -422,12 +425,13 @@ def find_payable_records(account, records):
|
||||
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"]
|
||||
@ -457,13 +461,13 @@ def find_existing_equipments(account, records):
|
||||
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:
|
||||
|
@ -25,11 +25,11 @@ from django.utils.translation import gettext as _
|
||||
from .models import Account, Record
|
||||
|
||||
|
||||
def validate_record_id(value):
|
||||
def validate_record_id(value: str) -> None:
|
||||
"""Validates the record ID.
|
||||
|
||||
Args:
|
||||
value (str): The record ID.
|
||||
value: The record ID.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
@ -41,11 +41,11 @@ def validate_record_id(value):
|
||||
code="not_exist")
|
||||
|
||||
|
||||
def validate_record_account_code(value):
|
||||
def validate_record_account_code(value: str) -> None:
|
||||
"""Validates an account code.
|
||||
|
||||
Args:
|
||||
value (str): The account code.
|
||||
value: The account code.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
|
@ -18,6 +18,7 @@
|
||||
"""The view controllers of the accounting application.
|
||||
|
||||
"""
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
|
||||
@ -26,7 +27,8 @@ from django.db import transaction
|
||||
from django.db.models import Sum, Case, When, F, Q, Count, BooleanField, \
|
||||
ExpressionWrapper
|
||||
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.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
@ -62,16 +64,17 @@ class CashDefaultView(RedirectView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def cash(request, account, period):
|
||||
def cash(request: HttpRequest, account: Account,
|
||||
period: Period) -> HttpResponse:
|
||||
"""The cash account.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
account (Account): The account.
|
||||
period (Period): The period.
|
||||
request: The request.
|
||||
account: The account.
|
||||
period: The period.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The accounting records
|
||||
if account.code == "0":
|
||||
@ -180,15 +183,15 @@ class CashSummaryDefaultView(RedirectView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def cash_summary(request, account):
|
||||
def cash_summary(request: HttpRequest, account: Account) -> HttpResponse:
|
||||
"""The cash account summary.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
account (Account): The account.
|
||||
request: The request.
|
||||
account: The account.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The account
|
||||
accounts = utils.get_cash_accounts()
|
||||
@ -278,16 +281,17 @@ class LedgerDefaultView(RedirectView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def ledger(request, account, period):
|
||||
def ledger(request: HttpRequest, account: Account,
|
||||
period: Period) -> HttpResponse:
|
||||
"""The ledger.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
account (Account): The account.
|
||||
period (Period): The period.
|
||||
request: The request.
|
||||
account: The account.
|
||||
period: The period.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The accounting records
|
||||
records = list(
|
||||
@ -354,15 +358,15 @@ class LedgerSummaryDefaultView(RedirectView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def ledger_summary(request, account):
|
||||
def ledger_summary(request: HttpRequest, account: Account) -> HttpResponse:
|
||||
"""The ledger summary report.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
account (Account): The account.
|
||||
request: The request.
|
||||
account: The account.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The month summaries
|
||||
months = [utils.MonthlySummary(**x) for x in Record.objects
|
||||
@ -416,15 +420,15 @@ class JournalDefaultView(RedirectView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def journal(request, period):
|
||||
def journal(request: HttpRequest, period: Period) -> HttpResponse:
|
||||
"""The journal.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
period (Period): The period.
|
||||
request: The request.
|
||||
period: The period.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The accounting records
|
||||
records = Record.objects \
|
||||
@ -498,15 +502,15 @@ class TrialBalanceDefaultView(RedirectView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def trial_balance(request, period):
|
||||
def trial_balance(request: HttpRequest, period: Period) -> HttpResponse:
|
||||
"""The trial balance.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
period (Period): The period.
|
||||
request: The request.
|
||||
period: The period.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The accounts
|
||||
nominal = list(
|
||||
@ -601,15 +605,15 @@ class IncomeStatementDefaultView(RedirectView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def income_statement(request, period):
|
||||
def income_statement(request: HttpRequest, period: Period) -> HttpResponse:
|
||||
"""The income statement.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
period (Period): The period.
|
||||
request: The request.
|
||||
period: The period.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The accounts
|
||||
accounts = list(
|
||||
@ -676,15 +680,15 @@ class BalanceSheetDefaultView(RedirectView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def balance_sheet(request, period):
|
||||
def balance_sheet(request: HttpRequest, period: Period) -> HttpResponse:
|
||||
"""The balance sheet.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
period (Period): The period.
|
||||
request: The request.
|
||||
period: The period.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The accounts
|
||||
accounts = list(
|
||||
@ -764,14 +768,14 @@ def balance_sheet(request, period):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def search(request):
|
||||
def search(request: HttpRequest) -> HttpResponse:
|
||||
"""The search.
|
||||
|
||||
Args:
|
||||
request (HttpRequest) The request.
|
||||
request: The request.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
# The accounting records
|
||||
query = request.GET.get("q")
|
||||
@ -809,16 +813,17 @@ class TransactionView(DetailView):
|
||||
|
||||
@require_GET
|
||||
@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.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request.
|
||||
txn_type (str): The transaction type.
|
||||
txn (Transaction): The transaction.
|
||||
request: The request.
|
||||
txn_type: The transaction type.
|
||||
txn: The transaction.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
previous_post = stored_post.get_previous_post(request)
|
||||
if previous_post is not None:
|
||||
@ -847,16 +852,17 @@ def txn_form(request, txn_type, txn=None):
|
||||
|
||||
@require_POST
|
||||
@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.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request.
|
||||
txn_type (str): The transaction type.
|
||||
txn (Transaction): The transaction.
|
||||
request: The request.
|
||||
txn_type: The transaction type.
|
||||
txn: The transaction.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
post = request.POST.dict()
|
||||
strip_post(post)
|
||||
@ -900,15 +906,15 @@ class TransactionDeleteView(DeleteView):
|
||||
|
||||
|
||||
@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.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request.
|
||||
date (datetime.date): The day.
|
||||
request: The request.
|
||||
date: The day.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
|
||||
Raises:
|
||||
Http404: When there are less than two transactions in this day.
|
||||
@ -977,15 +983,16 @@ class AccountView(DetailView):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def account_form(request, account=None):
|
||||
def account_form(request: HttpRequest,
|
||||
account: Account = None) -> HttpResponse:
|
||||
"""The view to edit an accounting transaction.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request.
|
||||
account (Account): The account.
|
||||
request: The request.
|
||||
account: The account.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
previous_post = stored_post.get_previous_post(request)
|
||||
if previous_post is not None:
|
||||
@ -1005,15 +1012,16 @@ def account_form(request, account=None):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def account_store(request, account=None):
|
||||
def account_store(request: HttpRequest,
|
||||
account: Account = None) -> HttpResponseRedirect:
|
||||
"""The view to store an account.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request.
|
||||
account (Account): The account.
|
||||
request: The request.
|
||||
account: The account.
|
||||
|
||||
Returns:
|
||||
HttpResponseRedirect: The response.
|
||||
The response.
|
||||
"""
|
||||
post = request.POST.dict()
|
||||
strip_post(post)
|
||||
@ -1040,7 +1048,8 @@ def account_store(request, account=None):
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def account_delete(request, account):
|
||||
def account_delete(request: HttpRequest,
|
||||
account: Account) -> HttpResponseRedirect:
|
||||
"""The view to delete an account.
|
||||
|
||||
Args:
|
||||
@ -1062,28 +1071,28 @@ def account_delete(request, account):
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def api_account_list(request):
|
||||
def api_account_list(request: HttpRequest) -> JsonResponse:
|
||||
"""The API view to return all the accounts.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request.
|
||||
request: The request.
|
||||
|
||||
Returns:
|
||||
JsonResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
return JsonResponse({x.code: x.title for x in Account.objects.all()})
|
||||
|
||||
|
||||
@require_GET
|
||||
@login_required
|
||||
def api_account_options(request):
|
||||
def api_account_options(request: HttpRequest) -> JsonResponse:
|
||||
"""The API view to return the account options.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request.
|
||||
request: The request.
|
||||
|
||||
Returns:
|
||||
JsonResponse: The response.
|
||||
The response.
|
||||
"""
|
||||
accounts = Account.objects\
|
||||
.annotate(children_count=Count("child_set"))\
|
||||
|
Loading…
Reference in New Issue
Block a user