dded the account_store() view in the accounting application.

This commit is contained in:
依瑪貓 2020-08-09 16:22:51 +08:00
parent 7c218cbc76
commit 00ee0cc3bb
4 changed files with 163 additions and 6 deletions

View File

@ -22,6 +22,8 @@ import re
from django import forms
from django.core.validators import RegexValidator
from django.db.models import Q, Max
from django.db.models.functions import Length
from django.utils.translation import gettext as _
from .models import Account, Record
@ -317,7 +319,6 @@ class AccountForm(forms.Form):
RegexValidator(
regex="^[1-9]+$",
message=_("You can only use numbers 1-9 in the code.")),
validate_account_code,
])
title = forms.CharField(
max_length=128,
@ -328,6 +329,7 @@ class AccountForm(forms.Form):
def __init__(self, *args, **kwargs):
super(AccountForm, self).__init__(*args, **kwargs)
self.account = None
@property
def parent(self):
@ -335,3 +337,109 @@ class AccountForm(forms.Form):
if code is None or len(code) < 2:
return None
return Account.objects.get(code=code[:-1])
def clean(self):
"""Validates the form globally.
Raises:
ValidationError: When the validation fails.
"""
errors = []
validators = [self._validate_code_not_under_myself,
self._validate_code_unique,
self._validate_code_parent_exists,
self._validate_code_descendant_code_size]
for validator in validators:
try:
validator()
except forms.ValidationError as e:
errors.append(e)
if errors:
raise forms.ValidationError(errors)
def _validate_code_not_under_myself(self):
"""Validates whether the code is under itself.
Raises:
ValidationError: When the validation fails.
"""
if self.account is None:
return
if "code" not in self.data:
return
if self.data["code"] == self.account.code:
return
if not self.data["code"].startswith(self.account.code):
return
error = forms.ValidationError(
_("You cannot set the code under itself."),
code="not_under_myself")
self.add_error("code", error)
raise error
def _validate_code_unique(self):
"""Validates whether the code is unique.
Raises:
ValidationError: When the validation fails.
"""
if "code" not in self.data:
return
try:
if self.account is None:
Account.objects.get(code=self.data["code"])
else:
Account.objects.get(Q(code=self.data["code"]),
~Q(pk=self.account.pk))
except Account.DoesNotExist:
return
error = forms.ValidationError(_("This code is already in use."),
code="code_unique")
self.add_error("code", error)
raise error
def _validate_code_parent_exists(self):
"""Validates whether the parent account exists.
Raises:
ValidationError: When the validation fails.
"""
if "code" not in self.data:
return
if len(self.data["code"]) < 2:
return
try:
Account.objects.get(code=self.data["code"][:-1])
except Account.DoesNotExist:
error = forms.ValidationError(
_("The parent account of this code does not exist."),
code="code_unique")
self.add_error("code", error)
raise error
return
def _validate_code_descendant_code_size(self):
"""Validates whether the codes of the descendants will be too long.
Raises:
ValidationError: When the validation fails.
"""
if "code" not in self.data:
return
if self.account is None:
return
cur_max_len = Account.objects\
.filter(Q(code__startswith=self.account.code),
~Q(pk=self.account.pk))\
.aggregate(max_len=Max(Length("code")))["max_len"]
if cur_max_len is None:
return True
new_max_len = cur_max_len - len(self.account.code)\
+ len(self.data["code"])
if new_max_len <= 5:
return
error = forms.ValidationError(
_("The descendant account codes will be too long (max. 5)."),
code="descendant_code_size")
self.add_error("code", error)
raise error

View File

@ -24,7 +24,8 @@ from django.db import models, transaction
from django.db.models import Q
from django.urls import reverse
from mia_core.utils import get_multi_lingual_attr, set_multi_lingual_attr
from mia_core.utils import get_multi_lingual_attr, set_multi_lingual_attr, \
new_pk
class Account(DirtyFieldsMixin, models.Model):
@ -70,6 +71,19 @@ class Account(DirtyFieldsMixin, models.Model):
"""Returns the string representation of this account."""
return self.code.__str__() + " " + self.title
def save(self, current_user=None, force_insert=False, force_update=False,
using=None, update_fields=None):
self.parent = None if len(self.code) == 1\
else Account.objects.get(code=self.code[:-1])
if self.pk is None:
self.pk = new_pk(Account)
self.created_by = current_user
self.updated_by = current_user
with transaction.atomic():
super(Account, self).save(
force_insert=force_insert, force_update=force_update,
using=using, update_fields=update_fields)
class Meta:
db_table = "accounting_accounts"

View File

@ -94,9 +94,8 @@ urlpatterns = [
views.AccountListView.as_view(), name="accounts"),
path("accounts/create",
views.account_form, name="accounts.create"),
# TODO: To be done
path("accounts/store",
mia_core_views.todo, name="accounts.store"),
views.account_store, name="accounts.store"),
path("api/accounts",
views.api_account_list, name="api.accounts"),
path("api/accounts/options",
@ -105,9 +104,8 @@ urlpatterns = [
views.AccountView.as_view(), name="accounts.detail"),
path("accounts/<account:account>/edit",
views.account_form, name="accounts.edit"),
# TODO: To be done
path("accounts/<account:account>/update",
mia_core_views.todo, name="accounts.update"),
views.account_store, name="accounts.update"),
# TODO: To be done
path("accounts/<account:account>/delete",
mia_core_views.todo, name="accounts.delete"),

View File

@ -1053,11 +1053,48 @@ def account_form(request, account=None):
})
else:
form = AccountForm()
form.account = account
return render(request, "accounting/account_form.html", {
"form": form,
})
@require_POST
@login_required
def account_store(request, account=None):
"""The view to edit an accounting transaction.
Args:
request (HttpRequest): The request.
account (Account): The account.
Returns:
HttpResponseRedirect: The response.
"""
post = request.POST.dict()
strip_post(post)
form = AccountForm(post)
form.account = account
if not form.is_valid():
if account is None:
url = reverse("accounting:accounts.create")
else:
url = reverse("accounting:accounts.edit", args=(account,))
return stored_post.error_redirect(request, url, post)
if account is None:
account = Account()
account.code = form["code"].value()
account.title = form["title"].value()
if not account.is_dirty():
message = gettext_noop("This account was not modified.")
else:
account.save(current_user=request.user)
message = gettext_noop("This account was saved successfully.")
messages.success(request, message)
return HttpResponseRedirect(reverse("accounting:accounts.detail",
args=(account,)))
@require_GET
@login_required
def api_account_list(request):