Revised to allow amounts in decimal numbers in the accounting application.

This commit is contained in:
依瑪貓 2020-08-21 10:00:59 +08:00
parent 50bc6fb0b1
commit 567a610e90
11 changed files with 42 additions and 34 deletions

View File

@ -20,6 +20,7 @@
"""
import datetime
import re
from decimal import Decimal
from typing import Optional, List, Dict
from django import forms
@ -57,12 +58,14 @@ class RecordForm(forms.Form):
error_messages={
"max_length": _("This summary is too long (max. 128 characters)."),
})
amount = forms.IntegerField(
min_value=1,
amount = forms.DecimalField(
max_digits=18,
decimal_places=2,
min_value=0.01,
error_messages={
"required": _("Please fill in the amount."),
"invalid": _("Please fill in a number."),
"min_value": _("The amount must be at least 1."),
"min_value": _("The amount must be more than 0."),
})
def __init__(self, *args, **kwargs):
@ -436,22 +439,22 @@ class TransactionForm(forms.Form):
return errors[0].message
return None
def debit_total(self) -> int:
def debit_total(self) -> Decimal:
"""Returns the total amount of the debit records.
Returns:
The total amount of the credit records.
"""
return sum([int(x.data["amount"]) for x in self.debit_records
return sum([Decimal(x.data["amount"]) for x in self.debit_records
if "amount" in x.data and "amount" not in x.errors])
def credit_total(self) -> int:
def credit_total(self) -> Decimal:
"""Returns the total amount of the credit records.
Returns:
The total amount of the credit records.
"""
return sum([int(x.data["amount"]) for x in self.credit_records
return sum([Decimal(x.data["amount"]) for x in self.credit_records
if "amount" in x.data and "amount" not in x.errors])

View File

@ -157,7 +157,7 @@ class Command(BaseCommand):
self._filler.add_expense_transaction(
-13,
[(6273, "Bus—2623—Uptown→City Park", 30)])
[(6273, "Bus—2623—Uptown→City Park", 477543627.4775)])
self._filler.add_expense_transaction(
-2,

View File

@ -20,6 +20,7 @@
"""
import datetime
import re
from decimal import Decimal
from typing import Dict, List, Optional, Mapping
from dirtyfields import DirtyFieldsMixin
@ -236,7 +237,7 @@ class Transaction(DirtyFieldsMixin, BaseModel):
record.summary = post[F"{record_type}-{no}-summary"]
else:
record.summary = None
record.amount = int(post[F"{record_type}-{no}-amount"])
record.amount = Decimal(post[F"{record_type}-{no}-amount"])
records.append(record)
if txn_type != "transfer":
if txn_type == "expense":
@ -314,10 +315,10 @@ class Transaction(DirtyFieldsMixin, BaseModel):
"""
return [x for x in self.records if not x.is_credit]
def debit_total(self) -> int:
def debit_total(self) -> Decimal:
"""The total amount of the debit records."""
return sum([x.amount for x in self.debit_records
if isinstance(x.amount, int)])
if isinstance(x.amount, Decimal)])
@property
def debit_summaries(self) -> List[str]:
@ -334,10 +335,10 @@ class Transaction(DirtyFieldsMixin, BaseModel):
"""
return [x for x in self.records if x.is_credit]
def credit_total(self) -> int:
def credit_total(self) -> Decimal:
"""The total amount of the credit records."""
return sum([x.amount for x in self.credit_records
if isinstance(x.amount, int)])
if isinstance(x.amount, Decimal)])
@property
def credit_summaries(self) -> List[str]:
@ -346,7 +347,7 @@ class Transaction(DirtyFieldsMixin, BaseModel):
for x in self.credit_records]
@property
def amount(self) -> int:
def amount(self) -> Decimal:
"""The amount of this transaction."""
return self.debit_total()
@ -428,13 +429,13 @@ class Record(DirtyFieldsMixin, BaseModel):
account = models.ForeignKey(
Account, on_delete=models.PROTECT)
summary = models.CharField(max_length=128, blank=True, null=True)
amount = models.PositiveIntegerField()
amount = models.DecimalField(max_digits=18, decimal_places=2)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._debit_amount = None
self._credit_amount = None
self.balance = None
self._debit_amount: Optional[Decimal] = None
self._credit_amount: Optional[Decimal] = None
self.balance: Optional[Decimal] = None
self._is_balanced = None
self._has_order_hole = None
self._is_payable = None
@ -452,25 +453,25 @@ class Record(DirtyFieldsMixin, BaseModel):
self.amount)
@property
def debit_amount(self) -> Optional[int]:
def debit_amount(self) -> Optional[Decimal]:
"""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: Optional[int]) -> None:
def debit_amount(self, value: Optional[Decimal]) -> None:
self._debit_amount = value
@property
def credit_amount(self) -> Optional[int]:
def credit_amount(self) -> Optional[Decimal]:
"""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: Optional[int]):
def credit_amount(self, value: Optional[Decimal]):
self._credit_amount = value
@property

View File

