130 lines
4.6 KiB
Python
130 lines
4.6 KiB
Python
# The Mia! Accounting Flask Project.
|
|
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/1
|
|
|
|
# Copyright (c) 2023 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 for the account management.
|
|
|
|
"""
|
|
import sqlalchemy as sa
|
|
from flask_wtf import FlaskForm
|
|
from wtforms import StringField, BooleanField
|
|
from wtforms.validators import DataRequired, ValidationError
|
|
|
|
from accounting.database import db, user_utils
|
|
from accounting.locale import lazy_gettext
|
|
from accounting.models import BaseAccount, Account
|
|
from accounting.utils.random_id import new_id
|
|
from accounting.utils.strip_text import strip_text
|
|
|
|
|
|
class BaseAccountExists:
|
|
"""The validator to check if the base account code exists."""
|
|
|
|
def __call__(self, form: FlaskForm, field: StringField) -> None:
|
|
if field.data == "":
|
|
return
|
|
if db.session.get(BaseAccount, field.data) is None:
|
|
raise ValidationError(lazy_gettext(
|
|
"The base account does not exist."))
|
|
|
|
|
|
class AccountForm(FlaskForm):
|
|
"""The form to create or edit an account."""
|
|
base_code = StringField(
|
|
filters=[strip_text],
|
|
validators=[
|
|
DataRequired(lazy_gettext("Please select the base account.")),
|
|
BaseAccountExists()])
|
|
"""The code of the base account."""
|
|
title = StringField(
|
|
filters=[strip_text],
|
|
validators=[DataRequired(lazy_gettext("Please fill in the title"))])
|
|
"""The title."""
|
|
is_offset_needed = BooleanField()
|
|
"""Whether the the entries of this account need offsets."""
|
|
|
|
def populate_obj(self, obj: Account) -> None:
|
|
"""Populates the form data into an account object.
|
|
|
|
:param obj: The account object.
|
|
:return: None.
|
|
"""
|
|
is_new: bool = obj.id is None
|
|
prev_base_code: str | None = obj.base_code
|
|
if is_new:
|
|
obj.id = new_id(Account)
|
|
obj.base_code = self.base_code.data
|
|
if prev_base_code != self.base_code.data:
|
|
last_same_base: Account = Account.query\
|
|
.filter(Account.base_code == self.base_code.data)\
|
|
.order_by(Account.base_code.desc()).first()
|
|
obj.no = 1 if last_same_base is None else last_same_base.no + 1
|
|
obj.title = self.title.data
|
|
obj.is_offset_needed = self.is_offset_needed.data
|
|
if is_new:
|
|
current_user_pk: int = user_utils.get_pk(user_utils.current_user)
|
|
obj.created_by_id = current_user_pk
|
|
obj.updated_by_id = current_user_pk
|
|
if prev_base_code is not None \
|
|
and prev_base_code != self.base_code.data:
|
|
setattr(self, "__post_update",
|
|
lambda: sort_accounts_in(prev_base_code, obj.id))
|
|
|
|
def post_update(self, obj) -> None:
|
|
"""The post-processing after the update.
|
|
|
|
:return: None
|
|
"""
|
|
current_user_pk: int = user_utils.get_pk(user_utils.current_user)
|
|
obj.updated_by_id = current_user_pk
|
|
obj.updated_at = sa.func.now()
|
|
if hasattr(self, "__post_update"):
|
|
getattr(self, "__post_update")()
|
|
|
|
@property
|
|
def selected_base(self) -> BaseAccount | None:
|
|
"""The selected base account in the form.
|
|
|
|
:return: The selected base account in the form.
|
|
"""
|
|
return db.session.get(BaseAccount, self.base_code.data)
|
|
|
|
@property
|
|
def base_options(self) -> list[BaseAccount]:
|
|
"""The selectable base accounts.
|
|
|
|
:return: The selectable base accounts.
|
|
"""
|
|
return BaseAccount.query\
|
|
.filter(sa.func.char_length(BaseAccount.code) == 4)\
|
|
.order_by(BaseAccount.code).all()
|
|
|
|
|
|
def sort_accounts_in(base_code: str, exclude: int) -> None:
|
|
"""Sorts the accounts under a base account after changing the base
|
|
account or deleting an account.
|
|
|
|
:param base_code: The code of the base account.
|
|
:param exclude: The account ID to exclude.
|
|
:return: None.
|
|
"""
|
|
accounts: list[Account] = Account.query\
|
|
.filter(Account.base_code == base_code,
|
|
Account.id != exclude)\
|
|
.order_by(Account.no).all()
|
|
for i in range(len(accounts)):
|
|
if accounts[i].no != i + 1:
|
|
accounts[i].no = i + 1
|