# The Mia! Accounting Project. # Author: imacat@mail.imacat.idv.tw (imacat), 2023/2/19 # 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 operators for different journal entry types. """ from abc import ABC, abstractmethod from typing import Type from flask import render_template, request, abort from flask_wtf import FlaskForm from accounting.journal_entry.forms import JournalEntryForm, \ CashReceiptJournalEntryForm, CashDisbursementJournalEntryForm, \ TransferJournalEntryForm from accounting.journal_entry.forms.line_item import LineItemForm from accounting.models import JournalEntry from accounting.template_globals import default_currency_code from accounting.utils.journal_entry_types import JournalEntryType class JournalEntryOperator(ABC): """The base journal entry operator.""" CHECK_ORDER: int = -1 """The order when checking the journal entry operator.""" @property @abstractmethod def form(self) -> Type[JournalEntryForm]: """Returns the form class. :return: The form class. """ @abstractmethod def render_create_template(self, form: FlaskForm) -> str: """Renders the template for the form to create a journal entry. :param form: The journal entry form. :return: the form to create a journal entry. """ @abstractmethod def render_detail_template(self, journal_entry: JournalEntry) -> str: """Renders the template for the detail page. :param journal_entry: The journal entry. :return: the detail page. """ @abstractmethod def render_edit_template(self, journal_entry: JournalEntry, form: FlaskForm) -> str: """Renders the template for the form to edit a journal entry. :param journal_entry: The journal entry. :param form: The form. :return: the form to edit a journal entry. """ @abstractmethod def is_my_type(self, journal_entry: JournalEntry) -> bool: """Checks and returns whether the journal entry belongs to the type. :param journal_entry: The journal entry. :return: True if the journal entry belongs to the type, or False otherwise. """ @property def _line_item_template(self) -> str: """Renders and returns the template for the line item sub-form. :return: The template for the line item sub-form. """ return render_template( "accounting/journal-entry/include/form-line-item.html", currency_index="CURRENCY_INDEX", debit_credit="DEBIT_CREDIT", line_item_index="LINE_ITEM_INDEX", form=LineItemForm()) class CashReceiptJournalEntry(JournalEntryOperator): """A cash receipt journal entry.""" CHECK_ORDER: int = 2 """The order when checking the journal entry operator.""" @property def form(self) -> Type[JournalEntryForm]: """Returns the form class. :return: The form class. """ return CashReceiptJournalEntryForm def render_create_template(self, form: CashReceiptJournalEntryForm) -> str: """Renders the template for the form to create a journal entry. :param form: The journal entry form. :return: the form to create a journal entry. """ return render_template( "accounting/journal-entry/receipt/create.html", form=form, journal_entry_type=JournalEntryType.CASH_RECEIPT, currency_template=self.__currency_template, line_item_template=self._line_item_template) def render_detail_template(self, journal_entry: JournalEntry) -> str: """Renders the template for the detail page. :param journal_entry: The journal entry. :return: the detail page. """ return render_template("accounting/journal-entry/receipt/detail.html", obj=journal_entry) def render_edit_template(self, journal_entry: JournalEntry, form: CashReceiptJournalEntryForm) -> str: """Renders the template for the form to edit a journal entry. :param journal_entry: The journal entry. :param form: The form. :return: the form to edit a journal entry. """ return render_template("accounting/journal-entry/receipt/edit.html", journal_entry=journal_entry, form=form, currency_template=self.__currency_template, line_item_template=self._line_item_template) def is_my_type(self, journal_entry: JournalEntry) -> bool: """Checks and returns whether the journal entry belongs to the type. :param journal_entry: The journal entry. :return: True if the journal entry belongs to the type, or False otherwise. """ return journal_entry.is_cash_receipt @property def __currency_template(self) -> str: """Renders and returns the template for the currency sub-form. :return: The template for the currency sub-form. """ return render_template( "accounting/journal-entry/receipt/include/form-currency.html", currency_index="CURRENCY_INDEX", currency_code_data=default_currency_code(), credit_total="-") class CashDisbursementJournalEntry(JournalEntryOperator): """A cash disbursement journal entry.""" CHECK_ORDER: int = 1 """The order when checking the journal entry operator.""" @property def form(self) -> Type[JournalEntryForm]: """Returns the form class. :return: The form class. """ return CashDisbursementJournalEntryForm def render_create_template(self, form: CashDisbursementJournalEntryForm) \ -> str: """Renders the template for the form to create a journal entry. :param form: The journal entry form. :return: the form to create a journal entry. """ return render_template( "accounting/journal-entry/disbursement/create.html", form=form, journal_entry_type=JournalEntryType.CASH_DISBURSEMENT, currency_template=self.__currency_template, line_item_template=self._line_item_template) def render_detail_template(self, journal_entry: JournalEntry) -> str: """Renders the template for the detail page. :param journal_entry: The journal entry. :return: the detail page. """ return render_template( "accounting/journal-entry/disbursement/detail.html", obj=journal_entry) def render_edit_template(self, journal_entry: JournalEntry, form: CashDisbursementJournalEntryForm) -> str: """Renders the template for the form to edit a journal entry. :param journal_entry: The journal entry. :param form: The form. :return: the form to edit a journal entry. """ return render_template( "accounting/journal-entry/disbursement/edit.html", journal_entry=journal_entry, form=form, currency_template=self.__currency_template, line_item_template=self._line_item_template) def is_my_type(self, journal_entry: JournalEntry) -> bool: """Checks and returns whether the journal entry belongs to the type. :param journal_entry: The journal entry. :return: True if the journal entry belongs to the type, or False otherwise. """ return journal_entry.is_cash_disbursement @property def __currency_template(self) -> str: """Renders and returns the template for the currency sub-form. :return: The template for the currency sub-form. """ return render_template( "accounting/journal-entry/disbursement/include/form-currency.html", currency_index="CURRENCY_INDEX", currency_code_data=default_currency_code(), debit_total="-") class TransferJournalEntry(JournalEntryOperator): """A transfer journal entry.""" CHECK_ORDER: int = 3 """The order when checking the journal entry operator.""" @property def form(self) -> Type[JournalEntryForm]: """Returns the form class. :return: The form class. """ return TransferJournalEntryForm def render_create_template(self, form: TransferJournalEntryForm) -> str: """Renders the template for the form to create a journal entry. :param form: The journal entry form. :return: the form to create a journal entry. """ return render_template( "accounting/journal-entry/transfer/create.html", form=form, journal_entry_type=JournalEntryType.TRANSFER, currency_template=self.__currency_template, line_item_template=self._line_item_template) def render_detail_template(self, journal_entry: JournalEntry) -> str: """Renders the template for the detail page. :param journal_entry: The journal entry. :return: the detail page. """ return render_template("accounting/journal-entry/transfer/detail.html", obj=journal_entry) def render_edit_template(self, journal_entry: JournalEntry, form: TransferJournalEntryForm) -> str: """Renders the template for the form to edit a journal entry. :param journal_entry: The journal entry. :param form: The form. :return: the form to edit a journal entry. """ return render_template("accounting/journal-entry/transfer/edit.html", journal_entry=journal_entry, form=form, currency_template=self.__currency_template, line_item_template=self._line_item_template) def is_my_type(self, journal_entry: JournalEntry) -> bool: """Checks and returns whether the journal entry belongs to the type. :param journal_entry: The journal entry. :return: True if the journal entry belongs to the type, or False otherwise. """ return True @property def __currency_template(self) -> str: """Renders and returns the template for the currency sub-form. :return: The template for the currency sub-form. """ return render_template( "accounting/journal-entry/transfer/include/form-currency.html", currency_index="CURRENCY_INDEX", currency_code_data=default_currency_code(), debit_total="-", credit_total="-") JOURNAL_ENTRY_TYPE_TO_OP: dict[JournalEntryType, JournalEntryOperator] \ = {JournalEntryType.CASH_RECEIPT: CashReceiptJournalEntry(), JournalEntryType.CASH_DISBURSEMENT: CashDisbursementJournalEntry(), JournalEntryType.TRANSFER: TransferJournalEntry()} """The map from the journal entry types to their operators.""" def get_journal_entry_op(journal_entry: JournalEntry, is_check_as: bool = False) -> JournalEntryOperator: """Returns the journal entry operator that may be specified in the "as" query parameter. If it is not specified, check the journal entry type from the journal entry. :param journal_entry: The journal entry. :param is_check_as: True to check the "as" parameter, or False otherwise. :return: None. """ if is_check_as and "as" in request.args: type_dict: dict[str, JournalEntryType] \ = {x.value: x for x in JournalEntryType} if request.args["as"] not in type_dict: abort(404) return JOURNAL_ENTRY_TYPE_TO_OP[type_dict[request.args["as"]]] for journal_entry_type in sorted(JOURNAL_ENTRY_TYPE_TO_OP.values(), key=lambda x: x.CHECK_ORDER): if journal_entry_type.is_my_type(journal_entry): return journal_entry_type