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 datetime
|
||||||
import re
|
import re
|
||||||
|
from decimal import Decimal
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
@ -57,12 +58,14 @@ class RecordForm(forms.Form):
|
|||||||
error_messages={
|
error_messages={
|
||||||
"max_length": _("This summary is too long (max. 128 characters)."),
|
"max_length": _("This summary is too long (max. 128 characters)."),
|
||||||
})
|
})
|
||||||
amount = forms.IntegerField(
|
amount = forms.DecimalField(
|
||||||
min_value=1,
|
max_digits=18,
|
||||||
|
decimal_places=2,
|
||||||
|
min_value=0.01,
|
||||||
error_messages={
|
error_messages={
|
||||||
"required": _("Please fill in the amount."),
|
"required": _("Please fill in the amount."),
|
||||||
"invalid": _("Please fill in a number."),
|
"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):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -436,22 +439,22 @@ class TransactionForm(forms.Form):
|
|||||||
return errors[0].message
|
return errors[0].message
|
||||||
return None
|
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 debit records.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
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([Decimal(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) -> int:
|
def credit_total(self) -> Decimal:
|
||||||
"""Returns the total amount of the credit records.
|
"""Returns the total amount of the credit records.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
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([Decimal(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])
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
self._filler.add_expense_transaction(
|
self._filler.add_expense_transaction(
|
||||||
-13,
|
-13,
|
||||||
[(6273, "Bus—2623—Uptown→City Park", 30)])
|
[(6273, "Bus—2623—Uptown→City Park", 477543627.4775)])
|
||||||
|
|
||||||
self._filler.add_expense_transaction(
|
self._filler.add_expense_transaction(
|
||||||
-2,
|
-2,
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"""
|
"""
|
||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
|
from decimal import Decimal
|
||||||
from typing import Dict, List, Optional, Mapping
|
from typing import Dict, List, Optional, Mapping
|
||||||
|
|
||||||
from dirtyfields import DirtyFieldsMixin
|
from dirtyfields import DirtyFieldsMixin
|
||||||
@ -236,7 +237,7 @@ class Transaction(DirtyFieldsMixin, BaseModel):
|
|||||||
record.summary = post[F"{record_type}-{no}-summary"]
|
record.summary = post[F"{record_type}-{no}-summary"]
|
||||||
else:
|
else:
|
||||||
record.summary = None
|
record.summary = None
|
||||||
record.amount = int(post[F"{record_type}-{no}-amount"])
|
record.amount = Decimal(post[F"{record_type}-{no}-amount"])
|
||||||
records.append(record)
|
records.append(record)
|
||||||
if txn_type != "transfer":
|
if txn_type != "transfer":
|
||||||
if txn_type == "expense":
|
if txn_type == "expense":
|
||||||
@ -314,10 +315,10 @@ class Transaction(DirtyFieldsMixin, BaseModel):
|
|||||||
"""
|
"""
|
||||||
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) -> int:
|
def debit_total(self) -> Decimal:
|
||||||
"""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, Decimal)])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def debit_summaries(self) -> List[str]:
|
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]
|
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."""
|
"""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, Decimal)])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def credit_summaries(self) -> List[str]:
|
def credit_summaries(self) -> List[str]:
|
||||||
@ -346,7 +347,7 @@ class Transaction(DirtyFieldsMixin, BaseModel):
|
|||||||
for x in self.credit_records]
|
for x in self.credit_records]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def amount(self) -> int:
|
def amount(self) -> Decimal:
|
||||||
"""The amount of this transaction."""
|
"""The amount of this transaction."""
|
||||||
return self.debit_total()
|
return self.debit_total()
|
||||||
|
|
||||||
@ -428,13 +429,13 @@ class Record(DirtyFieldsMixin, BaseModel):
|
|||||||
account = models.ForeignKey(
|
account = models.ForeignKey(
|
||||||
Account, on_delete=models.PROTECT)
|
Account, on_delete=models.PROTECT)
|
||||||
summary = models.CharField(max_length=128, blank=True, null=True)
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._debit_amount = None
|
self._debit_amount: Optional[Decimal] = None
|
||||||
self._credit_amount = None
|
self._credit_amount: Optional[Decimal] = None
|
||||||
self.balance = None
|
self.balance: Optional[Decimal] = None
|
||||||
self._is_balanced = None
|
self._is_balanced = None
|
||||||
self._has_order_hole = None
|
self._has_order_hole = None
|
||||||
self._is_payable = None
|
self._is_payable = None
|
||||||
@ -452,25 +453,25 @@ class Record(DirtyFieldsMixin, BaseModel):
|
|||||||
self.amount)
|
self.amount)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def debit_amount(self) -> Optional[int]:
|
def debit_amount(self) -> Optional[Decimal]:
|
||||||
"""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: Optional[int]) -> None:
|
def debit_amount(self, value: Optional[Decimal]) -> None:
|
||||||
self._debit_amount = value
|
self._debit_amount = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def credit_amount(self) -> Optional[int]:
|
def credit_amount(self) -> Optional[Decimal]:
|
||||||
"""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: Optional[int]):
|
def credit_amount(self, value: Optional[Decimal]):
|
||||||
self._credit_amount = value
|
self._credit_amount = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -177,10 +177,10 @@ function removeBlankOption(select) {
|
|||||||
*/
|
*/
|
||||||
function updateTotalAmount(element) {
|
function updateTotalAmount(element) {
|
||||||
const type = element.data("type")
|
const type = element.data("type")
|
||||||
let total = 0;
|
let total = new Decimal("0");
|
||||||
$("." + type + "-to-sum").each(function () {
|
$("." + type + "-to-sum").each(function () {
|
||||||
if (this.value !== "") {
|
if (this.value !== "") {
|
||||||
total += parseInt(this.value);
|
total = total.plus(new Decimal(this.value));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
total = String(total);
|
total = String(total);
|
||||||
@ -465,19 +465,19 @@ function validateAmount(amount) {
|
|||||||
function validateBalance() {
|
function validateBalance() {
|
||||||
const balanceRows = $(".balance-row");
|
const balanceRows = $(".balance-row");
|
||||||
const errorMessages = $(".balance-error");
|
const errorMessages = $(".balance-error");
|
||||||
let debitTotal = 0;
|
let debitTotal = new Decimal("0");
|
||||||
$(".debit-to-sum").each(function () {
|
$(".debit-to-sum").each(function () {
|
||||||
if (this.value !== "") {
|
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 () {
|
$(".credit-to-sum").each(function () {
|
||||||
if (this.value !== "") {
|
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");
|
balanceRows.addClass("is-invalid");
|
||||||
errorMessages.text(gettext("The total amount of debit and credit records are inconsistent."))
|
errorMessages.text(gettext("The total amount of debit and credit records are inconsistent."))
|
||||||
return false;
|
return false;
|
||||||
|
@ -37,7 +37,7 @@ First written: 2020/8/5
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<label for="{{ record_type }}-{{ no }}-amount" class="record-label">{{ _("Amount:")|force_escape }}</label>
|
<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 id="{{ record_type }}-{{ no }}-amount-error" class="invalid-feedback">{{ record.amount.errors.0|default:"" }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +36,7 @@ First written: 2020/8/5
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<label for="{{ record_type }}-{{ no }}-amount" class="record-label">{{ _("Amount:")|force_escape }}</label>
|
<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 id="{{ record_type }}-{{ no }}-amount-error" class="invalid-feedback">{{ record.amount.errors.0|default:"" }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,6 +28,7 @@ First written: 2020/7/23
|
|||||||
{% block settings %}
|
{% block settings %}
|
||||||
{% setvar "title" _("Cash Expense Transaction") %}
|
{% setvar "title" _("Cash Expense Transaction") %}
|
||||||
{% setvar "use_jqueryui" True %}
|
{% 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/transactions.css" as file %}{% add_css file %}
|
||||||
{% static "accounting/css/summary-helper.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 %}
|
{% static "accounting/js/transaction-form.js" as file %}{% add_js file %}
|
||||||
|
@ -28,6 +28,7 @@ First written: 2020/7/23
|
|||||||
{% block settings %}
|
{% block settings %}
|
||||||
{% setvar "title" _("Cash Income Transaction") %}
|
{% setvar "title" _("Cash Income Transaction") %}
|
||||||
{% setvar "use_jqueryui" True %}
|
{% 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/transactions.css" as file %}{% add_css file %}
|
||||||
{% static "accounting/css/summary-helper.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 %}
|
{% static "accounting/js/transaction-form.js" as file %}{% add_js file %}
|
||||||
|
@ -28,6 +28,7 @@ First written: 2020/7/23
|
|||||||
{% block settings %}
|
{% block settings %}
|
||||||
{% setvar "title" _("Transfer Transaction") %}
|
{% setvar "title" _("Transfer Transaction") %}
|
||||||
{% setvar "use_jqueryui" True %}
|
{% 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/transactions.css" as file %}{% add_css file %}
|
||||||
{% static "accounting/css/summary-helper.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 %}
|
{% static "accounting/js/transaction-form.js" as file %}{% add_js file %}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
from decimal import Decimal
|
||||||
from typing import Union, Optional
|
from typing import Union, Optional
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
@ -32,14 +33,14 @@ register = template.Library()
|
|||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def accounting_amount(value: Union[str, int]) -> str:
|
def accounting_amount(value: Union[str, Decimal]) -> str:
|
||||||
if value is None:
|
if value is None:
|
||||||
return ""
|
return ""
|
||||||
if value == 0:
|
if value == 0:
|
||||||
return "-"
|
return "-"
|
||||||
s = str(abs(value))
|
s = str(abs(value))
|
||||||
while True:
|
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:
|
if m is None:
|
||||||
break
|
break
|
||||||
s = m.group(1) + "," + m.group(2)
|
s = m.group(1) + "," + m.group(2)
|
||||||
|
@ -33,7 +33,7 @@ from mia_core.utils import new_pk
|
|||||||
from .models import Account, Transaction, Record
|
from .models import Account, Transaction, Record
|
||||||
|
|
||||||
AccountData = Tuple[Union[str, int], str, str, str]
|
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"
|
DEFAULT_CASH_ACCOUNT = "1111"
|
||||||
CASH_SHORTCUT_ACCOUNTS = ["0", "1111"]
|
CASH_SHORTCUT_ACCOUNTS = ["0", "1111"]
|
||||||
|
Loading…
Reference in New Issue
Block a user