Added forms and validators, and applied them to the transaction form in the accounting application.
This commit is contained in:
parent
c1da25b3b5
commit
9d988f17ca
242
accounting/forms.py
Normal file
242
accounting/forms.py
Normal file
@ -0,0 +1,242 @@
|
||||
# The core application of the Mia project.
|
||||
# by imacat <imacat@mail.imacat.idv.tw>, 2020/7/31
|
||||
|
||||
# Copyright (c) 2020 imacat.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""The forms of the Mia core application.
|
||||
|
||||
"""
|
||||
import re
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import pgettext
|
||||
|
||||
from .models import Account, Record
|
||||
from .validators import validate_record_account_code, validate_record_id
|
||||
|
||||
|
||||
class RecordForm(forms.Form):
|
||||
"""An accounting record form.
|
||||
|
||||
Attributes:
|
||||
transaction (Transaction|None): The current transaction or None.
|
||||
is_credit (bool): Whether this is a credit record.
|
||||
"""
|
||||
id = forms.IntegerField(
|
||||
required=False,
|
||||
error_messages={
|
||||
"invalid": pgettext("Accounting|", "This record is not valid."),
|
||||
},
|
||||
validators=[validate_record_id])
|
||||
account = forms.CharField(
|
||||
error_messages={
|
||||
"required": pgettext("Accounting|", "Please select the account."),
|
||||
},
|
||||
validators=[validate_record_account_code])
|
||||
summary = forms.CharField(
|
||||
required=False,
|
||||
max_length=128,
|
||||
error_messages={
|
||||
"max_length": pgettext("Accounting|", "This summary is too long."),
|
||||
})
|
||||
amount = forms.IntegerField(
|
||||
min_value=1,
|
||||
error_messages={
|
||||
"required": pgettext("Accounting|", "Please fill in the amount."),
|
||||
"invalid": pgettext("Accounting|", "Please fill in a number."),
|
||||
"min_value": pgettext(
|
||||
"Accounting|", "The amount must be at least 1."),
|
||||
})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.transaction = None
|
||||
self.is_credit = None
|
||||
|
||||
def account_title(self):
|
||||
"""Returns the title of the specified account, if any.
|
||||
|
||||
Returns:
|
||||
str: The title of the specified account, or None if the specified
|
||||
account is not available.
|
||||
"""
|
||||
try:
|
||||
return Account.objects.get(code=self["account"].value()).title
|
||||
except KeyError:
|
||||
return None
|
||||
except Account.DoesNotExist:
|
||||
return None
|
||||
|
||||
def clean(self):
|
||||
"""Validates the form globally.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
"""
|
||||
errors = []
|
||||
validators = [self._validate_transaction, self._validate_account_type]
|
||||
for validator in validators:
|
||||
try:
|
||||
validator()
|
||||
except forms.ValidationError as e:
|
||||
errors.append(e)
|
||||
if errors:
|
||||
print(errors)
|
||||
raise forms.ValidationError(errors)
|
||||
|
||||
def _validate_transaction(self):
|
||||
"""Validates whether the transaction matches the transaction form.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
"""
|
||||
if "id" in self.errors:
|
||||
return
|
||||
if self.transaction is None:
|
||||
if "id" in self.data:
|
||||
error = forms.ValidationError(
|
||||
pgettext("Accounting|",
|
||||
"This record is not for this transaction."),
|
||||
code="not_belong")
|
||||
self.add_error("id", error)
|
||||
raise error
|
||||
else:
|
||||
if "id" in self.data:
|
||||
record = Record.objects.get(pk=self.data["id"])
|
||||
if record.transaction.pk != self.transaction.pk:
|
||||
error = forms.ValidationError(
|
||||
pgettext("Accounting|",
|
||||
"This record is not for this transaction."),
|
||||
code="not_belong")
|
||||
self.add_error("id", error)
|
||||
raise error
|
||||
|
||||
def _validate_account_type(self):
|
||||
"""Validates whether the account is a correct debit or credit account.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
"""
|
||||
if "account" in self.errors:
|
||||
return
|
||||
if self.is_credit:
|
||||
print(self.data["account"])
|
||||
if not re.match("^([123489]|7[1234])", self.data["account"]):
|
||||
error = forms.ValidationError(
|
||||
pgettext("Accounting|",
|
||||
"This account is not for credit records."),
|
||||
code="not_credit")
|
||||
self.add_error("account", error)
|
||||
raise error
|
||||
else:
|
||||
if not re.match("^([1235689]|7[5678])", self.data["account"]):
|
||||
error = forms.ValidationError(
|
||||
pgettext("Accounting|",
|
||||
"This account is not for debit records."),
|
||||
code="not_debit")
|
||||
self.add_error("account", error)
|
||||
raise error
|
||||
|
||||
|
||||
class TransactionForm(forms.Form):
|
||||
"""A transaction form.
|
||||
|
||||
Attributes:
|
||||
txn_type (str): The transaction type.
|
||||
transaction (Transaction|None): The current transaction or None
|
||||
debit_records (list[RecordForm]): The debit records.
|
||||
credit_records (list[RecordForm]): The credit records.
|
||||
"""
|
||||
date = forms.DateField(
|
||||
required=True,
|
||||
error_messages={
|
||||
"invalid": pgettext("Accounting|", "This date is not valid.")
|
||||
})
|
||||
notes = forms.CharField(
|
||||
required=False,
|
||||
max_length=128,
|
||||
error_messages={
|
||||
"max_length": pgettext("Accounting|", "This notes is too long.")
|
||||
})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.txn_type = None
|
||||
self.transaction = None
|
||||
self.debit_records = []
|
||||
self.credit_records = []
|
||||
|
||||
def clean(self):
|
||||
"""Validates the form globally.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
"""
|
||||
self._validate_balance()
|
||||
|
||||
def _validate_balance(self):
|
||||
"""Validates whether the total amount of debit and credit records are
|
||||
consistent.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
"""
|
||||
if self.txn_type != "transfer":
|
||||
return
|
||||
if self.debit_total() == self.credit_total():
|
||||
return
|
||||
raise forms.ValidationError(pgettext(
|
||||
"Accounting|",
|
||||
"The total amount of debit and credit records are inconsistent."),
|
||||
code="balance")
|
||||
|
||||
def is_valid(self):
|
||||
if not super(TransactionForm, self).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):
|
||||
"""Returns the error message when the transaction is imbalanced.
|
||||
|
||||
Returns:
|
||||
str: 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"]
|
||||
if errors:
|
||||
return errors[0].message
|
||||
return None
|
||||
|
||||
def debit_total(self):
|
||||
"""Returns the total amount of the debit records.
|
||||
|
||||
Returns:
|
||||
int: The total amount of the credit records.
|
||||
"""
|
||||
return sum([int(x.data["amount"]) for x in self.debit_records
|
||||
if "amount" not in x.errors])
|
||||
|
||||
def credit_total(self):
|
||||
"""Returns the total amount of the credit records.
|
||||
|
||||
Returns:
|
||||
int: The total amount of the credit records.
|
||||
"""
|
||||
return sum([int(x.data["amount"]) for x in self.credit_records
|
||||
if "amount" not in x.errors])
|
@ -33,13 +33,13 @@ First written: 2020/7/23
|
||||
{% block content %}
|
||||
|
||||
<div class="btn-group btn-actions">
|
||||
<a class="btn btn-primary" role="button" href="{% if item.pk %}{% url_keep_return "accounting:transactions.show" "expense" item %}{% elif request.GET.r %}{{ request.GET.r }}{% else %}{% url "accounting:home" %}{% endif %}">
|
||||
<a class="btn btn-primary" role="button" href="{% if item.transaction %}{% url_keep_return "accounting:transactions.show" "expense" item.transaction %}{% elif request.GET.r %}{{ request.GET.r }}{% else %}{% url "accounting:home" %}{% endif %}">
|
||||
<i class="fas fa-chevron-circle-left"></i>
|
||||
{% trans "Back" context "Navigation|" as text %}{{ text|force_escape }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form id="txn-form" action="{% if item.pk %}{% url_keep_return "accounting:transactions.update" "expense" item %}{% else %}{% url_keep_return "accounting:transactions.store" "expense" %}{% endif %}" method="post">
|
||||
<form id="txn-form" action="{% if item.transaction %}{% url_keep_return "accounting:transactions.update" "expense" item.transaction %}{% else %}{% url_keep_return "accounting:transactions.store" "expense" %}{% endif %}" method="post">
|
||||
{% csrf_token %}
|
||||
{# TODO: To be done #}
|
||||
<input id="l10n-messages" type="hidden" value="{{ l10n_messages }}" />
|
||||
@ -52,8 +52,8 @@ First written: 2020/7/23
|
||||
<label for="txn-date">{% trans "Date:" context "Accounting|" as text %}{{ text|force_escape }}</label>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<input id="txn-date" class="form-control {% if errors|dict:"date" %} is-invalid {% endif %}" type="date" name="date" value="{{ item.date|date:"Y-m-d" }}" required="required" />
|
||||
<div id="txn-date-error" class="invalid-feedback">{{ errors|dict:"date"|default:"" }}</div>
|
||||
<input id="txn-date" class="form-control {% if item.date.errors %} is-invalid {% endif %}" type="date" name="date" value="{{ item.date.value }}" required="required" />
|
||||
<div id="txn-date-error" class="invalid-feedback">{{ item.date.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -64,32 +64,29 @@ First written: 2020/7/23
|
||||
<li id="debit-{{ forloop.counter }}" class="list-group-item d-flex justify-content-between draggable-record debit-record">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
{% if x.pk is not None %}
|
||||
<input type="hidden" name="debit-{{ forloop.counter }}-sn" value="{{ x.pk }}" />
|
||||
{% if x.id.value %}
|
||||
<input type="hidden" name="debit-{{ forloop.counter }}-id" value="{{ x.id.value }}" />
|
||||
{% endif %}
|
||||
<input id="debit-{{ forloop.counter }}-ord" class="debit-ord" type="hidden" name="debit-{{ forloop.counter }}-ord" value="{{ x.ord }}" />
|
||||
{% str_format "debit-{}-account" forloop.counter as field %}
|
||||
<select id="{{ field }}" class="form-control record-account debit-account {% if errors|dict:field %} is-invalid {% endif %}" name="{{ field }}">
|
||||
<input id="debit-{{ forloop.counter }}-ord" class="debit-ord" type="hidden" name="debit-{{ forloop.counter }}-ord" value="{{ x.ord.value }}" />
|
||||
<select id="debit-{{ forloop.counter }}-account" class="form-control record-account debit-account {% if x.account.errors %} is-invalid {% endif %}" name="debit-{{ forloop.counter }}-account">
|
||||
{% if x.account is not None %}
|
||||
<option value="{{ x.account.code }}" selected="selected">{{ x.account.code }} {{ x.account.title }}</option>
|
||||
<option value="{{ x.account.value|default:"" }}" selected="selected">{{ x.account.value|default:"" }} {{ x.account_title|default:"" }}</option>
|
||||
{% else %}
|
||||
<option value=""></option>
|
||||
{% endif %}
|
||||
<option value="">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</option>
|
||||
</select>
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<div id="debit-{{ forloop.counter }}-account-error" class="invalid-feedback">{{ x.account.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
{% str_format "debit-{}-summary" forloop.counter as field %}
|
||||
<input id="{{ field }}" class="form-control record-summary {% if errors|dict:field %} is-invalid {% endif %}" type="text" name="{{ field }}" value="{{ x.summary|default:"" }}" maxlength="128" />
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<input id="debit-{{ forloop.counter }}-summary" class="form-control record-summary {% if x.summary.errors %} is-invalid {% endif %}" type="text" name="debit-{{ forloop.counter }}-summary" value="{{ x.summary.value|default:"" }}" maxlength="128" />
|
||||
<div id="debit-{{ forloop.counter }}-summary-error" class="invalid-feedback">{{ x.summary.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% str_format "debit-{}-amount" forloop.counter as field %}
|
||||
<input id="{{ field }}" class="form-control record-amount debit-to-sum {% if errors|dict:field %} is-invalid {% endif %}" type="number" min="1" name="{{ field }}" value="{{ x.amount|default:"" }}" required="required" />
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<input id="debit-{{ forloop.counter }}-amount" class="form-control record-amount debit-to-sum {% if x.amount.errors %} is-invalid {% endif %}" type="number" min="1" name="debit-{{ forloop.counter }}-amount" value="{{ x.amount.value|default:"" }}" required="required" />
|
||||
<div id="debit-{{ forloop.counter }}-amount-error" class="invalid-feedback">{{ x.amount.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -136,8 +133,8 @@ First written: 2020/7/23
|
||||
<label for="txn-note">{% trans "Notes:" context "Accounting|" as text %}{{ text|force_escape }}</label>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<textarea id="txn-note" class="form-control {% if errors|dict:"notes" %} is-invalid {% endif %}" name="note">{{ item.notes|default:"" }}</textarea>
|
||||
<div id="txn-note-error" class="invalid-feedback">{{ errors|dict:"notes"|default:"" }}</div>
|
||||
<textarea id="txn-note" class="form-control {% if item.notes.errors %} is-invalid {% endif %}" name="notes">{{ item.notes.value|default:"" }}</textarea>
|
||||
<div id="txn-note-error" class="invalid-feedback">{{ item.notes.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -33,13 +33,13 @@ First written: 2020/7/23
|
||||
{% block content %}
|
||||
|
||||
<div class="btn-group btn-actions">
|
||||
<a class="btn btn-primary" role="button" href="{% if item.pk %}{% url_keep_return "accounting:transactions.show" "income" item %}{% elif request.GET.r %}{{ request.GET.r }}{% else %}{% url "accounting:home" %}{% endif %}">
|
||||
<a class="btn btn-primary" role="button" href="{% if item.transaction %}{% url_keep_return "accounting:transactions.show" "income" item.transaction %}{% elif request.GET.r %}{{ request.GET.r }}{% else %}{% url "accounting:home" %}{% endif %}">
|
||||
<i class="fas fa-chevron-circle-left"></i>
|
||||
{% trans "Back" context "Navigation|" as text %}{{ text|force_escape }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form id="txn-form" action="{% if item.pk %}{% url_keep_return "accounting:transactions.update" "income" item %}{% else %}{% url_keep_return "accounting:transactions.store" "income" %}{% endif %}" method="post">
|
||||
<form id="txn-form" action="{% if item.transaction %}{% url_keep_return "accounting:transactions.update" "income" item.transaction %}{% else %}{% url_keep_return "accounting:transactions.store" "income" %}{% endif %}" method="post">
|
||||
{% csrf_token %}
|
||||
{# TODO: To be done #}
|
||||
<input id="l10n-messages" type="hidden" value="{{ l10n_messages }}" />
|
||||
@ -52,8 +52,8 @@ First written: 2020/7/23
|
||||
<label for="txn-date">{% trans "Date:" context "Accounting|" as text %}{{ text|force_escape }}</label>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<input id="txn-date" class="form-control {% if errors|dict:"date" %} is-invalid {% endif %}" type="date" name="date" value="{{ item.date|date:"Y-m-d" }}" required="required" />
|
||||
<div id="txn-date-error" class="invalid-feedback">{{ errors|dict:"date"|default:"" }}</div>
|
||||
<input id="txn-date" class="form-control {% if item.date.errors %} is-invalid {% endif %}" type="date" name="date" value="{{ item.date.value }}" required="required" />
|
||||
<div id="txn-date-error" class="invalid-feedback">{{ item.date.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -64,32 +64,29 @@ First written: 2020/7/23
|
||||
<li id="credit-{{ forloop.counter }}" class="list-group-item d-flex justify-content-between draggable-record credit-record">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
{% if x.pk is not None %}
|
||||
<input type="hidden" name="credit-{{ forloop.counter }}-sn" value="{{ x.pk }}" />
|
||||
{% if x.id.value %}
|
||||
<input type="hidden" name="credit-{{ forloop.counter }}-id" value="{{ x.id.value }}" />
|
||||
{% endif %}
|
||||
<input id="credit-{{ forloop.counter }}-ord" class="credit-ord" type="hidden" name="credit-{{ forloop.counter }}-ord" value="{{ x.ord }}" />
|
||||
{% str_format "credit-{}-account" forloop.counter as field %}
|
||||
<select id="{{ field }}" class="form-control record-account credit-account {% if errors|dict:field %} is-invalid {% endif %}" name="{{ field }}">
|
||||
<input id="credit-{{ forloop.counter }}-ord" class="credit-ord" type="hidden" name="credit-{{ forloop.counter }}-ord" value="{{ x.ord.value }}" />
|
||||
<select id="credit-{{ forloop.counter }}-account" class="form-control record-account credit-account {% if x.account.errors %} is-invalid {% endif %}" name="credit-{{ forloop.counter }}-account">
|
||||
{% if x.account is not None %}
|
||||
<option value="{{ x.account.code }}" selected="selected">{{ x.account.code }} {{ x.account.title }}</option>
|
||||
<option value="{{ x.account.value|default:"" }}" selected="selected">{{ x.account.value|default:"" }} {{ x.account_title|default:"" }}</option>
|
||||
{% else %}
|
||||
<option value=""></option>
|
||||
{% endif %}
|
||||
<option value="">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</option>
|
||||
</select>
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<div id="credit-{{ forloop.counter }}-account-error" class="invalid-feedback">{{ x.account.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
{% str_format "credit-{}-summary" forloop.counter as field %}
|
||||
<input id="{{ field }}" class="form-control record-summary {% if errors|dict:field %} is-invalid {% endif %}" type="text" name="{{ field }}" value="{{ x.summary|default:"" }}" maxlength="128" />
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<input id="credit-{{ forloop.counter }}-summary" class="form-control record-summary {% if x.summary.errors %} is-invalid {% endif %}" type="text" name="credit-{{ forloop.counter }}-summary" value="{{ x.summary.value|default:"" }}" maxlength="128" />
|
||||
<div id="credit-{{ forloop.counter }}-summary-error" class="invalid-feedback">{{ x.summary.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% str_format "credit-{}-amount" forloop.counter as field %}
|
||||
<input id="{{ field }}" class="form-control record-amount credit-to-sum {% if errors|dict:field %} is-invalid {% endif %}" type="number" min="1" name="{{ field }}" value="{{ x.amount|default:"" }}" required="required" />
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<input id="credit-{{ forloop.counter }}-amount" class="form-control record-amount credit-to-sum {% if x.amount.errors %} is-invalid {% endif %}" type="number" min="1" name="credit-{{ forloop.counter }}-amount" value="{{ x.amount.value|default:"" }}" required="required" />
|
||||
<div id="credit-{{ forloop.counter }}-amount-error" class="invalid-feedback">{{ x.amount.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -136,8 +133,8 @@ First written: 2020/7/23
|
||||
<label for="txn-note">{% trans "Notes:" context "Accounting|" as text %}{{ text|force_escape }}</label>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<textarea id="txn-note" class="form-control {% if errors|dict:"notes" %} is-invalid {% endif %}" name="note">{{ item.notes|default:"" }}</textarea>
|
||||
<div id="txn-note-error" class="invalid-feedback">{{ errors|dict:"notes"|default:"" }}</div>
|
||||
<textarea id="txn-note" class="form-control {% if item.notes.errors %} is-invalid {% endif %}" name="notes">{{ item.notes.value|default:"" }}</textarea>
|
||||
<div id="txn-note-error" class="invalid-feedback">{{ item.notes.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -33,13 +33,13 @@ First written: 2020/7/23
|
||||
{% block content %}
|
||||
|
||||
<div class="btn-group btn-actions">
|
||||
<a class="btn btn-primary" role="button" href="{% if item.pk %}{% url_keep_return "accounting:transactions.show" "transfer" item %}{% elif request.GET.r %}{{ request.GET.r }}{% else %}{% url "accounting:home" %}{% endif %}">
|
||||
<a class="btn btn-primary" role="button" href="{% if item.transaction %}{% url_keep_return "accounting:transactions.show" "transfer" item.transaction %}{% elif request.GET.r %}{{ request.GET.r }}{% else %}{% url "accounting:home" %}{% endif %}">
|
||||
<i class="fas fa-chevron-circle-left"></i>
|
||||
{% trans "Back" context "Navigation|" as text %}{{ text|force_escape }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form id="txn-form" action="{% if item.pk %}{% url_keep_return "accounting:transactions.update" "transfer" item %}{% else %}{% url_keep_return "accounting:transactions.store" "transfer" %}{% endif %}" method="post">
|
||||
<form id="txn-form" action="{% if item.transaction %}{% url_keep_return "accounting:transactions.update" "transfer" item.transaction %}{% else %}{% url_keep_return "accounting:transactions.store" "transfer" %}{% endif %}" method="post">
|
||||
{% csrf_token %}
|
||||
{# TODO: To be done #}
|
||||
<input id="l10n-messages" type="hidden" value="{{ l10n_messages }}" />
|
||||
@ -55,8 +55,8 @@ First written: 2020/7/23
|
||||
<label for="txn-date">{% trans "Date:" context "Accounting|" as text %}{{ text|force_escape }}</label>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<input id="txn-date" class="form-control {% if errors|dict:"date" %} is-invalid {% endif %}" type="date" name="date" value="{{ item.date|date:"Y-m-d" }}" required="required" />
|
||||
<div id="txn-date-error" class="invalid-feedback">{{ errors|dict:"date"|default:"" }}</div>
|
||||
<input id="txn-date" class="form-control {% if item.date.errors %} is-invalid {% endif %}" type="date" name="date" value="{{ item.date.value }}" required="required" />
|
||||
<div id="txn-date-error" class="invalid-feedback">{{ item.date.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -70,32 +70,29 @@ First written: 2020/7/23
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% if x.pk is not None %}
|
||||
<input type="hidden" name="debit-{{ forloop.counter }}-sn" value="{{ x.pk }}" />
|
||||
{% if x.id.value %}
|
||||
<input type="hidden" name="debit-{{ forloop.counter }}-id" value="{{ x.id.value }}" />
|
||||
{% endif %}
|
||||
<input id="debit-{{ forloop.counter }}-ord" class="debit-ord" type="hidden" name="debit-{{ forloop.counter }}-ord" value="{{ x.ord }}" />
|
||||
{% str_format "debit-{}-account" forloop.counter as field %}
|
||||
<select id="{{ field }}" class="form-control record-account debit-account {% if errors|dict:field %} is-invalid {% endif %}" name="{{ field }}">
|
||||
<input id="debit-{{ forloop.counter }}-ord" class="debit-ord" type="hidden" name="debit-{{ forloop.counter }}-ord" value="{{ x.ord.value }}" />
|
||||
<select id="debit-{{ forloop.counter }}-account" class="form-control record-account debit-account {% if x.account.errors %} is-invalid {% endif %}" name="debit-{{ forloop.counter }}-account">
|
||||
{% if x.account is not None %}
|
||||
<option value="{{ x.account.code }}" selected="selected">{{ x.account.code }} {{ x.account.title }}</option>
|
||||
<option value="{{ x.account.value|default:"" }}" selected="selected">{{ x.account.value|default:"" }} {{ x.account_title|default:"" }}</option>
|
||||
{% else %}
|
||||
<option value=""></option>
|
||||
{% endif %}
|
||||
<option value="">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</option>
|
||||
</select>
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<div id="debit-{{ forloop.counter }}-account-error" class="invalid-feedback">{{ x.account.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
{% str_format "debit-{}-summary" forloop.counter as field %}
|
||||
<input id="{{ field }}" class="form-control record-summary {% if errors|dict:field %} is-invalid {% endif %}" type="text" name="{{ field }}" value="{{ x.summary|default:"" }}" maxlength="128" />
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<input id="debit-{{ forloop.counter }}-summary" class="form-control record-summary {% if x.summary.errors %} is-invalid {% endif %}" type="text" name="debit-{{ forloop.counter }}-summary" value="{{ x.summary.value|default:"" }}" maxlength="128" />
|
||||
<div id="debit-{{ forloop.counter }}-summary-error" class="invalid-feedback">{{ x.summary.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
{% str_format "debit-{}-amount" forloop.counter as field %}
|
||||
<input id="{{ field }}" class="form-control record-amount debit-to-sum {% if errors|dict:field %} is-invalid {% endif %}" type="number" min="1" name="{{ field }}" value="{{ x.amount|default:"" }}" required="required" />
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<input id="debit-{{ forloop.counter }}-amount" class="form-control record-amount debit-to-sum {% if x.amount.errors %} is-invalid {% endif %}" type="number" min="1" name="debit-{{ forloop.counter }}-amount" value="{{ x.amount.value|default:"" }}" required="required" />
|
||||
<div id="debit-{{ forloop.counter }}-amount-error" class="invalid-feedback">{{ x.amount.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -119,11 +116,11 @@ First written: 2020/7/23
|
||||
</button>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div id="debit-total-row" class="d-flex justify-content-between align-items-center form-control {% if errors|dict:"balance" %} is-invalid {% endif %} balance-row">
|
||||
<div id="debit-total-row" class="d-flex justify-content-between align-items-center form-control {% if item.balance_error %} is-invalid {% endif %} balance-row">
|
||||
{% trans "Total" context "Accounting|" as text %}{{ text|force_escape }}
|
||||
<span id="debit-total" class="amount">{{ item.debit_total }}</span>
|
||||
</div>
|
||||
<div id="debit-total-error" class="invalid-feedback balance-error">{{ errors|dict:"balance"|default:"" }}</div>
|
||||
<div id="debit-total-error" class="invalid-feedback balance-error">{{ item.balance_error|default:"" }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -137,32 +134,29 @@ First written: 2020/7/23
|
||||
<div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% if x.pk is not None %}
|
||||
<input type="hidden" name="credit-{{ forloop.counter }}-sn" value="{{ x.pk }}" />
|
||||
{% if x.id.value %}
|
||||
<input type="hidden" name="credit-{{ forloop.counter }}-id" value="{{ x.id.value }}" />
|
||||
{% endif %}
|
||||
<input id="credit-{{ forloop.counter }}-ord" class="credit-ord" type="hidden" name="credit-{{ forloop.counter }}-ord" value="{{ x.ord }}" />
|
||||
{% str_format "credit-{}-account" forloop.counter as field %}
|
||||
<select id="{{ field }}" class="form-control record-account credit-account {% if errors|dict:field %} is-invalid {% endif %}" name="{{ field }}">
|
||||
<input id="credit-{{ forloop.counter }}-ord" class="credit-ord" type="hidden" name="credit-{{ forloop.counter }}-ord" value="{{ x.ord.value }}" />
|
||||
<select id="credit-{{ forloop.counter }}-account" class="form-control record-account credit-account {% if x.account.errors %} is-invalid {% endif %}" name="credit-{{ forloop.counter }}-account">
|
||||
{% if x.account is not None %}
|
||||
<option value="{{ x.account.code }}" selected="selected">{{ x.account.code }} {{ x.account.title }}</option>
|
||||
<option value="{{ x.account.value|default:"" }}" selected="selected">{{ x.account.value|default:"" }} {{ x.account_title|default:"" }}</option>
|
||||
{% else %}
|
||||
<option value=""></option>
|
||||
{% endif %}
|
||||
<option value="">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</option>
|
||||
</select>
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<div id="credit-{{ forloop.counter }}-account-error" class="invalid-feedback">{{ x.account.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
{% str_format "credit-{}-summary" forloop.counter as field %}
|
||||
<input id="{{ field }}" class="form-control record-summary {% if errors|dict:field %} is-invalid {% endif %}" type="text" name="{{ field }}" value="{{ x.summary|default:"" }}" maxlength="128" />
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<input id="credit-{{ forloop.counter }}-summary" class="form-control record-summary {% if x.summary.errors %} is-invalid {% endif %}" type="text" name="credit-{{ forloop.counter }}-summary" value="{{ x.summary.value|default:"" }}" maxlength="128" />
|
||||
<div id="credit-{{ forloop.counter }}-summary-error" class="invalid-feedback">{{ x.summary.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
{% str_format "credit-{}-amount" forloop.counter as field %}
|
||||
<input id="{{ field }}" class="form-control record-amount credit-to-sum {% if errors|dict:field %} is-invalid {% endif %}" type="number" min="1" name="{{ field }}" value="{{ x.amount|default:"" }}" required="required" />
|
||||
<div id="{{ field }}-error" class="invalid-feedback">{{ errors|dict:field|default:"" }}</div>
|
||||
<input id="credit-{{ forloop.counter }}-amount" class="form-control record-amount credit-to-sum {% if x.amount.errors %} is-invalid {% endif %}" type="number" min="1" name="credit-{{ forloop.counter }}-amount" value="{{ x.amount.value|default:"" }}" required="required" />
|
||||
<div id="credit-{{ forloop.counter }}-amount-error" class="invalid-feedback">{{ x.amount.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -186,11 +180,11 @@ First written: 2020/7/23
|
||||
</button>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<div id="credit-total-row" class="d-flex justify-content-between align-items-center form-control {% if errors|dict:"balance" %} is-invalid {% endif %} balance-row">
|
||||
<div id="credit-total-row" class="d-flex justify-content-between align-items-center form-control {% if item.balance_error %} is-invalid {% endif %} balance-row">
|
||||
{% trans "Total" context "Accounting|" as text %}{{ text|force_escape }}
|
||||
<span id="credit-total" class="amount">{{ item.credit_total }}</span>
|
||||
</div>
|
||||
<div id="credit-total-error" class="invalid-feedback balance-error">{{ errors|dict:"balance"|default:"" }}</div>
|
||||
<div id="credit-total-error" class="invalid-feedback balance-error">{{ item.balance_error|default:"" }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -201,8 +195,8 @@ First written: 2020/7/23
|
||||
<label for="txn-note">{% trans "Notes:" context "Accounting|" as text %}{{ text|force_escape }}</label>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<textarea id="txn-note" class="form-control {% if errors|dict:"notes" %} is-invalid {% endif %}" name="note">{{ item.notes|default:"" }}</textarea>
|
||||
<div id="txn-note-error" class="invalid-feedback">{{ errors|dict:"notes"|default:"" }}</div>
|
||||
<textarea id="txn-note" class="form-control {% if item.notes.errors %} is-invalid {% endif %}" name="notes">{{ item.notes.value|default:"" }}</textarea>
|
||||
<div id="txn-note-error" class="invalid-feedback">{{ item.notes.errors.0|default:"" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -21,13 +21,13 @@
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q, Sum, Case, When, F, Count, Max, Min
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import pgettext, gettext_noop
|
||||
from django.utils.translation import pgettext
|
||||
|
||||
from accounting.models import Account, Transaction, Record
|
||||
from .forms import TransactionForm, RecordForm
|
||||
from .models import Account, Transaction, Record
|
||||
from mia_core.period import Period
|
||||
from mia_core.status import retrieve_status
|
||||
from mia_core.utils import new_pk
|
||||
@ -289,7 +289,7 @@ def find_order_holes(records):
|
||||
.filter(~(Q(max=F("count")) & Q(min=1)))] +\
|
||||
[x["date"] for x in Transaction.objects
|
||||
.values("date", "ord")
|
||||
.annotate(count=Count("sn"))
|
||||
.annotate(count=Count("pk"))
|
||||
.filter(~Q(count=1))]
|
||||
for record in records:
|
||||
record.has_order_hole = record.transaction.date in holes
|
||||
@ -313,7 +313,7 @@ def fill_transaction_from_form(transaction, form):
|
||||
}
|
||||
for key in form.keys():
|
||||
m = re.match(
|
||||
"^(debit|credit)-([1-9][0-9]*)-(sn|ord|account|summary|amount)$",
|
||||
"^(debit|credit)-([1-9][0-9]*)-(id|ord|account|summary|amount)$",
|
||||
key)
|
||||
if m is not None:
|
||||
rec_type = m.group(1)
|
||||
@ -328,8 +328,8 @@ def fill_transaction_from_form(transaction, form):
|
||||
ord=no,
|
||||
is_credit=(rec_type == "credit"),
|
||||
transaction=transaction)
|
||||
if F"{rec_type}-{no}-sn" in form:
|
||||
record.pk = form[F"{rec_type}-{no}-sn"]
|
||||
if F"{rec_type}-{no}-id" in form:
|
||||
record.pk = form[F"{rec_type}-{no}-id"]
|
||||
if F"{rec_type}-{no}-account" in form:
|
||||
record.account = Account(code=form[F"{rec_type}-{no}-account"])
|
||||
if F"{rec_type}-{no}-summary" in form:
|
||||
@ -340,22 +340,6 @@ def fill_transaction_from_form(transaction, form):
|
||||
transaction.records = records
|
||||
|
||||
|
||||
def fill_transaction_from_previous_form(request, transaction):
|
||||
"""Fills the transaction from the previously-stored form.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request
|
||||
transaction (Transaction): The transaction.
|
||||
"""
|
||||
status = retrieve_status(request)
|
||||
if status is None:
|
||||
return
|
||||
if "form" not in status:
|
||||
return
|
||||
form = status["form"]
|
||||
fill_transaction_from_form(transaction, form)
|
||||
|
||||
|
||||
def sort_form_transaction_records(form):
|
||||
"""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.
|
||||
@ -370,7 +354,7 @@ def sort_form_transaction_records(form):
|
||||
}
|
||||
for key in form.keys():
|
||||
m = re.match(
|
||||
"^(debit|credit)-([1-9][0-9]*)-(sn|ord|account|summary|amount)",
|
||||
"^(debit|credit)-([1-9][0-9]*)-(id|ord|account|summary|amount)",
|
||||
key)
|
||||
if m is None:
|
||||
continue
|
||||
@ -396,41 +380,122 @@ def sort_form_transaction_records(form):
|
||||
old_no = record_no[record_type][i]
|
||||
no = i + 1
|
||||
new_form[F"{record_type}-{no}-ord"] = no
|
||||
for attr in ["sn", "account", "summary", "amount"]:
|
||||
for attr in ["id", "account", "summary", "amount"]:
|
||||
if F"{record_type}-{old_no}-{attr}" in form:
|
||||
new_form[F"{record_type}-{no}-{attr}"]\
|
||||
= form[F"{record_type}-{old_no}-{attr}"]
|
||||
# Purges the old form and fills it with the new form
|
||||
for x in [x for x in form.keys() if re.match(
|
||||
"^(debit|credit)-([1-9][0-9]*)-(sn|ord|account|summary|amount)",
|
||||
"^(debit|credit)-([1-9][0-9]*)-(id|ord|account|summary|amount)",
|
||||
x)]:
|
||||
del form[x]
|
||||
for key in new_form.keys():
|
||||
form[key] = new_form[key]
|
||||
|
||||
|
||||
def validate_account_code(record):
|
||||
"""Validates the account code.
|
||||
def make_transaction_form_from_model(transaction, exists):
|
||||
"""Converts a transaction data model to a transaction form.
|
||||
|
||||
Args:
|
||||
record (Record): The accounting record.
|
||||
transaction (Transaction): The transaction data model.
|
||||
exists (bool): Whether the current transaction exists.
|
||||
|
||||
Exceptions:
|
||||
ValidationError: Thrown when validation fails.
|
||||
Returns:
|
||||
TransactionForm: The transaction form.
|
||||
"""
|
||||
if record.account.code is None:
|
||||
raise ValidationError(gettext_noop(
|
||||
"Please select the account."))
|
||||
if record.account.code == "":
|
||||
raise ValidationError(gettext_noop(
|
||||
"Please select the account."))
|
||||
try:
|
||||
record.account = Account.objects.get(code=record.account.code)
|
||||
except Account.DoesNotExist:
|
||||
raise ValidationError(gettext_noop(
|
||||
"This account does not exist."))
|
||||
child_account = Account.objects.filter(
|
||||
code__startswith=record.account.code).first()
|
||||
if child_account is not None:
|
||||
raise ValidationError(gettext_noop(
|
||||
"You cannot choose a parent account."))
|
||||
transaction_form = TransactionForm(
|
||||
{x: str(getattr(transaction, x)) for x in ["date", "notes"]
|
||||
if getattr(transaction, x) is not None})
|
||||
transaction_form.transaction = transaction if exists else None
|
||||
for record in transaction.records:
|
||||
data = {x: getattr(record, x)
|
||||
for x in ["summary", "amount"]
|
||||
if getattr(record, x) is not None}
|
||||
data["id"] = record.pk
|
||||
try:
|
||||
data["account"] = record.account.code
|
||||
except AttributeError:
|
||||
pass
|
||||
record_form = RecordForm(data)
|
||||
record_form.transaction = transaction_form.transaction
|
||||
record_form.is_credit = record.is_credit
|
||||
if record.is_credit:
|
||||
transaction_form.credit_records.append(record_form)
|
||||
else:
|
||||
transaction_form.debit_records.append(record_form)
|
||||
return transaction_form
|
||||
|
||||
|
||||
def make_transaction_form_from_post(post, txn_type, transaction):
|
||||
"""Converts the POSTed data to a transaction form.
|
||||
|
||||
Args:
|
||||
post (dict[str]): The POSTed data.
|
||||
txn_type (str): The transaction type.
|
||||
transaction (Transaction|None): The current transaction, or None
|
||||
if there is no current transaction.
|
||||
|
||||
Returns:
|
||||
TransactionForm: The transaction form.
|
||||
"""
|
||||
transaction_form = TransactionForm(
|
||||
{x: post[x] for x in ("date", "notes") if x in post})
|
||||
transaction_form.transaction = transaction
|
||||
transaction_form.txn_type = txn_type
|
||||
# The records
|
||||
max_no = {
|
||||
"debit": 0,
|
||||
"credit": 0,
|
||||
}
|
||||
for key in post.keys():
|
||||
m = re.match(
|
||||
"^(debit|credit)-([1-9][0-9]*)-(id|ord|account|summary|amount)$",
|
||||
key)
|
||||
if m is not None:
|
||||
rec_type = m.group(1)
|
||||
no = int(m.group(2))
|
||||
if max_no[rec_type] < no:
|
||||
max_no[rec_type] = no
|
||||
if max_no["debit"] == 0:
|
||||
max_no["debit"] = 1
|
||||
if max_no["credit"] == 0:
|
||||
max_no["credit"] = 1
|
||||
for rec_type in max_no.keys():
|
||||
records = []
|
||||
is_credit = (rec_type == "credit")
|
||||
for i in range(max_no[rec_type]):
|
||||
no = i + 1
|
||||
record = RecordForm(
|
||||
{x: post[F"{rec_type}-{no}-{x}"]
|
||||
for x in ["id", "account", "summary", "amount"]
|
||||
if F"{rec_type}-{no}-{x}" in post})
|
||||
record.transaction = transaction_form.transaction
|
||||
record.is_credit = is_credit
|
||||
records.append(record)
|
||||
if rec_type == "debit":
|
||||
transaction_form.debit_records = records
|
||||
else:
|
||||
transaction_form.credit_records = records
|
||||
return transaction_form
|
||||
|
||||
|
||||
def make_transaction_form_from_status(request, txn_type, transaction):
|
||||
"""Converts the previously-stored status to a transaction form.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The request.
|
||||
txn_type (str): The transaction type.
|
||||
transaction (Transaction|None): The current transaction, or None
|
||||
if there is no current transaction.
|
||||
|
||||
Returns:
|
||||
TransactionForm: The transaction form, or None if there is no
|
||||
previously-stored status.
|
||||
"""
|
||||
status = retrieve_status(request)
|
||||
if status is None:
|
||||
return None
|
||||
if "form" not in status:
|
||||
return
|
||||
return make_transaction_form_from_post(
|
||||
status["form"], txn_type, transaction)
|
||||
|
67
accounting/validators.py
Normal file
67
accounting/validators.py
Normal file
@ -0,0 +1,67 @@
|
||||
# The core application of the Mia project.
|
||||
# by imacat <imacat@mail.imacat.idv.tw>, 2020/8/1
|
||||
|
||||
# Copyright (c) 2020 imacat.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""The validators of the Mia core application.
|
||||
|
||||
"""
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import pgettext
|
||||
|
||||
from .models import Account, Record
|
||||
|
||||
|
||||
def validate_record_id(value):
|
||||
"""Validates the record ID.
|
||||
|
||||
Args:
|
||||
value (str): The record ID.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
"""
|
||||
try:
|
||||
Record.objects.get(pk=value)
|
||||
except Record.DoesNotExist:
|
||||
raise ValidationError(
|
||||
pgettext("Accounting|", "This record does not exists."),
|
||||
code="not_exist")
|
||||
|
||||
|
||||
def validate_record_account_code(value):
|
||||
"""Validates an account code.
|
||||
|
||||
Args:
|
||||
value (str): The account code.
|
||||
|
||||
Raises:
|
||||
ValidationError: When the validation fails.
|
||||
"""
|
||||
try:
|
||||
Account.objects.get(code=value)
|
||||
except Account.DoesNotExist:
|
||||
raise ValidationError(
|
||||
pgettext("Accounting|", "This account does not exist."),
|
||||
code="not_exist")
|
||||
child = Account.objects.filter(
|
||||
Q(code__startswith=value),
|
||||
~Q(code=value),
|
||||
).first()
|
||||
if child is not None:
|
||||
raise ValidationError(
|
||||
pgettext("Accounting|", "You cannot select a parent account."),
|
||||
code="parent_account")
|
@ -21,7 +21,6 @@
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Sum, Case, When, F, Q
|
||||
from django.db.models.functions import TruncMonth, Coalesce
|
||||
from django.shortcuts import render
|
||||
@ -40,8 +39,8 @@ from mia_core.utils import Pagination, get_multi_lingual_search, UrlBuilder, \
|
||||
from .models import Record, Transaction, Account, RecordSummary
|
||||
from .utils import ReportUrl, get_cash_accounts, get_ledger_accounts, \
|
||||
find_imbalanced, find_order_holes, fill_transaction_from_form, \
|
||||
sort_form_transaction_records, fill_transaction_from_previous_form, \
|
||||
validate_account_code
|
||||
sort_form_transaction_records, make_transaction_form_from_status, \
|
||||
make_transaction_form_from_model, make_transaction_form_from_post
|
||||
|
||||
|
||||
@method_decorator(require_GET, name="dispatch")
|
||||
@ -823,15 +822,18 @@ def transaction_edit(request, txn_type, transaction=None):
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
"""
|
||||
if transaction is None:
|
||||
transaction = Transaction()
|
||||
fill_transaction_from_previous_form(request, transaction)
|
||||
if len(transaction.debit_records) == 0:
|
||||
transaction.records.append(Record(ord=1, is_credit=False))
|
||||
if len(transaction.credit_records) == 0:
|
||||
transaction.records.append(Record(ord=1, is_credit=True))
|
||||
form = make_transaction_form_from_status(request, txn_type, transaction)
|
||||
if form is None:
|
||||
exists = transaction is not None
|
||||
if transaction is None:
|
||||
transaction = Transaction(date=timezone.localdate())
|
||||
if len(transaction.debit_records) == 0:
|
||||
transaction.records.append(Record(ord=1, is_credit=False))
|
||||
if len(transaction.credit_records) == 0:
|
||||
transaction.records.append(Record(ord=1, is_credit=True))
|
||||
form = make_transaction_form_from_model(transaction, exists)
|
||||
return render(request, F"accounting/transactions/{txn_type}/form.html", {
|
||||
"item": transaction,
|
||||
"item": form,
|
||||
})
|
||||
|
||||
|
||||
@ -848,61 +850,24 @@ def transaction_store(request, txn_type, transaction=None):
|
||||
Returns:
|
||||
HttpResponse: The response.
|
||||
"""
|
||||
if transaction is None:
|
||||
transaction = Transaction()
|
||||
form = request.POST.dict()
|
||||
strip_form(form)
|
||||
sort_form_transaction_records(form)
|
||||
fill_transaction_from_form(transaction, form)
|
||||
errors = {}
|
||||
try:
|
||||
transaction.full_clean(exclude=["sn", "created_by", "updated_by"])
|
||||
except ValidationError as e:
|
||||
errors = e.message_dict
|
||||
records = {
|
||||
"debit": transaction.debit_records,
|
||||
"credit": transaction.credit_records,
|
||||
}
|
||||
for record_type in records.keys():
|
||||
no = 0
|
||||
for x in records[record_type]:
|
||||
no = no + 1
|
||||
try:
|
||||
x.full_clean(exclude=[
|
||||
"sn", "transaction", "account", "created_by", "updated_by",
|
||||
])
|
||||
except ValidationError as e:
|
||||
for key in e.message_dict:
|
||||
errors[F"{record_type}-{no}-{key}"] = e.message_dict[key]
|
||||
# Validates the account
|
||||
try:
|
||||
validate_account_code(x)
|
||||
except ValidationError as e:
|
||||
errors[F"{record_type}-{no}-account"] = e.message
|
||||
# Validates the transaction
|
||||
if x.transaction is None:
|
||||
x.transaction = transaction
|
||||
if transaction.pk is None:
|
||||
if x.transaction.pk is not None:
|
||||
errors[F"{record_type}-{no}-transaction"] = gettext_noop(
|
||||
"This record is not of the same transaction.")
|
||||
else:
|
||||
if x.transaction.pk is None:
|
||||
pass
|
||||
elif x.transaction.pk != transaction.pk:
|
||||
errors[F"{record_type}-{no}-transaction"] = gettext_noop(
|
||||
"This record is not of the same transaction.")
|
||||
if len(errors) > 0:
|
||||
if transaction.pk is None:
|
||||
post = request.POST.dict()
|
||||
strip_form(post)
|
||||
sort_form_transaction_records(post)
|
||||
form = make_transaction_form_from_post(post, txn_type, transaction)
|
||||
if not form.is_valid():
|
||||
if transaction is None:
|
||||
url = reverse("accounting:transactions.create", args=(txn_type,))
|
||||
else:
|
||||
url = reverse(
|
||||
"accounting:transactions.edit", args=(txn_type, transaction))
|
||||
return error_redirect(
|
||||
request,
|
||||
str(UrlBuilder(url).add("r", request.GET.get("r"))),
|
||||
form,
|
||||
errors)
|
||||
str(UrlBuilder(url).set("r", request.GET.get("r"))),
|
||||
post)
|
||||
if transaction is None:
|
||||
transaction = Transaction()
|
||||
fill_transaction_from_form(transaction, post)
|
||||
# TODO: Stores the data
|
||||
return success_redirect(
|
||||
request,
|
||||
str(UrlBuilder(reverse("accounting:transactions.show",
|
||||
|
@ -22,7 +22,7 @@ import random
|
||||
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from mia_core.utils import UrlBuilder
|
||||
from .utils import UrlBuilder
|
||||
|
||||
|
||||
def success_redirect(request, url, success):
|
||||
@ -39,10 +39,10 @@ def success_redirect(request, url, success):
|
||||
HttpResponseRedirect: The redirect response.
|
||||
"""
|
||||
id = _store(request, {"success": success})
|
||||
return HttpResponseRedirect(str(UrlBuilder(url).add("s", id)))
|
||||
return HttpResponseRedirect(str(UrlBuilder(url).set("s", id)))
|
||||
|
||||
|
||||
def error_redirect(request, url, form, errors_by_field):
|
||||
def error_redirect(request, url, form):
|
||||
"""Redirects to a specific URL on error, with the status ID appended
|
||||
as the query parameter "s". The status will be loaded with the
|
||||
retrieve_status template tag.
|
||||
@ -57,8 +57,8 @@ def error_redirect(request, url, form, errors_by_field):
|
||||
Returns:
|
||||
HttpResponseRedirect: The redirect response.
|
||||
"""
|
||||
id = _store(request, {"form": form, "errors_by_field": errors_by_field})
|
||||
return HttpResponseRedirect(str(UrlBuilder(url).add("s", id)))
|
||||
id = _store(request, {"form": form})
|
||||
return HttpResponseRedirect(str(UrlBuilder(url).set("s", id)))
|
||||
|
||||
|
||||
def retrieve_status(request):
|
||||
|
Loading…
Reference in New Issue
Block a user