Revised to allow amounts in decimal numbers in the accounting application.
This commit is contained in:
parent
50bc6fb0b1
commit
567a610e90
@ -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])
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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 %}
|
||||
|
@ -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)
|
||||
|
@ -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"]
|
||||
|
Loading…
Reference in New Issue
Block a user