@ -177,10 +177,10 @@ function removeBlankOption(select) {
*/
function updateTotalAmount(element) {
const type = element.data("type")
let total = 0;
let total = new Decimal("0");
$("." + type + "-to-sum").each(function () {
if (this.value !== "") {
total += parseInt(this.value);
total = total.plus(new Decimal(this.value));
}
});
total = String(total);
@ -465,19 +465,19 @@ function validateAmount(amount) {
function validateBalance() {
const balanceRows = $(".balance-row");
const errorMessages = $(".balance-error");
let debitTotal = 0;
let debitTotal = new Decimal("0");
$(".debit-to-sum").each(function () {
if (this.value !== "") {
debitTotal += parseInt(this.value);
debitTotal = debitTotal.plus(new Decimal(this.value));
}
});
let creditTotal = 0;
let creditTotal = new Decimal("0");
$(".credit-to-sum").each(function () {
if (this.value !== "") {
creditTotal += parseInt(this.value);
creditTotal = creditTotal.plus(new Decimal(this.value));
}
});
if (debitTotal !== creditTotal) {
if (!debitTotal.equals(creditTotal)) {
balanceRows.addClass("is-invalid");
errorMessages.text(gettext("The total amount of debit and credit records are inconsistent."))
return false;

View File

@ -37,7 +37,7 @@ First written: 2020/8/5
</div>
<div class="col-sm-4">
<label for="{{ record_type }}-{{ no }}-amount" class="record-label">{{ _("Amount:")|force_escape }}</label>
<input id="{{ record_type }}-{{ no }}-amount" class="form-control record-amount {{ record_type }}-to-sum {% if record.amount.errors %} is-invalid {% endif %}" type="number" min="1" name="{{ record_type }}-{{ no }}-amount" value="{{ record.amount.value|default:"" }}" required="required" data-type="{{ record_type }}" />
<input id="{{ record_type }}-{{ no }}-amount" class="form-control record-amount {{ record_type }}-to-sum {% if record.amount.errors %} is-invalid {% endif %}" type="number" step="0.01" min="0.01" name="{{ record_type }}-{{ no }}-amount" value="{{ record.amount.value|default:"" }}" required="required" data-type="{{ record_type }}" />
<div id="{{ record_type }}-{{ no }}-amount-error" class="invalid-feedback">{{ record.amount.errors.0|default:"" }}</div>
</div>
</div>

View File

@ -36,7 +36,7 @@ First written: 2020/8/5
</div>
<div class="col-lg-4">
<label for="{{ record_type }}-{{ no }}-amount" class="record-label">{{ _("Amount:")|force_escape }}</label>
<input id="{{ record_type }}-{{ no }}-amount" class="form-control record-amount {{ record_type }}-to-sum {% if record.amount.errors %} is-invalid {% endif %}" type="number" min="1" name="{{ record_type }}-{{ no }}-amount" value="{{ record.amount.value|default:"" }}" required="required" data-type="{{ record_type }}" />
<input id="{{ record_type }}-{{ no }}-amount" class="form-control record-amount {{ record_type }}-to-sum {% if record.amount.errors %} is-invalid {% endif %}" type="number" step="0.01" min="0.01" name="{{ record_type }}-{{ no }}-amount" value="{{ record.amount.value|default:"" }}" required="required" data-type="{{ record_type }}" />
<div id="{{ record_type }}-{{ no }}-amount-error" class="invalid-feedback">{{ record.amount.errors.0|default:"" }}</div>
</div>
</div>

View File

@ -28,6 +28,7 @@ First written: 2020/7/23
{% block settings %}
{% setvar "title" _("Cash Expense Transaction") %}
{% setvar "use_jqueryui" True %}
{% static "ext-libs/decimal/decimal.min.js" as file %}{% add_js file %}
{% static "accounting/css/transactions.css" as file %}{% add_css file %}
{% static "accounting/css/summary-helper.css" as file %}{% add_css file %}
{% static "accounting/js/transaction-form.js" as file %}{% add_js file %}

View File

@ -28,6 +28,7 @@ First written: 2020/7/23
{% block settings %}
{% setvar "title" _("Cash Income Transaction") %}
{% setvar "use_jqueryui" True %}
{% static "ext-libs/decimal/decimal.min.js" as file %}{% add_js file %}
{% static "accounting/css/transactions.css" as file %}{% add_css file %}
{% static "accounting/css/summary-helper.css" as file %}{% add_css file %}
{% static "accounting/js/transaction-form.js" as file %}{% add_js file %}

View File

@ -28,6 +28,7 @@ First written: 2020/7/23
{% block settings %}
{% setvar "title" _("Transfer Transaction") %}
{% setvar "use_jqueryui" True %}
{% static "ext-libs/decimal/decimal.min.js" as file %}{% add_js file %}
{% static "accounting/css/transactions.css" as file %}{% add_css file %}
{% static "accounting/css/summary-helper.css" as file %}{% add_css file %}
{% static "accounting/js/transaction-form.js" as file %}{% add_js file %}

View File

@ -19,6 +19,7 @@
"""
import re
from decimal import Decimal
from typing import Union, Optional
from django import template
@ -32,14 +33,14 @@ register = template.Library()
@register.filter
def accounting_amount(value: Union[str, int]) -> str:
def accounting_amount(value: Union[str, Decimal]) -> str:
if value is None:
return ""
if value == 0:
return "-"
s = str(abs(value))
while True:
m = re.match("^([1-9][0-9]*)([0-9]{3})", s)
m = re.match("^([1-9][0-9]*)([0-9]{3}.*)", s)
if m is None:
break
s = m.group(1) + "," + m.group(2)

View File

@ -33,7 +33,7 @@ from mia_core.utils import new_pk
from .models import Account, Transaction, Record
AccountData = Tuple[Union[str, int], str, str, str]
RecordData = Tuple[Union[str, int], Optional[str], int]
RecordData = Tuple[Union[str, int], Optional[str], float]
DEFAULT_CASH_ACCOUNT = "1111"
CASH_SHORTCUT_ACCOUNTS = ["0", "1111"]