From c1235608d8ca171db675f4489b04ca32229f35cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Sun, 19 Mar 2023 21:00:11 +0800 Subject: [PATCH] Renamed "journal entry" to "voucher line item", and "entry type" to "side". --- src/accounting/account/commands.py | 5 +- src/accounting/account/forms.py | 2 +- src/accounting/models.py | 117 +++--- .../report/reports/balance_sheet.py | 16 +- .../report/reports/income_expenses.py | 244 ++++++------- .../report/reports/income_statement.py | 8 +- src/accounting/report/reports/journal.py | 82 ++--- src/accounting/report/reports/ledger.py | 234 ++++++------ src/accounting/report/reports/search.py | 67 ++-- .../report/reports/trial_balance.py | 8 +- .../report/utils/base_page_params.py | 6 +- src/accounting/static/css/style.css | 27 +- src/accounting/static/js/account-selector.js | 42 +-- ...ctor.js => original-line-item-selector.js} | 124 +++---- src/accounting/static/js/summary-editor.js | 38 +- src/accounting/static/js/voucher-form.js | 310 ++++++++-------- ...-editor.js => voucher-line-item-editor.js} | 240 ++++++------- .../accounting/account/include/form.html | 2 +- .../include/income-expenses-row-desktop.html | 12 +- .../include/income-expenses-row-mobile.html | 28 +- .../report/include/ledger-row-desktop.html | 10 +- .../report/include/ledger-row-mobile.html | 18 +- .../accounting/report/income-expenses.html | 22 +- .../templates/accounting/report/journal.html | 38 +- .../templates/accounting/report/ledger.html | 22 +- .../templates/accounting/report/search.html | 38 +- .../voucher/disbursement/detail.html | 8 +- .../include/form-currency-item.html | 50 +-- .../voucher/disbursement/include/form.html | 2 +- .../include/account-selector-modal.html | 22 +- ...il-entries.html => detail-line-items.html} | 34 +- .../voucher/include/form-entry-item.html | 74 ---- .../voucher/include/form-line-item.html | 74 ++++ .../accounting/voucher/include/form.html | 10 +- .../include/journal-entry-editor-modal.html | 76 ---- .../original-entry-selector-modal.html | 56 --- .../original-line-item-selector-modal.html | 56 +++ .../voucher/include/summary-editor-modal.html | 110 +++--- .../voucher-line-item-editor-modal.html | 76 ++++ .../accounting/voucher/receipt/detail.html | 8 +- .../receipt/include/form-currency-item.html | 50 +-- .../voucher/receipt/include/form.html | 2 +- .../accounting/voucher/transfer/detail.html | 20 +- .../transfer/include/form-currency-item.html | 104 +++--- .../voucher/transfer/include/form.html | 4 +- src/accounting/voucher/converters.py | 10 +- src/accounting/voucher/forms/currency.py | 143 ++++---- .../forms/{journal_entry.py => line_item.py} | 314 +++++++++-------- src/accounting/voucher/forms/voucher.py | 333 +++++++++--------- src/accounting/voucher/utils/offset_alias.py | 10 +- src/accounting/voucher/utils/operators.py | 24 +- ...inal_entries.py => original_line_items.py} | 58 +-- .../voucher/utils/summary_editor.py | 52 +-- tests/test_offset.py | 136 +++---- tests/test_summary_editor.py | 4 +- tests/test_voucher.py | 32 +- tests/testlib_offset.py | 88 ++--- tests/testlib_voucher.py | 87 ++--- 58 files changed, 1961 insertions(+), 1926 deletions(-) rename src/accounting/static/js/{original-entry-selector.js => original-line-item-selector.js} (67%) rename src/accounting/static/js/{journal-entry-editor.js => voucher-line-item-editor.js} (66%) rename src/accounting/templates/accounting/voucher/include/{detail-entries.html => detail-line-items.html} (68%) delete mode 100644 src/accounting/templates/accounting/voucher/include/form-entry-item.html create mode 100644 src/accounting/templates/accounting/voucher/include/form-line-item.html delete mode 100644 src/accounting/templates/accounting/voucher/include/journal-entry-editor-modal.html delete mode 100644 src/accounting/templates/accounting/voucher/include/original-entry-selector-modal.html create mode 100644 src/accounting/templates/accounting/voucher/include/original-line-item-selector-modal.html create mode 100644 src/accounting/templates/accounting/voucher/include/voucher-line-item-editor-modal.html rename src/accounting/voucher/forms/{journal_entry.py => line_item.py} (57%) rename src/accounting/voucher/utils/{original_entries.py => original_line_items.py} (52%) diff --git a/src/accounting/account/commands.py b/src/accounting/account/commands.py index c8c3352..6f41ae1 100644 --- a/src/accounting/account/commands.py +++ b/src/accounting/account/commands.py @@ -100,10 +100,11 @@ def init_accounts_command(username: str) -> None: def __is_need_offset(base_code: str) -> bool: - """Checks that whether entries in the account need offset. + """Checks that whether voucher line items in the account need offset. :param base_code: The code of the base account. - :return: True if entries in the account need offset, or False otherwise. + :return: True if voucher line items in the account need offset, or False + otherwise. """ # Assets if base_code[0] == "1": diff --git a/src/accounting/account/forms.py b/src/accounting/account/forms.py index 20ee455..cf745b0 100644 --- a/src/accounting/account/forms.py +++ b/src/accounting/account/forms.py @@ -82,7 +82,7 @@ class AccountForm(FlaskForm): """The title.""" is_need_offset = BooleanField( validators=[NoOffsetNominalAccount()]) - """Whether the the entries of this account need offset.""" + """Whether the the voucher line items of this account need offset.""" def populate_obj(self, obj: Account) -> None: """Populates the form data into an account object. diff --git a/src/accounting/models.py b/src/accounting/models.py index c20096c..f0f9cd8 100644 --- a/src/accounting/models.py +++ b/src/accounting/models.py @@ -115,7 +115,7 @@ class Account(db.Model): title_l10n = db.Column("title", db.String, nullable=False) """The title.""" is_need_offset = db.Column(db.Boolean, nullable=False, default=False) - """Whether the entries of this account need offset.""" + """Whether the voucher line items of this account need offset.""" created_at = db.Column(db.DateTime(timezone=True), nullable=False, server_default=db.func.now()) """The time of creation.""" @@ -139,8 +139,8 @@ class Account(db.Model): l10n = db.relationship("AccountL10n", back_populates="account", lazy=False) """The localized titles.""" - entries = db.relationship("JournalEntry", back_populates="account") - """The journal entries.""" + line_items = db.relationship("VoucherLineItem", back_populates="account") + """The voucher line items.""" CASH_CODE: str = "1111-001" """The code of the cash account,""" @@ -363,8 +363,8 @@ class Currency(db.Model): l10n = db.relationship("CurrencyL10n", back_populates="currency", lazy=False) """The localized names.""" - entries = db.relationship("JournalEntry", back_populates="currency") - """The journal entries.""" + line_items = db.relationship("VoucherLineItem", back_populates="currency") + """The voucher line items.""" def __str__(self) -> str: """Returns the string representation of the currency. @@ -450,20 +450,20 @@ class CurrencyL10n(db.Model): class VoucherCurrency: """A currency in a voucher.""" - def __init__(self, code: str, debit: list[JournalEntry], - credit: list[JournalEntry]): + def __init__(self, code: str, debit: list[VoucherLineItem], + credit: list[VoucherLineItem]): """Constructs the currency in the voucher. :param code: The currency code. - :param debit: The debit entries. - :param credit: The credit entries. + :param debit: The debit line items. + :param credit: The credit line items. """ self.code: str = code """The currency code.""" - self.debit: list[JournalEntry] = debit - """The debit entries.""" - self.credit: list[JournalEntry] = credit - """The credit entries.""" + self.debit: list[VoucherLineItem] = debit + """The debit line items.""" + self.credit: list[VoucherLineItem] = credit + """The credit line items.""" @property def name(self) -> str: @@ -475,17 +475,17 @@ class VoucherCurrency: @property def debit_total(self) -> Decimal: - """Returns the total amount of the debit journal entries. + """Returns the total amount of the debit line items. - :return: The total amount of the debit journal entries. + :return: The total amount of the debit line items. """ return sum([x.amount for x in self.debit]) @property def credit_total(self) -> str: - """Returns the total amount of the credit journal entries. + """Returns the total amount of the credit line items. - :return: The total amount of the credit journal entries. + :return: The total amount of the credit line items. """ return sum([x.amount for x in self.credit]) @@ -523,8 +523,8 @@ class Voucher(db.Model): """The ID of the updator.""" updated_by = db.relationship(user_cls, foreign_keys=updated_by_id) """The updator.""" - entries = db.relationship("JournalEntry", back_populates="voucher") - """The journal entries.""" + line_items = db.relationship("VoucherLineItem", back_populates="voucher") + """The line items.""" def __str__(self) -> str: """Returns the string representation of this voucher. @@ -539,18 +539,19 @@ class Voucher(db.Model): @property def currencies(self) -> list[VoucherCurrency]: - """Returns the journal entries categorized by their currencies. + """Returns the line items categorized by their currencies. :return: The currency categories. """ - entries: list[JournalEntry] = sorted(self.entries, key=lambda x: x.no) + line_items: list[VoucherLineItem] = sorted(self.line_items, + key=lambda x: x.no) codes: list[str] = [] - by_currency: dict[str, list[JournalEntry]] = {} - for entry in entries: - if entry.currency_code not in by_currency: - codes.append(entry.currency_code) - by_currency[entry.currency_code] = [] - by_currency[entry.currency_code].append(entry) + by_currency: dict[str, list[VoucherLineItem]] = {} + for line_item in line_items: + if line_item.currency_code not in by_currency: + codes.append(line_item.currency_code) + by_currency[line_item.currency_code] = [] + by_currency[line_item.currency_code].append(line_item) return [VoucherCurrency(code=x, debit=[y for y in by_currency[x] if y.is_debit], @@ -593,8 +594,8 @@ class Voucher(db.Model): """ if not hasattr(self, "__can_delete"): def has_offset() -> bool: - for entry in self.entries: - if len(entry.offsets) > 0: + for line_item in self.line_items: + if len(line_item.offsets) > 0: return True return False setattr(self, "__can_delete", not has_offset()) @@ -605,50 +606,52 @@ class Voucher(db.Model): :return: None. """ - JournalEntry.query\ - .filter(JournalEntry.voucher_id == self.id).delete() + VoucherLineItem.query\ + .filter(VoucherLineItem.voucher_id == self.id).delete() db.session.delete(self) -class JournalEntry(db.Model): - """An accounting journal entry.""" - __tablename__ = "accounting_journal_entries" +class VoucherLineItem(db.Model): + """A line item in the voucher.""" + __tablename__ = "accounting_voucher_line_items" """The table name.""" id = db.Column(db.Integer, nullable=False, primary_key=True, autoincrement=False) - """The entry ID.""" + """The line item ID.""" voucher_id = db.Column(db.Integer, db.ForeignKey(Voucher.id, onupdate="CASCADE", ondelete="CASCADE"), nullable=False) """The voucher ID.""" - voucher = db.relationship(Voucher, back_populates="entries") + voucher = db.relationship(Voucher, back_populates="line_items") """The voucher.""" is_debit = db.Column(db.Boolean, nullable=False) - """True for a debit entry, or False for a credit entry.""" + """True for a debit line item, or False for a credit line item.""" no = db.Column(db.Integer, nullable=False) - """The entry number under the voucher and debit or credit.""" - original_entry_id = db.Column(db.Integer, - db.ForeignKey(id, onupdate="CASCADE"), - nullable=True) - """The ID of the original entry.""" - original_entry = db.relationship("JournalEntry", back_populates="offsets", - remote_side=id, passive_deletes=True) - """The original entry.""" - offsets = db.relationship("JournalEntry", back_populates="original_entry") - """The offset entries.""" + """The line item number under the voucher and debit or credit.""" + original_line_item_id = db.Column(db.Integer, + db.ForeignKey(id, onupdate="CASCADE"), + nullable=True) + """The ID of the original line item.""" + original_line_item = db.relationship("VoucherLineItem", + back_populates="offsets", + remote_side=id, passive_deletes=True) + """The original line item.""" + offsets = db.relationship("VoucherLineItem", + back_populates="original_line_item") + """The offset items.""" currency_code = db.Column(db.String, db.ForeignKey(Currency.code, onupdate="CASCADE"), nullable=False) """The currency code.""" - currency = db.relationship(Currency, back_populates="entries") + currency = db.relationship(Currency, back_populates="line_items") """The currency.""" account_id = db.Column(db.Integer, db.ForeignKey(Account.id, onupdate="CASCADE"), nullable=False) """The account ID.""" - account = db.relationship(Account, back_populates="entries", lazy=False) + account = db.relationship(Account, back_populates="line_items", lazy=False) """The account.""" summary = db.Column(db.String, nullable=True) """The summary.""" @@ -656,9 +659,9 @@ class JournalEntry(db.Model): """The amount.""" def __str__(self) -> str: - """Returns the string representation of the journal entry. + """Returns the string representation of the line item. - :return: The string representation of the journal entry. + :return: The string representation of the line item. """ if not hasattr(self, "__str"): from accounting.template_filters import format_date, format_amount @@ -672,10 +675,10 @@ class JournalEntry(db.Model): @property def eid(self) -> int | None: - """Returns the journal entry ID. This is the alternative name of the + """Returns the line item ID. This is the alternative name of the ID field, to work with WTForms. - :return: The journal entry ID. + :return: The line item ID. """ return self.id @@ -691,15 +694,15 @@ class JournalEntry(db.Model): def debit(self) -> Decimal | None: """Returns the debit amount. - :return: The debit amount, or None if this is not a debit entry. + :return: The debit amount, or None if this is not a debit line item. """ return self.amount if self.is_debit else None @property def is_need_offset(self) -> bool: - """Returns whether the entry needs offset. + """Returns whether the line item needs offset. - :return: True if the entry needs offset, or False otherwise. + :return: True if the line item needs offset, or False otherwise. """ if not self.account.is_need_offset: return False @@ -713,7 +716,7 @@ class JournalEntry(db.Model): def credit(self) -> Decimal | None: """Returns the credit amount. - :return: The credit amount, or None if this is not a credit entry. + :return: The credit amount, or None if this is not a credit line item. """ return None if self.is_debit else self.amount diff --git a/src/accounting/report/reports/balance_sheet.py b/src/accounting/report/reports/balance_sheet.py index 04d228d..0a1bb6c 100644 --- a/src/accounting/report/reports/balance_sheet.py +++ b/src/accounting/report/reports/balance_sheet.py @@ -25,7 +25,7 @@ from flask import render_template, Response from accounting import db from accounting.locale import gettext from accounting.models import Currency, BaseAccount, Account, Voucher, \ - JournalEntry + VoucherLineItem from accounting.report.period import Period, PeriodChooser from accounting.report.utils.base_page_params import BasePageParams from accounting.report.utils.base_report import BaseReport @@ -124,13 +124,13 @@ class AccountCollector: sub_conditions: list[sa.BinaryExpression] \ = [Account.base_code.startswith(x) for x in {"1", "2", "3"}] conditions: list[sa.BinaryExpression] \ - = [JournalEntry.currency_code == self.__currency.code, + = [VoucherLineItem.currency_code == self.__currency.code, sa.or_(*sub_conditions)] if self.__period.end is not None: conditions.append(Voucher.date <= self.__period.end) balance_func: sa.Function = sa.func.sum(sa.case( - (JournalEntry.is_debit, JournalEntry.amount), - else_=-JournalEntry.amount)).label("balance") + (VoucherLineItem.is_debit, VoucherLineItem.amount), + else_=-VoucherLineItem.amount)).label("balance") select_balance: sa.Select \ = sa.select(Account.id, Account.base_code, Account.no, balance_func)\ @@ -178,7 +178,7 @@ class AccountCollector: if self.__period.start is None: return None conditions: list[sa.BinaryExpression] \ - = [JournalEntry.currency_code == self.__currency.code, + = [VoucherLineItem.currency_code == self.__currency.code, Voucher.date < self.__period.start] return self.__query_balance(conditions) @@ -197,7 +197,7 @@ class AccountCollector: :return: The net income or loss for current period. """ conditions: list[sa.BinaryExpression] \ - = [JournalEntry.currency_code == self.__currency.code] + = [VoucherLineItem.currency_code == self.__currency.code] if self.__period.start is not None: conditions.append(Voucher.date >= self.__period.start) if self.__period.end is not None: @@ -215,8 +215,8 @@ class AccountCollector: conditions.extend([sa.not_(Account.base_code.startswith(x)) for x in {"1", "2", "3"}]) balance_func: sa.Function = sa.func.sum(sa.case( - (JournalEntry.is_debit, JournalEntry.amount), - else_=-JournalEntry.amount)) + (VoucherLineItem.is_debit, VoucherLineItem.amount), + else_=-VoucherLineItem.amount)) select_balance: sa.Select = sa.select(balance_func)\ .join(Voucher).join(Account).filter(*conditions) return db.session.scalar(select_balance) diff --git a/src/accounting/report/reports/income_expenses.py b/src/accounting/report/reports/income_expenses.py index c9131f5..30b3c92 100644 --- a/src/accounting/report/reports/income_expenses.py +++ b/src/accounting/report/reports/income_expenses.py @@ -26,7 +26,7 @@ from sqlalchemy.orm import selectinload from accounting import db from accounting.locale import gettext -from accounting.models import Currency, Account, Voucher, JournalEntry +from accounting.models import Currency, Account, Voucher, VoucherLineItem from accounting.report.period import Period, PeriodChooser from accounting.report.utils.base_page_params import BasePageParams from accounting.report.utils.base_report import BaseReport @@ -41,18 +41,18 @@ from accounting.utils.cast import be from accounting.utils.pagination import Pagination -class ReportEntry: - """An entry in the report.""" +class ReportLineItem: + """A line item in the report.""" - def __init__(self, entry: JournalEntry | None = None): - """Constructs the entry in the report. + def __init__(self, line_item: VoucherLineItem | None = None): + """Constructs the line item in the report. - :param entry: The journal entry. + :param line_item: The voucher line item. """ self.is_brought_forward: bool = False - """Whether this is the brought-forward entry.""" + """Whether this is the brought-forward line item.""" self.is_total: bool = False - """Whether this is the total entry.""" + """Whether this is the total line item.""" self.date: date | None = None """The date.""" self.account: Account | None = None @@ -68,24 +68,24 @@ class ReportEntry: self.note: str | None = None """The note.""" self.url: str | None = None - """The URL to the journal entry.""" - if entry is not None: - self.date = entry.voucher.date - self.account = entry.account - self.summary = entry.summary - self.income = None if entry.is_debit else entry.amount - self.expense = entry.amount if entry.is_debit else None - self.note = entry.voucher.note + """The URL to the voucher line item.""" + if line_item is not None: + self.date = line_item.voucher.date + self.account = line_item.account + self.summary = line_item.summary + self.income = None if line_item.is_debit else line_item.amount + self.expense = line_item.amount if line_item.is_debit else None + self.note = line_item.voucher.note self.url = url_for("accounting.voucher.detail", - voucher=entry.voucher) + voucher=line_item.voucher) -class EntryCollector: - """The report entry collector.""" +class LineItemCollector: + """The line item collector.""" def __init__(self, currency: Currency, account: IncomeExpensesAccount, period: Period): - """Constructs the report entry collector. + """Constructs the line item collector. :param currency: The currency. :param account: The account. @@ -97,74 +97,74 @@ class EntryCollector: """The account.""" self.__period: Period = period """The period""" - self.brought_forward: ReportEntry | None - """The brought-forward entry.""" - self.entries: list[ReportEntry] - """The log entries.""" - self.total: ReportEntry | None - """The total entry.""" - self.brought_forward = self.__get_brought_forward_entry() - self.entries = self.__query_entries() - self.total = self.__get_total_entry() + self.brought_forward: ReportLineItem | None + """The brought-forward line item.""" + self.line_items: list[ReportLineItem] + """The line items.""" + self.total: ReportLineItem | None + """The total line item.""" + self.brought_forward = self.__get_brought_forward() + self.line_items = self.__query_line_items() + self.total = self.__get_total() self.__populate_balance() - def __get_brought_forward_entry(self) -> ReportEntry | None: - """Queries, composes and returns the brought-forward entry. + def __get_brought_forward(self) -> ReportLineItem | None: + """Queries, composes and returns the brought-forward line item. - :return: The brought-forward entry, or None if the period starts from - the beginning. + :return: The brought-forward line item, or None if the period starts + from the beginning. """ if self.__period.start is None: return None balance_func: sa.Function = sa.func.sum(sa.case( - (JournalEntry.is_debit, JournalEntry.amount), - else_=-JournalEntry.amount)) + (VoucherLineItem.is_debit, VoucherLineItem.amount), + else_=-VoucherLineItem.amount)) select: sa.Select = sa.Select(balance_func)\ .join(Voucher).join(Account)\ - .filter(be(JournalEntry.currency_code == self.__currency.code), + .filter(be(VoucherLineItem.currency_code == self.__currency.code), self.__account_condition, Voucher.date < self.__period.start) balance: int | None = db.session.scalar(select) if balance is None: return None - entry: ReportEntry = ReportEntry() - entry.is_brought_forward = True - entry.date = self.__period.start - entry.account = Account.accumulated_change() - entry.summary = gettext("Brought forward") + line_item: ReportLineItem = ReportLineItem() + line_item.is_brought_forward = True + line_item.date = self.__period.start + line_item.account = Account.accumulated_change() + line_item.summary = gettext("Brought forward") if balance > 0: - entry.income = balance + line_item.income = balance elif balance < 0: - entry.expense = -balance - entry.balance = balance - return entry + line_item.expense = -balance + line_item.balance = balance + return line_item - def __query_entries(self) -> list[ReportEntry]: - """Queries and returns the log entries. + def __query_line_items(self) -> list[ReportLineItem]: + """Queries and returns the line items. - :return: The log entries. + :return: The line items. """ conditions: list[sa.BinaryExpression] \ - = [JournalEntry.currency_code == self.__currency.code, + = [VoucherLineItem.currency_code == self.__currency.code, self.__account_condition] if self.__period.start is not None: conditions.append(Voucher.date >= self.__period.start) if self.__period.end is not None: conditions.append(Voucher.date <= self.__period.end) voucher_with_account: sa.Select = sa.Select(Voucher.id).\ - join(JournalEntry).join(Account).filter(*conditions) + join(VoucherLineItem).join(Account).filter(*conditions) - return [ReportEntry(x) - for x in JournalEntry.query.join(Voucher).join(Account) - .filter(JournalEntry.voucher_id.in_(voucher_with_account), - JournalEntry.currency_code == self.__currency.code, + return [ReportLineItem(x) + for x in VoucherLineItem.query.join(Voucher).join(Account) + .filter(VoucherLineItem.voucher_id.in_(voucher_with_account), + VoucherLineItem.currency_code == self.__currency.code, sa.not_(self.__account_condition)) .order_by(Voucher.date, Voucher.no, - JournalEntry.is_debit, - JournalEntry.no) - .options(selectinload(JournalEntry.account), - selectinload(JournalEntry.voucher))] + VoucherLineItem.is_debit, + VoucherLineItem.no) + .options(selectinload(VoucherLineItem.account), + selectinload(VoucherLineItem.voucher))] @property def __account_condition(self) -> sa.BinaryExpression: @@ -175,38 +175,39 @@ class EntryCollector: Account.base_code.startswith("22")) return Account.id == self.__account.id - def __get_total_entry(self) -> ReportEntry | None: - """Composes the total entry. + def __get_total(self) -> ReportLineItem | None: + """Composes the total line item. - :return: The total entry, or None if there is no data. + :return: The total line item, or None if there is no data. """ - if self.brought_forward is None and len(self.entries) == 0: + if self.brought_forward is None and len(self.line_items) == 0: return None - entry: ReportEntry = ReportEntry() - entry.is_total = True - entry.summary = gettext("Total") - entry.income = sum([x.income for x in self.entries - if x.income is not None]) - entry.expense = sum([x.expense for x in self.entries - if x.expense is not None]) - entry.balance = entry.income - entry.expense + line_item: ReportLineItem = ReportLineItem() + line_item.is_total = True + line_item.summary = gettext("Total") + line_item.income = sum([x.income for x in self.line_items + if x.income is not None]) + line_item.expense = sum([x.expense for x in self.line_items + if x.expense is not None]) + line_item.balance = line_item.income - line_item.expense if self.brought_forward is not None: - entry.balance = self.brought_forward.balance + entry.balance - return entry + line_item.balance \ + = self.brought_forward.balance + line_item.balance + return line_item def __populate_balance(self) -> None: - """Populates the balance of the entries. + """Populates the balance of the line items. :return: None. """ balance: Decimal = 0 if self.brought_forward is None \ else self.brought_forward.balance - for entry in self.entries: - if entry.income is not None: - balance = balance + entry.income - if entry.expense is not None: - balance = balance - entry.expense - entry.balance = balance + for line_item in self.line_items: + if line_item.income is not None: + balance = balance + line_item.income + if line_item.expense is not None: + balance = balance - line_item.expense + line_item.balance = balance class CSVRow(BaseCSVRow): @@ -261,19 +262,19 @@ class PageParams(BasePageParams): account: IncomeExpensesAccount, period: Period, has_data: bool, - pagination: Pagination[ReportEntry], - brought_forward: ReportEntry | None, - entries: list[ReportEntry], - total: ReportEntry | None): + pagination: Pagination[ReportLineItem], + brought_forward: ReportLineItem | None, + line_items: list[ReportLineItem], + total: ReportLineItem | None): """Constructs the HTML page parameters. :param currency: The currency. :param account: The account. :param period: The period. :param has_data: True if there is any data, or False otherwise. - :param brought_forward: The brought-forward entry. - :param entries: The log entries. - :param total: The total entry. + :param brought_forward: The brought-forward line item. + :param line_items: The line items. + :param total: The total line item. """ self.currency: Currency = currency """The currency.""" @@ -283,14 +284,14 @@ class PageParams(BasePageParams): """The period.""" self.__has_data: bool = has_data """True if there is any data, or False otherwise.""" - self.pagination: Pagination[ReportEntry] = pagination + self.pagination: Pagination[ReportLineItem] = pagination """The pagination.""" - self.brought_forward: ReportEntry | None = brought_forward - """The brought-forward entry.""" - self.entries: list[ReportEntry] = entries - """The report entries.""" - self.total: ReportEntry | None = total - """The total entry.""" + self.brought_forward: ReportLineItem | None = brought_forward + """The brought-forward line item.""" + self.line_items: list[ReportLineItem] = line_items + """The line items.""" + self.total: ReportLineItem | None = total + """The total line item.""" self.period_chooser: PeriodChooser = PeriodChooser( lambda x: income_expenses_url(currency, account, x)) """The period chooser.""" @@ -342,14 +343,14 @@ class PageParams(BasePageParams): income_expenses_url(self.currency, current_al, self.period), self.account.id == 0)] - in_use: sa.Select = sa.Select(JournalEntry.account_id)\ + in_use: sa.Select = sa.Select(VoucherLineItem.account_id)\ .join(Account)\ - .filter(be(JournalEntry.currency_code == self.currency.code), + .filter(be(VoucherLineItem.currency_code == self.currency.code), sa.or_(Account.base_code.startswith("11"), Account.base_code.startswith("12"), Account.base_code.startswith("21"), Account.base_code.startswith("22")))\ - .group_by(JournalEntry.account_id) + .group_by(VoucherLineItem.account_id) options.extend([OptionLink(str(x), income_expenses_url( self.currency, @@ -378,14 +379,15 @@ class IncomeExpenses(BaseReport): """The account.""" self.__period: Period = period """The period.""" - collector: EntryCollector = EntryCollector( + collector: LineItemCollector = LineItemCollector( self.__currency, self.__account, self.__period) - self.__brought_forward: ReportEntry | None = collector.brought_forward - """The brought-forward entry.""" - self.__entries: list[ReportEntry] = collector.entries - """The report entries.""" - self.__total: ReportEntry | None = collector.total - """The total entry.""" + self.__brought_forward: ReportLineItem | None \ + = collector.brought_forward + """The brought-forward line item.""" + self.__line_items: list[ReportLineItem] = collector.line_items + """The line items.""" + self.__total: ReportLineItem | None = collector.total + """The total line item.""" def csv(self) -> Response: """Returns the report as CSV for download. @@ -416,7 +418,7 @@ class IncomeExpenses(BaseReport): None)) rows.extend([CSVRow(x.date, str(x.account).title(), x.summary, x.income, x.expense, x.balance, x.note) - for x in self.__entries]) + for x in self.__line_items]) if self.__total is not None: rows.append(CSVRow(gettext("Total"), None, None, self.__total.income, self.__total.expense, @@ -428,31 +430,31 @@ class IncomeExpenses(BaseReport): :return: The report as HTML. """ - all_entries: list[ReportEntry] = [] + all_line_items: list[ReportLineItem] = [] if self.__brought_forward is not None: - all_entries.append(self.__brought_forward) - all_entries.extend(self.__entries) + all_line_items.append(self.__brought_forward) + all_line_items.extend(self.__line_items) if self.__total is not None: - all_entries.append(self.__total) - pagination: Pagination[ReportEntry] \ - = Pagination[ReportEntry](all_entries, is_reversed=True) - page_entries: list[ReportEntry] = pagination.list - has_data: bool = len(page_entries) > 0 - brought_forward: ReportEntry | None = None - if len(page_entries) > 0 and page_entries[0].is_brought_forward: - brought_forward = page_entries[0] - page_entries = page_entries[1:] - total: ReportEntry | None = None - if len(page_entries) > 0 and page_entries[-1].is_total: - total = page_entries[-1] - page_entries = page_entries[:-1] + all_line_items.append(self.__total) + pagination: Pagination[ReportLineItem] \ + = Pagination[ReportLineItem](all_line_items, is_reversed=True) + page_line_items: list[ReportLineItem] = pagination.list + has_data: bool = len(page_line_items) > 0 + brought_forward: ReportLineItem | None = None + if len(page_line_items) > 0 and page_line_items[0].is_brought_forward: + brought_forward = page_line_items[0] + page_line_items = page_line_items[1:] + total: ReportLineItem | None = None + if len(page_line_items) > 0 and page_line_items[-1].is_total: + total = page_line_items[-1] + page_line_items = page_line_items[:-1] params: PageParams = PageParams(currency=self.__currency, account=self.__account, period=self.__period, has_data=has_data, pagination=pagination, brought_forward=brought_forward, - entries=page_entries, + line_items=page_line_items, total=total) return render_template("accounting/report/income-expenses.html", report=params) diff --git a/src/accounting/report/reports/income_statement.py b/src/accounting/report/reports/income_statement.py index f36e226..6cf369b 100644 --- a/src/accounting/report/reports/income_statement.py +++ b/src/accounting/report/reports/income_statement.py @@ -25,7 +25,7 @@ from flask import render_template, Response from accounting import db from accounting.locale import gettext from accounting.models import Currency, BaseAccount, Account, Voucher, \ - JournalEntry + VoucherLineItem from accounting.report.period import Period, PeriodChooser from accounting.report.utils.base_page_params import BasePageParams from accounting.report.utils.base_report import BaseReport @@ -256,15 +256,15 @@ class IncomeStatement(BaseReport): sub_conditions: list[sa.BinaryExpression] \ = [Account.base_code.startswith(str(x)) for x in range(4, 10)] conditions: list[sa.BinaryExpression] \ - = [JournalEntry.currency_code == self.__currency.code, + = [VoucherLineItem.currency_code == self.__currency.code, sa.or_(*sub_conditions)] if self.__period.start is not None: conditions.append(Voucher.date >= self.__period.start) if self.__period.end is not None: conditions.append(Voucher.date <= self.__period.end) balance_func: sa.Function = sa.func.sum(sa.case( - (JournalEntry.is_debit, -JournalEntry.amount), - else_=JournalEntry.amount)).label("balance") + (VoucherLineItem.is_debit, -VoucherLineItem.amount), + else_=VoucherLineItem.amount)).label("balance") select_balances: sa.Select = sa.select(Account.id, balance_func)\ .join(Voucher).join(Account)\ .filter(*conditions)\ diff --git a/src/accounting/report/reports/journal.py b/src/accounting/report/reports/journal.py index ec7e65d..aa30c38 100644 --- a/src/accounting/report/reports/journal.py +++ b/src/accounting/report/reports/journal.py @@ -25,7 +25,7 @@ from flask import render_template, Response from sqlalchemy.orm import selectinload from accounting.locale import gettext -from accounting.models import Currency, Account, Voucher, JournalEntry +from accounting.models import Currency, Account, Voucher, VoucherLineItem from accounting.report.period import Period, PeriodChooser from accounting.report.utils.base_page_params import BasePageParams from accounting.report.utils.base_report import BaseReport @@ -37,29 +37,29 @@ from accounting.report.utils.urls import journal_url from accounting.utils.pagination import Pagination -class ReportEntry: - """An entry in the report.""" +class ReportLineItem: + """A line item in the report.""" - def __init__(self, entry: JournalEntry): - """Constructs the entry in the report. + def __init__(self, line_item: VoucherLineItem): + """Constructs the line item in the report. - :param entry: The journal entry. + :param line_item: The voucher line item. """ - self.entry: JournalEntry = entry - """The journal entry.""" - self.voucher: Voucher = entry.voucher + self.line_item: VoucherLineItem = line_item + """The voucher line item.""" + self.voucher: Voucher = line_item.voucher """The voucher.""" - self.currency: Currency = entry.currency + self.currency: Currency = line_item.currency """The account.""" - self.account: Account = entry.account + self.account: Account = line_item.account """The account.""" - self.summary: str | None = entry.summary + self.summary: str | None = line_item.summary """The summary.""" - self.debit: Decimal | None = entry.debit + self.debit: Decimal | None = line_item.debit """The debit amount.""" - self.credit: Decimal | None = entry.credit + self.credit: Decimal | None = line_item.credit """The credit amount.""" - self.amount: Decimal = entry.amount + self.amount: Decimal = line_item.amount """The amount.""" @@ -110,19 +110,19 @@ class PageParams(BasePageParams): """The HTML page parameters.""" def __init__(self, period: Period, - pagination: Pagination[JournalEntry], - entries: list[JournalEntry]): + pagination: Pagination[VoucherLineItem], + line_items: list[VoucherLineItem]): """Constructs the HTML page parameters. :param period: The period. - :param entries: The journal entries. + :param line_items: The line items. """ self.period: Period = period """The period.""" - self.pagination: Pagination[JournalEntry] = pagination + self.pagination: Pagination[VoucherLineItem] = pagination """The pagination.""" - self.entries: list[JournalEntry] = entries - """The entries.""" + self.line_items: list[VoucherLineItem] = line_items + """The line items.""" self.period_chooser: PeriodChooser = PeriodChooser( lambda x: journal_url(x)) """The period chooser.""" @@ -133,7 +133,7 @@ class PageParams(BasePageParams): :return: True if there is any data, or False otherwise. """ - return len(self.entries) > 0 + return len(self.line_items) > 0 @property def report_chooser(self) -> ReportChooser: @@ -145,10 +145,10 @@ class PageParams(BasePageParams): period=self.period) -def get_csv_rows(entries: list[JournalEntry]) -> list[CSVRow]: - """Composes and returns the CSV rows from the report entries. +def get_csv_rows(line_items: list[VoucherLineItem]) -> list[CSVRow]: + """Composes and returns the CSV rows from the line items. - :param entries: The report entries. + :param line_items: The line items. :return: The CSV rows. """ rows: list[CSVRow] = [CSVRow(gettext("Date"), gettext("Currency"), @@ -158,7 +158,7 @@ def get_csv_rows(entries: list[JournalEntry]) -> list[CSVRow]: rows.extend([CSVRow(x.voucher.date, x.currency.code, str(x.account).title(), x.summary, x.debit, x.credit, x.voucher.note) - for x in entries]) + for x in line_items]) return rows @@ -172,28 +172,28 @@ class Journal(BaseReport): """ self.__period: Period = period """The period.""" - self.__entries: list[JournalEntry] = self.__query_entries() - """The journal entries.""" + self.__line_items: list[VoucherLineItem] = self.__query_line_items() + """The line items.""" - def __query_entries(self) -> list[JournalEntry]: - """Queries and returns the journal entries. + def __query_line_items(self) -> list[VoucherLineItem]: + """Queries and returns the line items. - :return: The journal entries. + :return: The line items. """ conditions: list[sa.BinaryExpression] = [] if self.__period.start is not None: conditions.append(Voucher.date >= self.__period.start) if self.__period.end is not None: conditions.append(Voucher.date <= self.__period.end) - return JournalEntry.query.join(Voucher)\ + return VoucherLineItem.query.join(Voucher)\ .filter(*conditions)\ .order_by(Voucher.date, Voucher.no, - JournalEntry.is_debit.desc(), - JournalEntry.no)\ - .options(selectinload(JournalEntry.account), - selectinload(JournalEntry.currency), - selectinload(JournalEntry.voucher)).all() + VoucherLineItem.is_debit.desc(), + VoucherLineItem.no)\ + .options(selectinload(VoucherLineItem.account), + selectinload(VoucherLineItem.currency), + selectinload(VoucherLineItem.voucher)).all() def csv(self) -> Response: """Returns the report as CSV for download. @@ -201,17 +201,17 @@ class Journal(BaseReport): :return: The response of the report for download. """ filename: str = f"journal-{period_spec(self.__period)}.csv" - return csv_download(filename, get_csv_rows(self.__entries)) + return csv_download(filename, get_csv_rows(self.__line_items)) def html(self) -> str: """Composes and returns the report as HTML. :return: The report as HTML. """ - pagination: Pagination[JournalEntry] \ - = Pagination[JournalEntry](self.__entries, is_reversed=True) + pagination: Pagination[VoucherLineItem] \ + = Pagination[VoucherLineItem](self.__line_items, is_reversed=True) params: PageParams = PageParams(period=self.__period, pagination=pagination, - entries=pagination.list) + line_items=pagination.list) return render_template("accounting/report/journal.html", report=params) diff --git a/src/accounting/report/reports/ledger.py b/src/accounting/report/reports/ledger.py index de455d1..3163fc0 100644 --- a/src/accounting/report/reports/ledger.py +++ b/src/accounting/report/reports/ledger.py @@ -26,7 +26,7 @@ from sqlalchemy.orm import selectinload from accounting import db from accounting.locale import gettext -from accounting.models import Currency, Account, Voucher, JournalEntry +from accounting.models import Currency, Account, Voucher, VoucherLineItem from accounting.report.period import Period, PeriodChooser from accounting.report.utils.base_page_params import BasePageParams from accounting.report.utils.base_report import BaseReport @@ -40,18 +40,18 @@ from accounting.utils.cast import be from accounting.utils.pagination import Pagination -class ReportEntry: - """An entry in the report.""" +class ReportLineItem: + """A line item in the report.""" - def __init__(self, entry: JournalEntry | None = None): - """Constructs the entry in the report. + def __init__(self, line_item: VoucherLineItem | None = None): + """Constructs the line item in the report. - :param entry: The journal entry. + :param line_item: The voucher line item. """ self.is_brought_forward: bool = False - """Whether this is the brought-forward entry.""" + """Whether this is the brought-forward line item.""" self.is_total: bool = False - """Whether this is the total entry.""" + """Whether this is the total line item.""" self.date: date | None = None """The date.""" self.summary: str | None = None @@ -65,22 +65,22 @@ class ReportEntry: self.note: str | None = None """The note.""" self.url: str | None = None - """The URL to the journal entry.""" - if entry is not None: - self.date = entry.voucher.date - self.summary = entry.summary - self.debit = entry.amount if entry.is_debit else None - self.credit = None if entry.is_debit else entry.amount - self.note = entry.voucher.note + """The URL to the voucher line item.""" + if line_item is not None: + self.date = line_item.voucher.date + self.summary = line_item.summary + self.debit = line_item.amount if line_item.is_debit else None + self.credit = None if line_item.is_debit else line_item.amount + self.note = line_item.voucher.note self.url = url_for("accounting.voucher.detail", - voucher=entry.voucher) + voucher=line_item.voucher) -class EntryCollector: - """The report entry collector.""" +class LineItemCollector: + """The line item collector.""" def __init__(self, currency: Currency, account: Account, period: Period): - """Constructs the report entry collector. + """Constructs the line item collector. :param currency: The currency. :param account: The account. @@ -92,89 +92,90 @@ class EntryCollector: """The account.""" self.__period: Period = period """The period""" - self.brought_forward: ReportEntry | None - """The brought-forward entry.""" - self.entries: list[ReportEntry] - """The report entries.""" - self.total: ReportEntry | None - """The total entry.""" - self.brought_forward = self.__get_brought_forward_entry() - self.entries = self.__query_entries() - self.total = self.__get_total_entry() + self.brought_forward: ReportLineItem | None + """The brought-forward line item.""" + self.line_items: list[ReportLineItem] + """The line items.""" + self.total: ReportLineItem | None + """The total line item.""" + self.brought_forward = self.__get_brought_forward() + self.line_items = self.__query_line_items() + self.total = self.__get_total() self.__populate_balance() - def __get_brought_forward_entry(self) -> ReportEntry | None: - """Queries, composes and returns the brought-forward entry. + def __get_brought_forward(self) -> ReportLineItem | None: + """Queries, composes and returns the brought-forward line item. - :return: The brought-forward entry, or None if the report starts from - the beginning. + :return: The brought-forward line item, or None if the report starts + from the beginning. """ if self.__period.start is None: return None if self.__account.is_nominal: return None balance_func: sa.Function = sa.func.sum(sa.case( - (JournalEntry.is_debit, JournalEntry.amount), - else_=-JournalEntry.amount)) + (VoucherLineItem.is_debit, VoucherLineItem.amount), + else_=-VoucherLineItem.amount)) select: sa.Select = sa.Select(balance_func).join(Voucher)\ - .filter(be(JournalEntry.currency_code == self.__currency.code), - be(JournalEntry.account_id == self.__account.id), + .filter(be(VoucherLineItem.currency_code == self.__currency.code), + be(VoucherLineItem.account_id == self.__account.id), Voucher.date < self.__period.start) balance: int | None = db.session.scalar(select) if balance is None: return None - entry: ReportEntry = ReportEntry() - entry.is_brought_forward = True - entry.date = self.__period.start - entry.summary = gettext("Brought forward") + line_item: ReportLineItem = ReportLineItem() + line_item.is_brought_forward = True + line_item.date = self.__period.start + line_item.summary = gettext("Brought forward") if balance > 0: - entry.debit = balance + line_item.debit = balance elif balance < 0: - entry.credit = -balance - entry.balance = balance - return entry + line_item.credit = -balance + line_item.balance = balance + return line_item - def __query_entries(self) -> list[ReportEntry]: - """Queries and returns the report entries. + def __query_line_items(self) -> list[ReportLineItem]: + """Queries and returns the line items. - :return: The report entries. + :return: The line items. """ conditions: list[sa.BinaryExpression] \ - = [JournalEntry.currency_code == self.__currency.code, - JournalEntry.account_id == self.__account.id] + = [VoucherLineItem.currency_code == self.__currency.code, + VoucherLineItem.account_id == self.__account.id] if self.__period.start is not None: conditions.append(Voucher.date >= self.__period.start) if self.__period.end is not None: conditions.append(Voucher.date <= self.__period.end) - return [ReportEntry(x) for x in JournalEntry.query.join(Voucher) + return [ReportLineItem(x) for x in VoucherLineItem.query.join(Voucher) .filter(*conditions) .order_by(Voucher.date, Voucher.no, - JournalEntry.is_debit.desc(), - JournalEntry.no) - .options(selectinload(JournalEntry.voucher)).all()] + VoucherLineItem.is_debit.desc(), + VoucherLineItem.no) + .options(selectinload(VoucherLineItem.voucher)).all()] - def __get_total_entry(self) -> ReportEntry | None: - """Composes the total entry. + def __get_total(self) -> ReportLineItem | None: + """Composes the total line item. - :return: The total entry, or None if there is no data. + :return: The total line item, or None if there is no data. """ - if self.brought_forward is None and len(self.entries) == 0: + if self.brought_forward is None and len(self.line_items) == 0: return None - entry: ReportEntry = ReportEntry() - entry.is_total = True - entry.summary = gettext("Total") - entry.debit = sum([x.debit for x in self.entries - if x.debit is not None]) - entry.credit = sum([x.credit for x in self.entries - if x.credit is not None]) - entry.balance = entry.debit - entry.credit + line_item: ReportLineItem = ReportLineItem() + line_item.is_total = True + line_item.summary = gettext("Total") + line_item.debit = sum([x.debit for x in self.line_items + if x.debit is not None]) + line_item.credit = sum([x.credit for x in self.line_items + if x.credit is not None]) + line_item.balance = line_item.debit - line_item.credit if self.brought_forward is not None: - entry.balance = self.brought_forward.balance + entry.balance - return entry + line_item.balance \ + = self.brought_forward.balance + line_item.balance + return line_item def __populate_balance(self) -> None: - """Populates the balance of the entries. + """Populates the balance of the line items. :return: None. """ @@ -182,12 +183,12 @@ class EntryCollector: return None balance: Decimal = 0 if self.brought_forward is None \ else self.brought_forward.balance - for entry in self.entries: - if entry.debit is not None: - balance = balance + entry.debit - if entry.credit is not None: - balance = balance - entry.credit - entry.balance = balance + for line_item in self.line_items: + if line_item.debit is not None: + balance = balance + line_item.debit + if line_item.credit is not None: + balance = balance - line_item.credit + line_item.balance = balance class CSVRow(BaseCSVRow): @@ -238,19 +239,19 @@ class PageParams(BasePageParams): account: Account, period: Period, has_data: bool, - pagination: Pagination[ReportEntry], - brought_forward: ReportEntry | None, - entries: list[ReportEntry], - total: ReportEntry | None): + pagination: Pagination[ReportLineItem], + brought_forward: ReportLineItem | None, + line_items: list[ReportLineItem], + total: ReportLineItem | None): """Constructs the HTML page parameters. :param currency: The currency. :param account: The account. :param period: The period. :param has_data: True if there is any data, or False otherwise. - :param brought_forward: The brought-forward entry. - :param entries: The report entries. - :param total: The total entry. + :param brought_forward: The brought-forward line item. + :param line_items: The line items. + :param total: The total line item. """ self.currency: Currency = currency """The currency.""" @@ -260,14 +261,14 @@ class PageParams(BasePageParams): """The period.""" self.__has_data: bool = has_data """True if there is any data, or False otherwise.""" - self.pagination: Pagination[ReportEntry] = pagination + self.pagination: Pagination[ReportLineItem] = pagination """The pagination.""" - self.brought_forward: ReportEntry | None = brought_forward - """The brought-forward entry.""" - self.entries: list[ReportEntry] = entries - """The entries.""" - self.total: ReportEntry | None = total - """The total entry.""" + self.brought_forward: ReportLineItem | None = brought_forward + """The brought-forward line item.""" + self.line_items: list[ReportLineItem] = line_items + """The line items.""" + self.total: ReportLineItem | None = total + """The total line item.""" self.period_chooser: PeriodChooser = PeriodChooser( lambda x: ledger_url(currency, account, x)) """The period chooser.""" @@ -306,9 +307,9 @@ class PageParams(BasePageParams): :return: The account options. """ - in_use: sa.Select = sa.Select(JournalEntry.account_id)\ - .filter(be(JournalEntry.currency_code == self.currency.code))\ - .group_by(JournalEntry.account_id) + in_use: sa.Select = sa.Select(VoucherLineItem.account_id)\ + .filter(be(VoucherLineItem.currency_code == self.currency.code))\ + .group_by(VoucherLineItem.account_id) return [OptionLink(str(x), ledger_url(self.currency, x, self.period), x.id == self.account.id) for x in Account.query.filter(Account.id.in_(in_use)) @@ -331,14 +332,15 @@ class Ledger(BaseReport): """The account.""" self.__period: Period = period """The period.""" - collector: EntryCollector = EntryCollector( + collector: LineItemCollector = LineItemCollector( self.__currency, self.__account, self.__period) - self.__brought_forward: ReportEntry | None = collector.brought_forward - """The brought-forward entry.""" - self.__entries: list[ReportEntry] = collector.entries - """The report entries.""" - self.__total: ReportEntry | None = collector.total - """The total entry.""" + self.__brought_forward: ReportLineItem | None \ + = collector.brought_forward + """The brought-forward line item.""" + self.__line_items: list[ReportLineItem] = collector.line_items + """The line items.""" + self.__total: ReportLineItem | None = collector.total + """The total line item.""" def csv(self) -> Response: """Returns the report as CSV for download. @@ -367,7 +369,7 @@ class Ledger(BaseReport): None)) rows.extend([CSVRow(x.date, x.summary, x.debit, x.credit, x.balance, x.note) - for x in self.__entries]) + for x in self.__line_items]) if self.__total is not None: rows.append(CSVRow(gettext("Total"), None, self.__total.debit, self.__total.credit, @@ -379,31 +381,31 @@ class Ledger(BaseReport): :return: The report as HTML. """ - all_entries: list[ReportEntry] = [] + all_line_items: list[ReportLineItem] = [] if self.__brought_forward is not None: - all_entries.append(self.__brought_forward) - all_entries.extend(self.__entries) + all_line_items.append(self.__brought_forward) + all_line_items.extend(self.__line_items) if self.__total is not None: - all_entries.append(self.__total) - pagination: Pagination[ReportEntry] \ - = Pagination[ReportEntry](all_entries, is_reversed=True) - page_entries: list[ReportEntry] = pagination.list - has_data: bool = len(page_entries) > 0 - brought_forward: ReportEntry | None = None - if len(page_entries) > 0 and page_entries[0].is_brought_forward: - brought_forward = page_entries[0] - page_entries = page_entries[1:] - total: ReportEntry | None = None - if len(page_entries) > 0 and page_entries[-1].is_total: - total = page_entries[-1] - page_entries = page_entries[:-1] + all_line_items.append(self.__total) + pagination: Pagination[ReportLineItem] \ + = Pagination[ReportLineItem](all_line_items, is_reversed=True) + page_line_items: list[ReportLineItem] = pagination.list + has_data: bool = len(page_line_items) > 0 + brought_forward: ReportLineItem | None = None + if len(page_line_items) > 0 and page_line_items[0].is_brought_forward: + brought_forward = page_line_items[0] + page_line_items = page_line_items[1:] + total: ReportLineItem | None = None + if len(page_line_items) > 0 and page_line_items[-1].is_total: + total = page_line_items[-1] + page_line_items = page_line_items[:-1] params: PageParams = PageParams(currency=self.__currency, account=self.__account, period=self.__period, has_data=has_data, pagination=pagination, brought_forward=brought_forward, - entries=page_entries, + line_items=page_line_items, total=total) return render_template("accounting/report/ledger.html", report=params) diff --git a/src/accounting/report/reports/search.py b/src/accounting/report/reports/search.py index 9bbf6cc..91a9fdf 100644 --- a/src/accounting/report/reports/search.py +++ b/src/accounting/report/reports/search.py @@ -26,7 +26,7 @@ from sqlalchemy.orm import selectinload from accounting.locale import gettext from accounting.models import Currency, CurrencyL10n, Account, AccountL10n, \ - Voucher, JournalEntry + Voucher, VoucherLineItem from accounting.report.utils.base_page_params import BasePageParams from accounting.report.utils.base_report import BaseReport from accounting.report.utils.csv_export import csv_download @@ -38,18 +38,18 @@ from accounting.utils.query import parse_query_keywords from .journal import get_csv_rows -class EntryCollector: - """The report entry collector.""" +class LineItemCollector: + """The line item collector.""" def __init__(self): - """Constructs the report entry collector.""" - self.entries: list[JournalEntry] = self.__query_entries() - """The report entries.""" + """Constructs the line item collector.""" + self.line_items: list[VoucherLineItem] = self.__query_line_items() + """The line items.""" - def __query_entries(self) -> list[JournalEntry]: - """Queries and returns the journal entries. + def __query_line_items(self) -> list[VoucherLineItem]: + """Queries and returns the line items. - :return: The journal entries. + :return: The line items. """ keywords: list[str] = parse_query_keywords(request.args.get("q")) if len(keywords) == 0: @@ -57,26 +57,26 @@ class EntryCollector: conditions: list[sa.BinaryExpression] = [] for k in keywords: sub_conditions: list[sa.BinaryExpression] \ - = [JournalEntry.summary.contains(k), - JournalEntry.account_id.in_( + = [VoucherLineItem.summary.contains(k), + VoucherLineItem.account_id.in_( self.__get_account_condition(k)), - JournalEntry.currency_code.in_( + VoucherLineItem.currency_code.in_( self.__get_currency_condition(k)), - JournalEntry.voucher_id.in_( + VoucherLineItem.voucher_id.in_( self.__get_voucher_condition(k))] try: - sub_conditions.append(JournalEntry.amount == Decimal(k)) + sub_conditions.append(VoucherLineItem.amount == Decimal(k)) except ArithmeticError: pass conditions.append(sa.or_(*sub_conditions)) - return JournalEntry.query.join(Voucher).filter(*conditions)\ + return VoucherLineItem.query.join(Voucher).filter(*conditions)\ .order_by(Voucher.date, Voucher.no, - JournalEntry.is_debit, - JournalEntry.no)\ - .options(selectinload(JournalEntry.account), - selectinload(JournalEntry.currency), - selectinload(JournalEntry.voucher)).all() + VoucherLineItem.is_debit, + VoucherLineItem.no)\ + .options(selectinload(VoucherLineItem.account), + selectinload(VoucherLineItem.currency), + selectinload(VoucherLineItem.voucher)).all() @staticmethod def __get_account_condition(k: str) -> sa.Select: @@ -149,16 +149,16 @@ class EntryCollector: class PageParams(BasePageParams): """The HTML page parameters.""" - def __init__(self, pagination: Pagination[JournalEntry], - entries: list[JournalEntry]): + def __init__(self, pagination: Pagination[VoucherLineItem], + line_items: list[VoucherLineItem]): """Constructs the HTML page parameters. - :param entries: The search result entries. + :param line_items: The search result line items. """ - self.pagination: Pagination[JournalEntry] = pagination + self.pagination: Pagination[VoucherLineItem] = pagination """The pagination.""" - self.entries: list[JournalEntry] = entries - """The entries.""" + self.line_items: list[VoucherLineItem] = line_items + """The line items.""" @property def has_data(self) -> bool: @@ -166,7 +166,7 @@ class PageParams(BasePageParams): :return: True if there is any data, or False otherwise. """ - return len(self.entries) > 0 + return len(self.line_items) > 0 @property def report_chooser(self) -> ReportChooser: @@ -182,8 +182,9 @@ class Search(BaseReport): def __init__(self): """Constructs a search.""" - self.__entries: list[JournalEntry] = EntryCollector().entries - """The journal entries.""" + self.__line_items: list[VoucherLineItem] \ + = LineItemCollector().line_items + """The line items.""" def csv(self) -> Response: """Returns the report as CSV for download. @@ -191,16 +192,16 @@ class Search(BaseReport): :return: The response of the report for download. """ filename: str = "search-{q}.csv".format(q=request.args["q"]) - return csv_download(filename, get_csv_rows(self.__entries)) + return csv_download(filename, get_csv_rows(self.__line_items)) def html(self) -> str: """Composes and returns the report as HTML. :return: The report as HTML. """ - pagination: Pagination[JournalEntry] \ - = Pagination[JournalEntry](self.__entries, is_reversed=True) + pagination: Pagination[VoucherLineItem] \ + = Pagination[VoucherLineItem](self.__line_items, is_reversed=True) params: PageParams = PageParams(pagination=pagination, - entries=pagination.list) + line_items=pagination.list) return render_template("accounting/report/search.html", report=params) diff --git a/src/accounting/report/reports/trial_balance.py b/src/accounting/report/reports/trial_balance.py index e98f22d..2e6a9ae 100644 --- a/src/accounting/report/reports/trial_balance.py +++ b/src/accounting/report/reports/trial_balance.py @@ -24,7 +24,7 @@ from flask import Response, render_template from accounting import db from accounting.locale import gettext -from accounting.models import Currency, Account, Voucher, JournalEntry +from accounting.models import Currency, Account, Voucher, VoucherLineItem from accounting.report.period import Period, PeriodChooser from accounting.report.utils.base_page_params import BasePageParams from accounting.report.utils.base_report import BaseReport @@ -178,14 +178,14 @@ class TrialBalance(BaseReport): :return: None. """ conditions: list[sa.BinaryExpression] \ - = [JournalEntry.currency_code == self.__currency.code] + = [VoucherLineItem.currency_code == self.__currency.code] if self.__period.start is not None: conditions.append(Voucher.date >= self.__period.start) if self.__period.end is not None: conditions.append(Voucher.date <= self.__period.end) balance_func: sa.Function = sa.func.sum(sa.case( - (JournalEntry.is_debit, JournalEntry.amount), - else_=-JournalEntry.amount)).label("balance") + (VoucherLineItem.is_debit, VoucherLineItem.amount), + else_=-VoucherLineItem.amount)).label("balance") select_balances: sa.Select = sa.select(Account.id, balance_func)\ .join(Voucher).join(Account)\ .filter(*conditions)\ diff --git a/src/accounting/report/utils/base_page_params.py b/src/accounting/report/utils/base_page_params.py index a78def3..3edb7db 100644 --- a/src/accounting/report/utils/base_page_params.py +++ b/src/accounting/report/utils/base_page_params.py @@ -26,7 +26,7 @@ import sqlalchemy as sa from flask import request from accounting import db -from accounting.models import Currency, JournalEntry +from accounting.models import Currency, VoucherLineItem from accounting.utils.voucher_types import VoucherType from .option_link import OptionLink from .report_chooser import ReportChooser @@ -81,8 +81,8 @@ class BasePageParams(ABC): :return: The currency options. """ in_use: set[str] = set(db.session.scalars( - sa.select(JournalEntry.currency_code) - .group_by(JournalEntry.currency_code)).all()) + sa.select(VoucherLineItem.currency_code) + .group_by(VoucherLineItem.currency_code)).all()) return [OptionLink(str(x), get_url(x), x.code == active_currency.code) for x in Currency.query.filter(Currency.code.in_(in_use)) .order_by(Currency.code).all()] diff --git a/src/accounting/static/css/style.css b/src/accounting/static/css/style.css index b914c4d..36f8a92 100644 --- a/src/accounting/static/css/style.css +++ b/src/accounting/static/css/style.css @@ -117,29 +117,29 @@ } /* Links between objects */ -.accounting-original-entry { +.accounting-original-line-item { border-top: thin solid darkslategray; padding: 0.2rem 0.5rem; } -.accounting-original-entry a { +.accounting-original-line-item a { color: inherit; text-decoration: none; } -.accounting-original-entry a:hover { +.accounting-original-line-item a:hover { color: inherit; } -.accounting-offset-entries { +.accounting-offset-line-items { border-top: thin solid darkslategray; padding: 0.2rem 0.5rem; } -.accounting-offset-entries ul li { +.accounting-offset-line-items ul li { list-style: none; } -.accounting-offset-entries ul li a { +.accounting-offset-line-items ul li a { color: inherit; text-decoration: none; } -.accounting-offset-entries ul li a:hover { +.accounting-offset-line-items ul li a:hover { color: inherit; } @@ -156,31 +156,28 @@ .accounting-currency-content { width: calc(100% - 3rem); } -.accounting-entry-content { +.accounting-line-item-content { width: calc(100% - 3rem); background-color: transparent; } -.accounting-entry-control { - border-color: transparent; -} .accounting-list-group-stripped .list-group-item:nth-child(2n+1) { background-color: #f2f2f2; } .accounting-list-group-hover .list-group-item:hover { background-color: #ececec; } -.accounting-voucher-entry { +.accounting-voucher-line-item { border: none; } -.accounting-voucher-entry-header { +.accounting-voucher-line-item-header { font-weight: bolder; border-bottom: thick double slategray; } -.list-group-item.accounting-voucher-entry-total { +.list-group-item.accounting-voucher-line-item-total { font-weight: bolder; border-top: thick double slategray; } -.accounting-entry-editor-original-entry-content { +.accounting-line-item-editor-original-line-item-content { width: calc(100% - 3rem); } diff --git a/src/accounting/static/js/account-selector.js b/src/accounting/static/js/account-selector.js index 84be852..62096d9 100644 --- a/src/accounting/static/js/account-selector.js +++ b/src/accounting/static/js/account-selector.js @@ -29,16 +29,16 @@ class AccountSelector { /** - * The journal entry editor - * @type {JournalEntryEditor} + * The line item editor + * @type {VoucherLineItemEditor} */ - #entryEditor; + #lineItemEditor; /** - * The entry type + * The side, either "debit" or "credit" * @type {string} */ - #entryType; + #side; /** * The prefix of the HTML ID and class @@ -85,13 +85,13 @@ class AccountSelector { /** * Constructs an account selector. * - * @param entryEditor {JournalEntryEditor} the journal entry editor - * @param entryType {string} the entry type, either "debit" or "credit" + * @param lineItemEditor {VoucherLineItemEditor} the line item editor + * @param side {string} the side, either "debit" or "credit" */ - constructor(entryEditor, entryType) { - this.#entryEditor = entryEditor - this.#entryType = entryType; - this.#prefix = "accounting-account-selector-" + entryType; + constructor(lineItemEditor, side) { + this.#lineItemEditor = lineItemEditor + this.#side = side; + this.#prefix = "accounting-account-selector-" + side; this.#query = document.getElementById(this.#prefix + "-query"); this.#queryNoResult = document.getElementById(this.#prefix + "-option-no-result"); this.#optionList = document.getElementById(this.#prefix + "-option-list"); @@ -103,9 +103,9 @@ class AccountSelector { this.#more.classList.add("d-none"); this.#filterOptions(); }; - this.#clearButton.onclick = () => this.#entryEditor.clearAccount(); + this.#clearButton.onclick = () => this.#lineItemEditor.clearAccount(); for (const option of this.#options) { - option.onclick = () => this.#entryEditor.saveAccount(option.dataset.code, option.dataset.content, option.classList.contains("accounting-account-is-need-offset")); + option.onclick = () => this.#lineItemEditor.saveAccount(option.dataset.code, option.dataset.content, option.classList.contains("accounting-account-is-need-offset")); } this.#query.addEventListener("input", () => { this.#filterOptions(); @@ -143,9 +143,9 @@ class AccountSelector { * @return {string[]} the account codes that are used in the form */ #getCodesUsedInForm() { - const inUse = this.#entryEditor.form.getAccountCodesUsed(this.#entryType); - if (this.#entryEditor.accountCode !== null) { - inUse.push(this.#entryEditor.accountCode); + const inUse = this.#lineItemEditor.form.getAccountCodesUsed(this.#side); + if (this.#lineItemEditor.accountCode !== null) { + inUse.push(this.#lineItemEditor.accountCode); } return inUse } @@ -190,13 +190,13 @@ class AccountSelector { this.#more.classList.remove("d-none"); this.#filterOptions(); for (const option of this.#options) { - if (option.dataset.code === this.#entryEditor.accountCode) { + if (option.dataset.code === this.#lineItemEditor.accountCode) { option.classList.add("active"); } else { option.classList.remove("active"); } } - if (this.#entryEditor.accountCode === null) { + if (this.#lineItemEditor.accountCode === null) { this.#clearButton.classList.add("btn-secondary"); this.#clearButton.classList.remove("btn-danger"); this.#clearButton.disabled = true; @@ -210,14 +210,14 @@ class AccountSelector { /** * Returns the account selector instances. * - * @param entryEditor {JournalEntryEditor} the journal entry editor + * @param lineItemEditor {VoucherLineItemEditor} the line item editor * @return {{debit: AccountSelector, credit: AccountSelector}} */ - static getInstances(entryEditor) { + static getInstances(lineItemEditor) { const selectors = {} const modals = Array.from(document.getElementsByClassName("accounting-account-selector")); for (const modal of modals) { - selectors[modal.dataset.entryType] = new AccountSelector(entryEditor, modal.dataset.entryType); + selectors[modal.dataset.side] = new AccountSelector(lineItemEditor, modal.dataset.side); } return selectors; } diff --git a/src/accounting/static/js/original-entry-selector.js b/src/accounting/static/js/original-line-item-selector.js similarity index 67% rename from src/accounting/static/js/original-entry-selector.js rename to src/accounting/static/js/original-line-item-selector.js index 3bd0700..54ec92f 100644 --- a/src/accounting/static/js/original-entry-selector.js +++ b/src/accounting/static/js/original-line-item-selector.js @@ -1,5 +1,5 @@ /* The Mia! Accounting Flask Project - * original-entry-selector.js: The JavaScript for the original entry selector + * original-line-item-selector.js: The JavaScript for the original line item selector */ /* Copyright (c) 2023 imacat. @@ -23,22 +23,22 @@ "use strict"; /** - * The original entry selector. + * The original line item selector. * */ -class OriginalEntrySelector { +class OriginalLineItemSelector { /** - * The journal entry editor - * @type {JournalEntryEditor} + * The line item editor + * @type {VoucherLineItemEditor} */ - entryEditor; + lineItemEditor; /** * The prefix of the HTML ID and class * @type {string} */ - #prefix = "accounting-original-entry-selector"; + #prefix = "accounting-original-line-item-selector"; /** * The query input @@ -60,13 +60,13 @@ class OriginalEntrySelector { /** * The options - * @type {OriginalEntry[]} + * @type {OriginalLineItem[]} */ #options; /** * The options by their ID - * @type {Object.} + * @type {Object.} */ #optionById; @@ -77,21 +77,21 @@ class OriginalEntrySelector { #currencyCode; /** - * The entry + * The side, either "credit" or "debit" */ - #entryType; + #side; /** - * Constructs an original entry selector. + * Constructs an original line item selector. * - * @param entryEditor {JournalEntryEditor} the journal entry editor + * @param lineItemEditor {VoucherLineItemEditor} the line item editor */ - constructor(entryEditor) { - this.entryEditor = entryEditor; + constructor(lineItemEditor) { + this.lineItemEditor = lineItemEditor; this.#query = document.getElementById(this.#prefix + "-query"); this.#queryNoResult = document.getElementById(this.#prefix + "-option-no-result"); this.#optionList = document.getElementById(this.#prefix + "-option-list"); - this.#options = Array.from(document.getElementsByClassName(this.#prefix + "-option")).map((element) => new OriginalEntry(this, element)); + this.#options = Array.from(document.getElementsByClassName(this.#prefix + "-option")).map((element) => new OriginalLineItem(this, element)); this.#optionById = {}; for (const option of this.#options) { this.#optionById[option.id] = option; @@ -102,44 +102,44 @@ class OriginalEntrySelector { } /** - * Returns the net balance for an original entry. + * Returns the net balance for an original line item. * - * @param currentEntry {JournalEntrySubForm} the journal entry sub-form that is currently editing + * @param currentLineItem {LineItemSubForm} the line item sub-form that is currently editing * @param form {VoucherForm} the voucher form - * @param originalEntryId {string} the ID of the original entry - * @return {Decimal} the net balance of the original entry + * @param originalLineItemId {string} the ID of the original line item + * @return {Decimal} the net balance of the original line item */ - getNetBalance(currentEntry, form, originalEntryId) { - const otherEntries = form.getEntries().filter((entry) => entry !== currentEntry); + getNetBalance(currentLineItem, form, originalLineItemId) { + const otherLineItems = form.getLineItems().filter((lineItem) => lineItem !== currentLineItem); let otherOffset = new Decimal(0); - for (const otherEntry of otherEntries) { - if (otherEntry.getOriginalEntryId() === originalEntryId) { - const amount = otherEntry.getAmount(); + for (const otherLineItem of otherLineItems) { + if (otherLineItem.getOriginalLineItemId() === originalLineItemId) { + const amount = otherLineItem.getAmount(); if (amount !== null) { otherOffset = otherOffset.plus(amount); } } } - return this.#optionById[originalEntryId].bareNetBalance.minus(otherOffset); + return this.#optionById[originalLineItemId].bareNetBalance.minus(otherOffset); } /** - * Updates the net balances, subtracting the offset amounts on the form but the currently editing journal entry + * Updates the net balances, subtracting the offset amounts on the form but the currently editing line item * */ #updateNetBalances() { - const otherEntries = this.entryEditor.form.getEntries().filter((entry) => entry !== this.entryEditor.entry); + const otherLineItems = this.lineItemEditor.form.getLineItems().filter((lineItem) => lineItem !== this.lineItemEditor.lineItem); const otherOffsets = {} - for (const otherEntry of otherEntries) { - const otherOriginalEntryId = otherEntry.getOriginalEntryId(); - const amount = otherEntry.getAmount(); - if (otherOriginalEntryId === null || amount === null) { + for (const otherLineItem of otherLineItems) { + const otherOriginalLineItemId = otherLineItem.getOriginalLineItemId(); + const amount = otherLineItem.getAmount(); + if (otherOriginalLineItemId === null || amount === null) { continue; } - if (!(otherOriginalEntryId in otherOffsets)) { - otherOffsets[otherOriginalEntryId] = new Decimal("0"); + if (!(otherOriginalLineItemId in otherOffsets)) { + otherOffsets[otherOriginalLineItemId] = new Decimal("0"); } - otherOffsets[otherOriginalEntryId] = otherOffsets[otherOriginalEntryId].plus(amount); + otherOffsets[otherOriginalLineItemId] = otherOffsets[otherOriginalLineItemId].plus(amount); } for (const option of this.#options) { if (option.id in otherOffsets) { @@ -157,7 +157,7 @@ class OriginalEntrySelector { #filterOptions() { let hasAnyMatched = false; for (const option of this.#options) { - if (option.isMatched(this.#entryType, this.#currencyCode, this.#query.value)) { + if (option.isMatched(this.#side, this.#currencyCode, this.#query.value)) { option.setShown(true); hasAnyMatched = true; } else { @@ -174,14 +174,14 @@ class OriginalEntrySelector { } /** - * The callback when the original entry selector is shown. + * The callback when the original line item selector is shown. * */ onOpen() { - this.#currencyCode = this.entryEditor.getCurrencyCode(); - this.#entryType = this.entryEditor.entryType; + this.#currencyCode = this.lineItemEditor.getCurrencyCode(); + this.#side = this.lineItemEditor.side; for (const option of this.#options) { - option.setActive(option.id === this.entryEditor.originalEntryId); + option.setActive(option.id === this.lineItemEditor.originalLineItemId); } this.#query.value = ""; this.#updateNetBalances(); @@ -190,14 +190,14 @@ class OriginalEntrySelector { } /** - * An original entry. + * An original line item. * */ -class OriginalEntry { +class OriginalLineItem { /** - * The original entry selector - * @type {OriginalEntrySelector} + * The original line item selector + * @type {OriginalLineItemSelector} */ #selector; @@ -220,10 +220,10 @@ class OriginalEntry { date; /** - * The entry type, either "debit" or "credit" + * The side, either "debit" or "credit" * @type {string} */ - #entryType; + #side; /** * The currency code @@ -268,7 +268,7 @@ class OriginalEntry { netBalanceText; /** - * The text representation of the original entry + * The text representation of the original line item * @type {string} */ text; @@ -280,9 +280,9 @@ class OriginalEntry { #queryValues; /** - * Constructs an original entry. + * Constructs an original line item. * - * @param selector {OriginalEntrySelector} the original entry selector + * @param selector {OriginalLineItemSelector} the original line item selector * @param element {HTMLLIElement} the element */ constructor(selector, element) { @@ -290,17 +290,17 @@ class OriginalEntry { this.#element = element; this.id = element.dataset.id; this.date = element.dataset.date; - this.#entryType = element.dataset.entryType; + this.#side = element.dataset.side; this.#currencyCode = element.dataset.currencyCode; this.accountCode = element.dataset.accountCode; this.accountText = element.dataset.accountText; this.summary = element.dataset.summary; this.bareNetBalance = new Decimal(element.dataset.netBalance); this.netBalance = this.bareNetBalance; - this.netBalanceText = document.getElementById("accounting-original-entry-selector-option-" + this.id + "-net-balance"); + this.netBalanceText = document.getElementById("accounting-original-line-item-selector-option-" + this.id + "-net-balance"); this.text = element.dataset.text; this.#queryValues = JSON.parse(element.dataset.queryValues); - this.#element.onclick = () => this.#selector.entryEditor.saveOriginalEntry(this); + this.#element.onclick = () => this.#selector.lineItemEditor.saveOriginalLineItem(this); } /** @@ -335,31 +335,31 @@ class OriginalEntry { /** * Returns whether the original matches. * - * @param entryType {string} the entry type, either "debit" or "credit" + * @param side {string} the side, either "debit" or "credit" * @param currencyCode {string} the currency code * @param query {string|null} the query term */ - isMatched(entryType, currencyCode, query = null) { + isMatched(side, currencyCode, query = null) { return this.netBalance.greaterThan(0) - && this.date <= this.#selector.entryEditor.form.getDate() - && this.#isEntryTypeMatches(entryType) + && this.date <= this.#selector.lineItemEditor.form.getDate() + && this.#isSideMatches(side) && this.#currencyCode === currencyCode && this.#isQueryMatches(query); } /** - * Returns whether the original entry matches the entry type. + * Returns whether the original line item matches the debit or credit side. * - * @param entryType {string} the entry type, either "debit" or credit + * @param side {string} the side, either "debit" or credit * @return {boolean} true if the option matches, or false otherwise */ - #isEntryTypeMatches(entryType) { - return (entryType === "debit" && this.#entryType === "credit") - || (entryType === "credit" && this.#entryType === "debit"); + #isSideMatches(side) { + return (side === "debit" && this.#side === "credit") + || (side === "credit" && this.#side === "debit"); } /** - * Returns whether the original entry matches the query. + * Returns whether the original line item matches the query. * * @param query {string|null} the query term * @return {boolean} true if the option matches, or false otherwise diff --git a/src/accounting/static/js/summary-editor.js b/src/accounting/static/js/summary-editor.js index 9dc07e8..a08fe07 100644 --- a/src/accounting/static/js/summary-editor.js +++ b/src/accounting/static/js/summary-editor.js @@ -29,10 +29,10 @@ class SummaryEditor { /** - * The journal entry editor - * @type {JournalEntryEditor} + * The line item editor + * @type {VoucherLineItemEditor} */ - #entryEditor; + #lineItemEditor; /** * The summary editor form @@ -53,10 +53,10 @@ class SummaryEditor { #modal; /** - * The entry type, either "debit" or "credit" + * The side, either "debit" or "credit" * @type {string} */ - entryType; + side; /** * The current tab @@ -71,7 +71,7 @@ class SummaryEditor { summary; /** - * The button to the original entry selector + * The button to the original line item selector * @type {HTMLButtonElement} */ #offsetButton; @@ -109,13 +109,13 @@ class SummaryEditor { /** * Constructs a summary editor. * - * @param entryEditor {JournalEntryEditor} the journal entry editor - * @param entryType {string} the entry type, either "debit" or "credit" + * @param lineItemEditor {VoucherLineItemEditor} the line item editor + * @param side {string} the side, either "debit" or "credit" */ - constructor(entryEditor, entryType) { - this.#entryEditor = entryEditor; - this.entryType = entryType; - this.prefix = "accounting-summary-editor-" + entryType; + constructor(lineItemEditor, side) { + this.#lineItemEditor = lineItemEditor; + this.side = side; + this.prefix = "accounting-summary-editor-" + side; this.#form = document.getElementById(this.prefix); this.#modal = document.getElementById(this.prefix + "-modal"); this.summary = document.getElementById(this.prefix + "-summary"); @@ -132,7 +132,7 @@ class SummaryEditor { this.currentTab = this.tabPlanes.general; this.#initializeSuggestedAccounts(); this.summary.onchange = () => this.#onSummaryChange(); - this.#offsetButton.onclick = () => this.#entryEditor.originalEntrySelector.onOpen(); + this.#offsetButton.onclick = () => this.#lineItemEditor.originalLineItemSelector.onOpen(); this.#form.onsubmit = () => { if (this.currentTab.validate()) { this.#submit(); @@ -215,9 +215,9 @@ class SummaryEditor { #submit() { bootstrap.Modal.getOrCreateInstance(this.#modal).hide(); if (this.#selectedAccount !== null) { - this.#entryEditor.saveSummaryWithAccount(this.summary.value, this.#selectedAccount.dataset.code, this.#selectedAccount.dataset.text, this.#selectedAccount.classList.contains("accounting-account-is-need-offset")); + this.#lineItemEditor.saveSummaryWithAccount(this.summary.value, this.#selectedAccount.dataset.code, this.#selectedAccount.dataset.text, this.#selectedAccount.classList.contains("accounting-account-is-need-offset")); } else { - this.#entryEditor.saveSummary(this.summary.value); + this.#lineItemEditor.saveSummary(this.summary.value); } } @@ -227,7 +227,7 @@ class SummaryEditor { */ onOpen() { this.#reset(); - this.summary.value = this.#entryEditor.summary === null? "": this.#entryEditor.summary; + this.summary.value = this.#lineItemEditor.summary === null? "": this.#lineItemEditor.summary; this.#onSummaryChange(); } @@ -246,14 +246,14 @@ class SummaryEditor { /** * Returns the summary editor instances. * - * @param entryEditor {JournalEntryEditor} the journal entry editor + * @param lineItemEditor {VoucherLineItemEditor} the line item editor * @return {{debit: SummaryEditor, credit: SummaryEditor}} */ - static getInstances(entryEditor) { + static getInstances(lineItemEditor) { const editors = {} const forms = Array.from(document.getElementsByClassName("accounting-summary-editor")); for (const form of forms) { - editors[form.dataset.entryType] = new SummaryEditor(entryEditor, form.dataset.entryType); + editors[form.dataset.side] = new SummaryEditor(lineItemEditor, form.dataset.side); } return editors; } diff --git a/src/accounting/static/js/voucher-form.js b/src/accounting/static/js/voucher-form.js index 5b7ac9c..72fe0d5 100644 --- a/src/accounting/static/js/voucher-form.js +++ b/src/accounting/static/js/voucher-form.js @@ -39,10 +39,10 @@ class VoucherForm { #element; /** - * The template to add a new journal entry + * The template to add a new line item * @type {string} */ - entryTemplate; + lineItemTemplate; /** * The date @@ -99,10 +99,10 @@ class VoucherForm { #noteError; /** - * The journal entry editor - * @type {JournalEntryEditor} + * The line item editor + * @type {VoucherLineItemEditor} */ - entryEditor; + lineItemEditor; /** * Constructs the voucher form. @@ -110,7 +110,7 @@ class VoucherForm { */ constructor() { this.#element = document.getElementById("accounting-form"); - this.entryTemplate = this.#element.dataset.entryTemplate; + this.lineItemTemplate = this.#element.dataset.lineItemTemplate; this.#date = document.getElementById("accounting-date"); this.#dateError = document.getElementById("accounting-date-error"); this.#currencyControl = document.getElementById("accounting-currencies"); @@ -121,7 +121,7 @@ class VoucherForm { this.#addCurrencyButton = document.getElementById("accounting-add-currency"); this.#note = document.getElementById("accounting-note"); this.#noteError = document.getElementById("accounting-note-error"); - this.entryEditor = new JournalEntryEditor(this); + this.lineItemEditor = new VoucherLineItemEditor(this); this.#addCurrencyButton.onclick = () => { const newIndex = 1 + (this.#currencies.length === 0? 0: Math.max(...this.#currencies.map((currency) => currency.index))); @@ -162,14 +162,14 @@ class VoucherForm { this.#currencies[0].deleteButton.classList.add("d-none"); } else { for (const currency of this.#currencies) { - let isAnyEntryMatched = false; - for (const entry of currency.getEntries()) { - if (entry.isMatched) { - isAnyEntryMatched = true; + let isAnyLineItemMatched = false; + for (const lineItem of currency.getLineItems()) { + if (lineItem.isMatched) { + isAnyLineItemMatched = true; break; } } - if (isAnyEntryMatched) { + if (isAnyLineItemMatched) { currency.deleteButton.classList.add("d-none"); } else { currency.deleteButton.classList.remove("d-none"); @@ -193,27 +193,27 @@ class VoucherForm { } /** - * Returns all the journal entries in the form. + * Returns all the line items in the form. * - * @param entryType {string|null} the entry type, either "debit" or "credit", or null for both - * @return {JournalEntrySubForm[]} all the journal entry sub-forms + * @param side {string|null} the side, either "debit" or "credit", or null for both + * @return {LineItemSubForm[]} all the line item sub-forms */ - getEntries(entryType = null) { - const entries = []; + getLineItems(side = null) { + const lineItems = []; for (const currency of this.#currencies) { - entries.push(...currency.getEntries(entryType)); + lineItems.push(...currency.getLineItems(side)); } - return entries; + return lineItems; } /** * Returns the account codes used in the form. * - * @param entryType {string} the entry type, either "debit" or "credit" + * @param side {string} the side, either "debit" or "credit" * @return {string[]} the account codes used in the form */ - getAccountCodesUsed(entryType) { - return this.getEntries(entryType).map((entry) => entry.getAccountCode()) + getAccountCodesUsed(side) { + return this.getLineItems(side).map((lineItem) => lineItem.getAccountCode()) .filter((code) => code !== null); } @@ -231,16 +231,16 @@ class VoucherForm { * */ updateMinDate() { - let lastOriginalEntryDate = null; - for (const entry of this.getEntries()) { - const date = entry.getOriginalEntryDate(); + let lastOriginalLineItemDate = null; + for (const lineItem of this.getLineItems()) { + const date = lineItem.getOriginalLineItemDate(); if (date !== null) { - if (lastOriginalEntryDate === null || lastOriginalEntryDate < date) { - lastOriginalEntryDate = date; + if (lastOriginalLineItemDate === null || lastOriginalLineItemDate < date) { + lastOriginalLineItemDate = date; } } } - this.#date.min = lastOriginalEntryDate === null? "": lastOriginalEntryDate; + this.#date.min = lastOriginalLineItemDate === null? "": lastOriginalLineItemDate; this.#validateDate(); } @@ -272,7 +272,7 @@ class VoucherForm { } if (this.#date.value < this.#date.min) { this.#date.classList.add("is-invalid"); - this.#dateError.innerText = A_("The date cannot be earlier than the original entries."); + this.#dateError.innerText = A_("The date cannot be earlier than the original line items."); return false; } this.#date.classList.remove("is-invalid"); @@ -407,13 +407,13 @@ class CurrencySubForm { /** * The debit side - * @type {DebitCreditSideSubForm|null} + * @type {SideSubForm|null} */ #debit; /** * The credit side - * @type {DebitCreditSideSubForm|null} + * @type {SideSubForm|null} */ #credit; @@ -435,9 +435,9 @@ class CurrencySubForm { this.#codeSelect = document.getElementById(this.#prefix + "-code-select"); this.deleteButton = document.getElementById(this.#prefix + "-delete"); const debitElement = document.getElementById(this.#prefix + "-debit"); - this.#debit = debitElement === null? null: new DebitCreditSideSubForm(this, debitElement, "debit"); + this.#debit = debitElement === null? null: new SideSubForm(this, debitElement, "debit"); const creditElement = document.getElementById(this.#prefix + "-credit"); - this.#credit = creditElement == null? null: new DebitCreditSideSubForm(this, creditElement, "credit"); + this.#credit = creditElement == null? null: new SideSubForm(this, creditElement, "credit"); this.#codeSelect.onchange = () => this.#code.value = this.#codeSelect.value; this.deleteButton.onclick = () => { this.element.parentElement.removeChild(this.element); @@ -455,21 +455,21 @@ class CurrencySubForm { } /** - * Returns all the journal entries in the form. + * Returns all the line items in the form. * - * @param entryType {string|null} the entry type, either "debit" or "credit", or null for both - * @return {JournalEntrySubForm[]} all the journal entry sub-forms + * @param side {string|null} the side, either "debit" or "credit", or null for both + * @return {LineItemSubForm[]} all the line item sub-forms */ - getEntries(entryType = null) { - const entries = [] - for (const side of [this.#debit, this.#credit]) { - if (side !== null ) { - if (entryType === null || side.entryType === entryType) { - entries.push(...side.entries); + getLineItems(side = null) { + const lineItems = [] + for (const sideSubForm of [this.#debit, this.#credit]) { + if (sideSubForm !== null ) { + if (side === null || sideSubForm.side === side) { + lineItems.push(...sideSubForm.lineItems); } } } - return entries; + return lineItems; } /** @@ -478,8 +478,8 @@ class CurrencySubForm { */ updateCodeSelectorStatus() { let isEnabled = true; - for (const entry of this.getEntries()) { - if (entry.getOriginalEntryId() !== null) { + for (const lineItem of this.getLineItems()) { + if (lineItem.getOriginalLineItemId() !== null) { isEnabled = false; break; } @@ -527,7 +527,7 @@ class CurrencySubForm { * The debit or credit side sub-form * */ -class DebitCreditSideSubForm { +class SideSubForm { /** * The currency sub-form @@ -548,10 +548,10 @@ class DebitCreditSideSubForm { #currencyIndex; /** - * The entry type, either "debit" or "credit" + * The side, either "debit" or "credit" * @type {string} */ - entryType; + side; /** * The prefix of the HTML ID and class @@ -566,16 +566,16 @@ class DebitCreditSideSubForm { #error; /** - * The journal entry list + * The line item list * @type {HTMLUListElement} */ - #entryList; + #lineItemList; /** - * The journal entry sub-forms - * @type {JournalEntrySubForm[]} + * The line item sub-forms + * @type {LineItemSubForm[]} */ - entries; + lineItems; /** * The total @@ -584,82 +584,82 @@ class DebitCreditSideSubForm { #total; /** - * The button to add a new entry + * The button to add a new line item * @type {HTMLButtonElement} */ - #addEntryButton; + #addLineItemButton; /** * Constructs a debit or credit side sub-form * * @param currency {CurrencySubForm} the currency sub-form * @param element {HTMLDivElement} the element - * @param entryType {string} the entry type, either "debit" or "credit" + * @param side {string} the side, either "debit" or "credit" */ - constructor(currency, element, entryType) { + constructor(currency, element, side) { this.currency = currency; this.#element = element; this.#currencyIndex = currency.index; - this.entryType = entryType; - this.#prefix = "accounting-currency-" + String(this.#currencyIndex) + "-" + entryType; + this.side = side; + this.#prefix = "accounting-currency-" + String(this.#currencyIndex) + "-" + side; this.#error = document.getElementById(this.#prefix + "-error"); - this.#entryList = document.getElementById(this.#prefix + "-list"); + this.#lineItemList = document.getElementById(this.#prefix + "-list"); // noinspection JSValidateTypes - this.entries = Array.from(document.getElementsByClassName(this.#prefix)).map((element) => new JournalEntrySubForm(this, element)); + this.lineItems = Array.from(document.getElementsByClassName(this.#prefix)).map((element) => new LineItemSubForm(this, element)); this.#total = document.getElementById(this.#prefix + "-total"); - this.#addEntryButton = document.getElementById(this.#prefix + "-add-entry"); - this.#addEntryButton.onclick = () => this.currency.form.entryEditor.onAddNew(this); - this.#resetDeleteJournalEntryButtons(); + this.#addLineItemButton = document.getElementById(this.#prefix + "-add-line-item"); + this.#addLineItemButton.onclick = () => this.currency.form.lineItemEditor.onAddNew(this); + this.#resetDeleteLineItemButtons(); this.#initializeDragAndDropReordering(); } /** - * Adds a new journal entry sub-form + * Adds a new line item sub-form * - * @returns {JournalEntrySubForm} the newly-added journal entry sub-form + * @returns {LineItemSubForm} the newly-added line item sub-form */ - addJournalEntry() { - const newIndex = 1 + (this.entries.length === 0? 0: Math.max(...this.entries.map((entry) => entry.entryIndex))); - const html = this.currency.form.entryTemplate + addLineItem() { + const newIndex = 1 + (this.lineItems.length === 0? 0: Math.max(...this.lineItems.map((lineItem) => lineItem.lineItemIndex))); + const html = this.currency.form.lineItemTemplate .replaceAll("CURRENCY_INDEX", escapeHtml(String(this.#currencyIndex))) - .replaceAll("ENTRY_TYPE", escapeHtml(this.entryType)) - .replaceAll("ENTRY_INDEX", escapeHtml(String(newIndex))); - this.#entryList.insertAdjacentHTML("beforeend", html); - const entry = new JournalEntrySubForm(this, document.getElementById(this.#prefix + "-" + String(newIndex))); - this.entries.push(entry); - this.#resetDeleteJournalEntryButtons(); + .replaceAll("SIDE", escapeHtml(this.side)) + .replaceAll("LINE_ITEM_INDEX", escapeHtml(String(newIndex))); + this.#lineItemList.insertAdjacentHTML("beforeend", html); + const lineItem = new LineItemSubForm(this, document.getElementById(this.#prefix + "-" + String(newIndex))); + this.lineItems.push(lineItem); + this.#resetDeleteLineItemButtons(); this.#initializeDragAndDropReordering(); this.validate(); - return entry; + return lineItem; } /** - * Deletes a journal entry sub-form + * Deletes a line item sub-form * - * @param entry {JournalEntrySubForm} + * @param lineItem {LineItemSubForm} */ - deleteJournalEntry(entry) { - const index = this.entries.indexOf(entry); - this.entries.splice(index, 1); + deleteLineItem(lineItem) { + const index = this.lineItems.indexOf(lineItem); + this.lineItems.splice(index, 1); this.updateTotal(); this.currency.updateCodeSelectorStatus(); this.currency.form.updateMinDate(); - this.#resetDeleteJournalEntryButtons(); + this.#resetDeleteLineItemButtons(); } /** - * Resets the buttons to delete the journal entry sub-forms + * Resets the buttons to delete the line item sub-forms * */ - #resetDeleteJournalEntryButtons() { - if (this.entries.length === 1) { - this.entries[0].deleteButton.classList.add("d-none"); + #resetDeleteLineItemButtons() { + if (this.lineItems.length === 1) { + this.lineItems[0].deleteButton.classList.add("d-none"); } else { - for (const entry of this.entries) { - if (entry.isMatched) { - entry.deleteButton.classList.add("d-none"); + for (const lineItem of this.lineItems) { + if (lineItem.isMatched) { + lineItem.deleteButton.classList.add("d-none"); } else { - entry.deleteButton.classList.remove("d-none"); + lineItem.deleteButton.classList.remove("d-none"); } } } @@ -672,8 +672,8 @@ class DebitCreditSideSubForm { */ getTotal() { let total = new Decimal("0"); - for (const entry of this.entries) { - const amount = entry.getAmount(); + for (const lineItem of this.lineItems) { + const amount = lineItem.getAmount(); if (amount !== null) { total = total.plus(amount); } @@ -695,11 +695,11 @@ class DebitCreditSideSubForm { * */ #initializeDragAndDropReordering() { - initializeDragAndDropReordering(this.#entryList, () => { - const entryId = Array.from(this.#entryList.children).map((entry) => entry.id); - this.entries.sort((a, b) => entryId.indexOf(a.element.id) - entryId.indexOf(b.element.id)); - for (let i = 0; i < this.entries.length; i++) { - this.entries[i].no.value = String(i + 1); + initializeDragAndDropReordering(this.#lineItemList, () => { + const lineItemId = Array.from(this.#lineItemList.children).map((lineItem) => lineItem.id); + this.lineItems.sort((a, b) => lineItemId.indexOf(a.element.id) - lineItemId.indexOf(b.element.id)); + for (let i = 0; i < this.lineItems.length; i++) { + this.lineItems[i].no.value = String(i + 1); } }); } @@ -712,8 +712,8 @@ class DebitCreditSideSubForm { validate() { let isValid = true; isValid = this.#validateReal() && isValid; - for (const entry of this.entries) { - isValid = entry.validate() && isValid; + for (const lineItem of this.lineItems) { + isValid = lineItem.validate() && isValid; } return isValid; } @@ -724,9 +724,9 @@ class DebitCreditSideSubForm { * @returns {boolean} true if valid, or false otherwise */ #validateReal() { - if (this.entries.length === 0) { + if (this.lineItems.length === 0) { this.#element.classList.add("is-invalid"); - this.#error.innerText = A_("Please add some journal entries."); + this.#error.innerText = A_("Please add some line items."); return false; } this.#element.classList.remove("is-invalid"); @@ -736,16 +736,16 @@ class DebitCreditSideSubForm { } /** - * The journal entry sub-form. + * The line item sub-form. * */ -class JournalEntrySubForm { +class LineItemSubForm { /** - * The debit or credit entry side sub-form - * @type {DebitCreditSideSubForm} + * The debit or credit side sub-form + * @type {SideSubForm} */ - side; + sideSubForm; /** * The element @@ -754,19 +754,19 @@ class JournalEntrySubForm { element; /** - * The entry type, either "debit" or "credit" + * The side, either "debit" or "credit" * @type {string} */ - entryType; + side; /** - * The entry index + * The line item index * @type {number} */ - entryIndex; + lineItemIndex; /** - * Whether this is an original entry with offsets + * Whether this is an original line item with offsets * @type {boolean} */ isMatched; @@ -820,19 +820,19 @@ class JournalEntrySubForm { #summaryText; /** - * The ID of the original entry + * The ID of the original line item * @type {HTMLInputElement} */ - #originalEntryId; + #originalLineItemId; /** - * The text of the original entry + * The text of the original line item * @type {HTMLDivElement} */ - #originalEntryText; + #originalLineItemText; /** - * The offset entries + * The offset items * @type {HTMLInputElement} */ #offsets; @@ -850,24 +850,24 @@ class JournalEntrySubForm { #amountText; /** - * The button to delete journal entry + * The button to delete line item * @type {HTMLButtonElement} */ deleteButton; /** - * Constructs the journal entry sub-form. + * Constructs the line item sub-form. * - * @param side {DebitCreditSideSubForm} the debit or credit entry side sub-form + * @param side {SideSubForm} the debit or credit side sub-form * @param element {HTMLLIElement} the element */ constructor(side, element) { - this.side = side; + this.sideSubForm = side; this.element = element; - this.entryType = element.dataset.entryType; - this.entryIndex = parseInt(element.dataset.entryIndex); - this.isMatched = element.classList.contains("accounting-matched-entry"); - this.#prefix = "accounting-currency-" + element.dataset.currencyIndex + "-" + this.entryType + "-" + this.entryIndex; + this.side = element.dataset.side; + this.lineItemIndex = parseInt(element.dataset.lineItemIndex); + this.isMatched = element.classList.contains("accounting-matched-line-item"); + this.#prefix = "accounting-currency-" + element.dataset.currencyIndex + "-" + this.side + "-" + this.lineItemIndex; this.#control = document.getElementById(this.#prefix + "-control"); this.#error = document.getElementById(this.#prefix + "-error"); this.no = document.getElementById(this.#prefix + "-no"); @@ -875,53 +875,53 @@ class JournalEntrySubForm { this.#accountText = document.getElementById(this.#prefix + "-account-text"); this.#summary = document.getElementById(this.#prefix + "-summary"); this.#summaryText = document.getElementById(this.#prefix + "-summary-text"); - this.#originalEntryId = document.getElementById(this.#prefix + "-original-entry-id"); - this.#originalEntryText = document.getElementById(this.#prefix + "-original-entry-text"); + this.#originalLineItemId = document.getElementById(this.#prefix + "-original-line-item-id"); + this.#originalLineItemText = document.getElementById(this.#prefix + "-original-line-item-text"); this.#offsets = document.getElementById(this.#prefix + "-offsets"); this.#amount = document.getElementById(this.#prefix + "-amount"); this.#amountText = document.getElementById(this.#prefix + "-amount-text"); this.deleteButton = document.getElementById(this.#prefix + "-delete"); - this.#control.onclick = () => this.side.currency.form.entryEditor.onEdit(this); + this.#control.onclick = () => this.sideSubForm.currency.form.lineItemEditor.onEdit(this); this.deleteButton.onclick = () => { this.element.parentElement.removeChild(this.element); - this.side.deleteJournalEntry(this); + this.sideSubForm.deleteLineItem(this); }; } /** - * Returns whether the entry is an original entry. + * Returns whether the line item needs offset. * - * @return {boolean} true if the entry is an original entry, or false otherwise + * @return {boolean} true if the line item needs offset, or false otherwise */ isNeedOffset() { return "isNeedOffset" in this.element.dataset; } /** - * Returns the ID of the original entry. + * Returns the ID of the original line item. * - * @return {string|null} the ID of the original entry + * @return {string|null} the ID of the original line item */ - getOriginalEntryId() { - return this.#originalEntryId.value === ""? null: this.#originalEntryId.value; + getOriginalLineItemId() { + return this.#originalLineItemId.value === ""? null: this.#originalLineItemId.value; } /** - * Returns the date of the original entry. + * Returns the date of the original line item. * - * @return {string|null} the date of the original entry + * @return {string|null} the date of the original line item */ - getOriginalEntryDate() { - return this.#originalEntryId.dataset.date === ""? null: this.#originalEntryId.dataset.date; + getOriginalLineItemDate() { + return this.#originalLineItemId.dataset.date === ""? null: this.#originalLineItemId.dataset.date; } /** - * Returns the text of the original entry. + * Returns the text of the original line item. * - * @return {string|null} the text of the original entry + * @return {string|null} the text of the original line item */ - getOriginalEntryText() { - return this.#originalEntryId.dataset.text === ""? null: this.#originalEntryId.dataset.text; + getOriginalLineItemText() { + return this.#originalLineItemId.dataset.text === ""? null: this.#originalLineItemId.dataset.text; } /** @@ -991,9 +991,9 @@ class JournalEntrySubForm { } /** - * Stores the data into the journal entry sub-form. + * Stores the data into the line item sub-form. * - * @param editor {JournalEntryEditor} the journal entry editor + * @param editor {VoucherLineItemEditor} the line item editor */ save(editor) { if (editor.isNeedOffset) { @@ -1001,15 +1001,15 @@ class JournalEntrySubForm { } else { this.#offsets.classList.add("d-none"); } - this.#originalEntryId.value = editor.originalEntryId === null? "": editor.originalEntryId; - this.#originalEntryId.dataset.date = editor.originalEntryDate === null? "": editor.originalEntryDate; - this.#originalEntryId.dataset.text = editor.originalEntryText === null? "": editor.originalEntryText; - if (editor.originalEntryText === null) { - this.#originalEntryText.classList.add("d-none"); - this.#originalEntryText.innerText = ""; + this.#originalLineItemId.value = editor.originalLineItemId === null? "": editor.originalLineItemId; + this.#originalLineItemId.dataset.date = editor.originalLineItemDate === null? "": editor.originalLineItemDate; + this.#originalLineItemId.dataset.text = editor.originalLineItemText === null? "": editor.originalLineItemText; + if (editor.originalLineItemText === null) { + this.#originalLineItemText.classList.add("d-none"); + this.#originalLineItemText.innerText = ""; } else { - this.#originalEntryText.classList.remove("d-none"); - this.#originalEntryText.innerText = A_("Offset %(entry)s", {entry: editor.originalEntryText}); + this.#originalLineItemText.classList.remove("d-none"); + this.#originalLineItemText.innerText = A_("Offset %(item)s", {item: editor.originalLineItemText}); } this.#accountCode.value = editor.accountCode === null? "": editor.accountCode; this.#accountCode.dataset.text = editor.accountText === null? "": editor.accountText; @@ -1019,9 +1019,9 @@ class JournalEntrySubForm { this.#amount.value = editor.amount; this.#amountText.innerText = formatDecimal(new Decimal(editor.amount)); this.validate(); - this.side.updateTotal(); - this.side.currency.updateCodeSelectorStatus(); - this.side.currency.form.updateMinDate(); + this.sideSubForm.updateTotal(); + this.sideSubForm.currency.updateCodeSelectorStatus(); + this.sideSubForm.currency.form.updateMinDate(); } } diff --git a/src/accounting/static/js/journal-entry-editor.js b/src/accounting/static/js/voucher-line-item-editor.js similarity index 66% rename from src/accounting/static/js/journal-entry-editor.js rename to src/accounting/static/js/voucher-line-item-editor.js index ef488b8..6cf00e4 100644 --- a/src/accounting/static/js/journal-entry-editor.js +++ b/src/accounting/static/js/voucher-line-item-editor.js @@ -1,5 +1,5 @@ /* The Mia! Accounting Flask Project - * journal-entry-editor.js: The JavaScript for the journal entry editor + * voucher-line-item-editor.js: The JavaScript for the voucher line item editor */ /* Copyright (c) 2023 imacat. @@ -23,10 +23,10 @@ "use strict"; /** - * The journal entry editor. + * The voucher line item editor. * */ -class JournalEntryEditor { +class VoucherLineItemEditor { /** * The voucher form @@ -35,7 +35,7 @@ class JournalEntryEditor { form; /** - * The journal entry editor + * The voucher line item editor * @type {HTMLFormElement} */ #element; @@ -47,46 +47,46 @@ class JournalEntryEditor { #modal; /** - * The entry type, either "debit" or "credit" + * The side, either "debit" or "credit" * @type {string} */ - entryType; + side; /** * The prefix of the HTML ID and class * @type {string} */ - #prefix = "accounting-entry-editor" + #prefix = "accounting-line-item-editor" /** - * The container of the original entry + * The container of the original line item * @type {HTMLDivElement} */ - #originalEntryContainer; + #originalLineItemContainer; /** - * The control of the original entry + * The control of the original line item * @type {HTMLDivElement} */ - #originalEntryControl; + #originalLineItemControl; /** - * The original entry + * The original line item * @type {HTMLDivElement} */ - #originalEntryText; + #originalLineItemText; /** - * The error message of the original entry + * The error message of the original line item * @type {HTMLDivElement} */ - #originalEntryError; + #originalLineItemError; /** - * The delete button of the original entry + * The delete button of the original line item * @type {HTMLButtonElement} */ - #originalEntryDelete; + #originalLineItemDelete; /** * The control of the summary @@ -137,40 +137,40 @@ class JournalEntryEditor { #amountError; /** - * The journal entry to edit - * @type {JournalEntrySubForm|null} + * The voucher line item to edit + * @type {LineItemSubForm|null} */ - entry; + lineItem; /** - * The debit or credit entry side sub-form - * @type {DebitCreditSideSubForm} + * The debit or credit side sub-form + * @type {SideSubForm} */ - #side; + #sideSubForm; /** - * Whether the journal entry needs offset + * Whether the voucher line item needs offset * @type {boolean} */ isNeedOffset = false; /** - * The ID of the original entry + * The ID of the original line item * @type {string|null} */ - originalEntryId = null; + originalLineItemId = null; /** - * The date of the original entry + * The date of the original line item * @type {string|null} */ - originalEntryDate = null; + originalLineItemDate = null; /** - * The text of the original entry + * The text of the original line item * @type {string|null} */ - originalEntryText = null; + originalLineItemText = null; /** * The account code @@ -209,13 +209,13 @@ class JournalEntryEditor { #accountSelectors; /** - * The original entry selector - * @type {OriginalEntrySelector} + * The original line item selector + * @type {OriginalLineItemSelector} */ - originalEntrySelector; + originalLineItemSelector; /** - * Constructs a new journal entry editor. + * Constructs a new voucher line item editor. * * @param form {VoucherForm} the voucher form */ @@ -223,11 +223,11 @@ class JournalEntryEditor { this.form = form; this.#element = document.getElementById(this.#prefix); this.#modal = document.getElementById(this.#prefix + "-modal"); - this.#originalEntryContainer = document.getElementById(this.#prefix + "-original-entry-container"); - this.#originalEntryControl = document.getElementById(this.#prefix + "-original-entry-control"); - this.#originalEntryText = document.getElementById(this.#prefix + "-original-entry"); - this.#originalEntryError = document.getElementById(this.#prefix + "-original-entry-error"); - this.#originalEntryDelete = document.getElementById(this.#prefix + "-original-entry-delete"); + this.#originalLineItemContainer = document.getElementById(this.#prefix + "-original-line-item-container"); + this.#originalLineItemControl = document.getElementById(this.#prefix + "-original-line-item-control"); + this.#originalLineItemText = document.getElementById(this.#prefix + "-original-line-item"); + this.#originalLineItemError = document.getElementById(this.#prefix + "-original-line-item-error"); + this.#originalLineItemDelete = document.getElementById(this.#prefix + "-original-line-item-delete"); this.#summaryControl = document.getElementById(this.#prefix + "-summary-control"); this.#summaryText = document.getElementById(this.#prefix + "-summary"); this.#summaryError = document.getElementById(this.#prefix + "-summary-error"); @@ -238,19 +238,19 @@ class JournalEntryEditor { this.#amountError = document.getElementById(this.#prefix + "-amount-error"); this.#summaryEditors = SummaryEditor.getInstances(this); this.#accountSelectors = AccountSelector.getInstances(this); - this.originalEntrySelector = new OriginalEntrySelector(this); - this.#originalEntryControl.onclick = () => this.originalEntrySelector.onOpen() - this.#originalEntryDelete.onclick = () => this.clearOriginalEntry(); - this.#summaryControl.onclick = () => this.#summaryEditors[this.entryType].onOpen(); - this.#accountControl.onclick = () => this.#accountSelectors[this.entryType].onOpen(); + this.originalLineItemSelector = new OriginalLineItemSelector(this); + this.#originalLineItemControl.onclick = () => this.originalLineItemSelector.onOpen() + this.#originalLineItemDelete.onclick = () => this.clearOriginalLineItem(); + this.#summaryControl.onclick = () => this.#summaryEditors[this.side].onOpen(); + this.#accountControl.onclick = () => this.#accountSelectors[this.side].onOpen(); this.#amountInput.onchange = () => this.#validateAmount(); this.#element.onsubmit = () => { if (this.#validate()) { - if (this.entry === null) { - this.entry = this.#side.addJournalEntry(); + if (this.lineItem === null) { + this.lineItem = this.#sideSubForm.addLineItem(); } this.amount = this.#amountInput.value; - this.entry.save(this); + this.lineItem.save(this); bootstrap.Modal.getInstance(this.#modal).hide(); } return false; @@ -258,48 +258,48 @@ class JournalEntryEditor { } /** - * Saves the original entry from the original entry selector. + * Saves the original line item from the original line item selector. * - * @param originalEntry {OriginalEntry} the original entry + * @param originalLineItem {OriginalLineItem} the original line item */ - saveOriginalEntry(originalEntry) { + saveOriginalLineItem(originalLineItem) { this.isNeedOffset = false; - this.#originalEntryContainer.classList.remove("d-none"); - this.#originalEntryControl.classList.add("accounting-not-empty"); - this.originalEntryId = originalEntry.id; - this.originalEntryDate = originalEntry.date; - this.originalEntryText = originalEntry.text; - this.#originalEntryText.innerText = originalEntry.text; + this.#originalLineItemContainer.classList.remove("d-none"); + this.#originalLineItemControl.classList.add("accounting-not-empty"); + this.originalLineItemId = originalLineItem.id; + this.originalLineItemDate = originalLineItem.date; + this.originalLineItemText = originalLineItem.text; + this.#originalLineItemText.innerText = originalLineItem.text; this.#setEnableSummaryAccount(false); - if (originalEntry.summary === "") { + if (originalLineItem.summary === "") { this.#summaryControl.classList.remove("accounting-not-empty"); } else { this.#summaryControl.classList.add("accounting-not-empty"); } - this.summary = originalEntry.summary === ""? null: originalEntry.summary; - this.#summaryText.innerText = originalEntry.summary; + this.summary = originalLineItem.summary === ""? null: originalLineItem.summary; + this.#summaryText.innerText = originalLineItem.summary; this.#accountControl.classList.add("accounting-not-empty"); - this.accountCode = originalEntry.accountCode; - this.accountText = originalEntry.accountText; - this.#accountText.innerText = originalEntry.accountText; - this.#amountInput.value = String(originalEntry.netBalance); - this.#amountInput.max = String(originalEntry.netBalance); + this.accountCode = originalLineItem.accountCode; + this.accountText = originalLineItem.accountText; + this.#accountText.innerText = originalLineItem.accountText; + this.#amountInput.value = String(originalLineItem.netBalance); + this.#amountInput.max = String(originalLineItem.netBalance); this.#amountInput.min = "0"; this.#validate(); } /** - * Clears the original entry. + * Clears the original line item. * */ - clearOriginalEntry() { + clearOriginalLineItem() { this.isNeedOffset = false; - this.#originalEntryContainer.classList.add("d-none"); - this.#originalEntryControl.classList.remove("accounting-not-empty"); - this.originalEntryId = null; - this.originalEntryDate = null; - this.originalEntryText = null; - this.#originalEntryText.innerText = ""; + this.#originalLineItemContainer.classList.add("d-none"); + this.#originalLineItemControl.classList.remove("accounting-not-empty"); + this.originalLineItemId = null; + this.originalLineItemDate = null; + this.originalLineItemText = null; + this.#originalLineItemText.innerText = ""; this.#setEnableSummaryAccount(true); this.#accountControl.classList.remove("accounting-not-empty"); this.accountCode = null; @@ -314,7 +314,7 @@ class JournalEntryEditor { * @return {string} the currency code */ getCurrencyCode() { - return this.#side.currency.getCurrencyCode(); + return this.#sideSubForm.currency.getCurrencyCode(); } /** @@ -340,7 +340,7 @@ class JournalEntryEditor { * @param summary {string} the summary * @param accountCode {string} the account code * @param accountText {string} the account text - * @param isAccountNeedOffset {boolean} true if the journal entries in the account need offset, or false otherwise + * @param isAccountNeedOffset {boolean} true if the line items in the account need offset, or false otherwise */ saveSummaryWithAccount(summary, accountCode, accountText, isAccountNeedOffset) { this.isNeedOffset = isAccountNeedOffset; @@ -370,7 +370,7 @@ class JournalEntryEditor { * * @param code {string} the account code * @param text {string} the account text - * @param isNeedOffset {boolean} true if the journal entries in the account need offset or false otherwise + * @param isNeedOffset {boolean} true if the line items in the account need offset or false otherwise */ saveAccount(code, text, isNeedOffset) { this.isNeedOffset = isNeedOffset; @@ -388,7 +388,7 @@ class JournalEntryEditor { */ #validate() { let isValid = true; - isValid = this.#validateOriginalEntry() && isValid; + isValid = this.#validateOriginalLineItem() && isValid; isValid = this.#validateSummary() && isValid; isValid = this.#validateAccount() && isValid; isValid = this.#validateAmount() && isValid @@ -396,14 +396,14 @@ class JournalEntryEditor { } /** - * Validates the original entry. + * Validates the original line item. * * @return {boolean} true if valid, or false otherwise * @private */ - #validateOriginalEntry() { - this.#originalEntryControl.classList.remove("is-invalid"); - this.#originalEntryError.innerText = ""; + #validateOriginalLineItem() { + this.#originalLineItemControl.classList.remove("is-invalid"); + this.#originalLineItemError.innerText = ""; return true; } @@ -458,7 +458,7 @@ class JournalEntryEditor { if (this.#amountInput.max !== "") { if (amount.greaterThan(new Decimal(this.#amountInput.max))) { this.#amountInput.classList.add("is-invalid"); - this.#amountError.innerText = A_("The amount must not exceed the net balance %(balance)s of the original entry.", {balance: new Decimal(this.#amountInput.max)}); + this.#amountError.innerText = A_("The amount must not exceed the net balance %(balance)s of the original line item.", {balance: new Decimal(this.#amountInput.max)}); return false; } } @@ -476,22 +476,22 @@ class JournalEntryEditor { } /** - * The callback when adding a new journal entry. + * The callback when adding a new voucher line item. * - * @param side {DebitCreditSideSubForm} the debit or credit side sub-form + * @param side {SideSubForm} the debit or credit side sub-form */ onAddNew(side) { - this.entry = null; - this.#side = side; - this.entryType = this.#side.entryType; + this.lineItem = null; + this.#sideSubForm = side; + this.side = this.#sideSubForm.side; this.isNeedOffset = false; - this.#originalEntryContainer.classList.add("d-none"); - this.#originalEntryControl.classList.remove("accounting-not-empty"); - this.#originalEntryControl.classList.remove("is-invalid"); - this.originalEntryId = null; - this.originalEntryDate = null; - this.originalEntryText = null; - this.#originalEntryText.innerText = ""; + this.#originalLineItemContainer.classList.add("d-none"); + this.#originalLineItemControl.classList.remove("accounting-not-empty"); + this.#originalLineItemControl.classList.remove("is-invalid"); + this.originalLineItemId = null; + this.originalLineItemDate = null; + this.originalLineItemText = null; + this.#originalLineItemText.innerText = ""; this.#setEnableSummaryAccount(true); this.#summaryControl.classList.remove("accounting-not-empty"); this.#summaryControl.classList.remove("is-invalid"); @@ -512,46 +512,46 @@ class JournalEntryEditor { } /** - * The callback when editing a journal entry. + * The callback when editing a voucher line item. * - * @param entry {JournalEntrySubForm} the journal entry sub-form + * @param lineItem {LineItemSubForm} the voucher line item sub-form */ - onEdit(entry) { - this.entry = entry; - this.#side = entry.side; - this.entryType = this.#side.entryType; - this.isNeedOffset = entry.isNeedOffset(); - this.originalEntryId = entry.getOriginalEntryId(); - this.originalEntryDate = entry.getOriginalEntryDate(); - this.originalEntryText = entry.getOriginalEntryText(); - this.#originalEntryText.innerText = this.originalEntryText; - if (this.originalEntryId === null) { - this.#originalEntryContainer.classList.add("d-none"); - this.#originalEntryControl.classList.remove("accounting-not-empty"); + onEdit(lineItem) { + this.lineItem = lineItem; + this.#sideSubForm = lineItem.sideSubForm; + this.side = this.#sideSubForm.side; + this.isNeedOffset = lineItem.isNeedOffset(); + this.originalLineItemId = lineItem.getOriginalLineItemId(); + this.originalLineItemDate = lineItem.getOriginalLineItemDate(); + this.originalLineItemText = lineItem.getOriginalLineItemText(); + this.#originalLineItemText.innerText = this.originalLineItemText; + if (this.originalLineItemId === null) { + this.#originalLineItemContainer.classList.add("d-none"); + this.#originalLineItemControl.classList.remove("accounting-not-empty"); } else { - this.#originalEntryContainer.classList.remove("d-none"); - this.#originalEntryControl.classList.add("accounting-not-empty"); + this.#originalLineItemContainer.classList.remove("d-none"); + this.#originalLineItemControl.classList.add("accounting-not-empty"); } - this.#setEnableSummaryAccount(!entry.isMatched && this.originalEntryId === null); - this.summary = entry.getSummary(); + this.#setEnableSummaryAccount(!lineItem.isMatched && this.originalLineItemId === null); + this.summary = lineItem.getSummary(); if (this.summary === null) { this.#summaryControl.classList.remove("accounting-not-empty"); } else { this.#summaryControl.classList.add("accounting-not-empty"); } this.#summaryText.innerText = this.summary === null? "": this.summary; - if (entry.getAccountCode() === null) { + if (lineItem.getAccountCode() === null) { this.#accountControl.classList.remove("accounting-not-empty"); } else { this.#accountControl.classList.add("accounting-not-empty"); } - this.accountCode = entry.getAccountCode(); - this.accountText = entry.getAccountText(); + this.accountCode = lineItem.getAccountCode(); + this.accountText = lineItem.getAccountText(); this.#accountText.innerText = this.accountText; - this.#amountInput.value = entry.getAmount() === null? "": String(entry.getAmount()); + this.#amountInput.value = lineItem.getAmount() === null? "": String(lineItem.getAmount()); const maxAmount = this.#getMaxAmount(); this.#amountInput.max = maxAmount === null? "": maxAmount; - this.#amountInput.min = entry.getAmountMin() === null? "": String(entry.getAmountMin()); + this.#amountInput.min = lineItem.getAmountMin() === null? "": String(lineItem.getAmountMin()); this.#validate(); } @@ -561,10 +561,10 @@ class JournalEntryEditor { * @return {Decimal|null} the max amount */ #getMaxAmount() { - if (this.originalEntryId === null) { + if (this.originalLineItemId === null) { return null; } - return this.originalEntrySelector.getNetBalance(this.entry, this.form, this.originalEntryId); + return this.originalLineItemSelector.getNetBalance(this.lineItem, this.form, this.originalLineItemId); } /** @@ -575,11 +575,11 @@ class JournalEntryEditor { #setEnableSummaryAccount(isEnabled) { if (isEnabled) { this.#summaryControl.dataset.bsToggle = "modal"; - this.#summaryControl.dataset.bsTarget = "#accounting-summary-editor-" + this.#side.entryType + "-modal"; + this.#summaryControl.dataset.bsTarget = "#accounting-summary-editor-" + this.#sideSubForm.side + "-modal"; this.#summaryControl.classList.remove("accounting-disabled"); this.#summaryControl.classList.add("accounting-clickable"); this.#accountControl.dataset.bsToggle = "modal"; - this.#accountControl.dataset.bsTarget = "#accounting-account-selector-" + this.#side.entryType + "-modal"; + this.#accountControl.dataset.bsTarget = "#accounting-account-selector-" + this.#sideSubForm.side + "-modal"; this.#accountControl.classList.remove("accounting-disabled"); this.#accountControl.classList.add("accounting-clickable"); } else { diff --git a/src/accounting/templates/accounting/account/include/form.html b/src/accounting/templates/accounting/account/include/form.html index e52ceba..f4564f7 100644 --- a/src/accounting/templates/accounting/account/include/form.html +++ b/src/accounting/templates/accounting/account/include/form.html @@ -65,7 +65,7 @@ First written: 2023/2/1
diff --git a/src/accounting/templates/accounting/report/include/income-expenses-row-desktop.html b/src/accounting/templates/accounting/report/include/income-expenses-row-desktop.html index 0cce495..1c38071 100644 --- a/src/accounting/templates/accounting/report/include/income-expenses-row-desktop.html +++ b/src/accounting/templates/accounting/report/include/income-expenses-row-desktop.html @@ -19,9 +19,9 @@ income-expenses-row-desktop.html: The row in the income and expenses log for the Author: imacat@mail.imacat.idv.tw (imacat) First written: 2023/3/8 #} -
{{ entry.date|accounting_format_date }}
-
{{ entry.account.title|title }}
-
{{ entry.summary|accounting_default }}
-
{{ entry.income|accounting_format_amount|accounting_default }}
-
{{ entry.expense|accounting_format_amount|accounting_default }}
-
{{ entry.balance|accounting_report_format_amount }}
+
{{ line_item.date|accounting_format_date }}
+
{{ line_item.account.title|title }}
+
{{ line_item.summary|accounting_default }}
+
{{ line_item.income|accounting_format_amount|accounting_default }}
+
{{ line_item.expense|accounting_format_amount|accounting_default }}
+
{{ line_item.balance|accounting_report_format_amount }}
diff --git a/src/accounting/templates/accounting/report/include/income-expenses-row-mobile.html b/src/accounting/templates/accounting/report/include/income-expenses-row-mobile.html index 9f91b53..9c6ac62 100644 --- a/src/accounting/templates/accounting/report/include/income-expenses-row-mobile.html +++ b/src/accounting/templates/accounting/report/include/income-expenses-row-mobile.html @@ -20,31 +20,31 @@ Author: imacat@mail.imacat.idv.tw (imacat) First written: 2023/3/5 #}
- {% if entry.date or entry.account %} + {% if line_item.date or line_item.account %}
- {% if entry.date %} - {{ entry.date|accounting_format_date }} + {% if line_item.date %} + {{ line_item.date|accounting_format_date }} {% endif %} - {% if entry.account %} - {{ entry.account.title|title }} + {% if line_item.account %} + {{ line_item.account.title|title }} {% endif %}
{% endif %} - {% if entry.summary %} -
{{ entry.summary }}
+ {% if line_item.summary %} +
{{ line_item.summary }}
{% endif %}
- {% if entry.income %} - +{{ entry.income|accounting_format_amount }} + {% if line_item.income %} + +{{ line_item.income|accounting_format_amount }} {% endif %} - {% if entry.expense %} - -{{ entry.expense|accounting_format_amount }} + {% if line_item.expense %} + -{{ line_item.expense|accounting_format_amount }} {% endif %} - {% if entry.balance < 0 %} - {{ entry.balance|accounting_format_amount }} + {% if line_item.balance < 0 %} + {{ line_item.balance|accounting_format_amount }} {% else %} - {{ entry.balance|accounting_format_amount }} + {{ line_item.balance|accounting_format_amount }} {% endif %}
diff --git a/src/accounting/templates/accounting/report/include/ledger-row-desktop.html b/src/accounting/templates/accounting/report/include/ledger-row-desktop.html index 30ffd1e..56c3dc4 100644 --- a/src/accounting/templates/accounting/report/include/ledger-row-desktop.html +++ b/src/accounting/templates/accounting/report/include/ledger-row-desktop.html @@ -19,10 +19,10 @@ ledger-row-desktop.html: The row in the ledger for the desktop computers Author: imacat@mail.imacat.idv.tw (imacat) First written: 2023/3/8 #} -
{{ entry.date|accounting_format_date }}
-
{{ entry.summary|accounting_default }}
-
{{ entry.debit|accounting_format_amount|accounting_default }}
-
{{ entry.credit|accounting_format_amount|accounting_default }}
+
{{ line_item.date|accounting_format_date }}
+
{{ line_item.summary|accounting_default }}
+
{{ line_item.debit|accounting_format_amount|accounting_default }}
+
{{ line_item.credit|accounting_format_amount|accounting_default }}
{% if report.account.is_real %} -
{{ entry.balance|accounting_report_format_amount }}
+
{{ line_item.balance|accounting_report_format_amount }}
{% endif %} diff --git a/src/accounting/templates/accounting/report/include/ledger-row-mobile.html b/src/accounting/templates/accounting/report/include/ledger-row-mobile.html index 03dff3e..96d9ccd 100644 --- a/src/accounting/templates/accounting/report/include/ledger-row-mobile.html +++ b/src/accounting/templates/accounting/report/include/ledger-row-mobile.html @@ -20,24 +20,24 @@ Author: imacat@mail.imacat.idv.tw (imacat) First written: 2023/3/5 #}
- {% if entry.date %} + {% if line_item.date %}
- {{ entry.date|accounting_format_date }} + {{ line_item.date|accounting_format_date }}
{% endif %} - {% if entry.summary %} -
{{ entry.summary }}
+ {% if line_item.summary %} +
{{ line_item.summary }}
{% endif %}
- {% if entry.debit %} - +{{ entry.debit|accounting_format_amount }} + {% if line_item.debit %} + +{{ line_item.debit|accounting_format_amount }} {% endif %} - {% if entry.credit %} - -{{ entry.credit|accounting_format_amount }} + {% if line_item.credit %} + -{{ line_item.credit|accounting_format_amount }} {% endif %} {% if report.account.is_real %} - {{ entry.balance|accounting_format_amount }} + {{ line_item.balance|accounting_format_amount }} {% endif %}
diff --git a/src/accounting/templates/accounting/report/income-expenses.html b/src/accounting/templates/accounting/report/income-expenses.html index ca9e64a..73c0cb3 100644 --- a/src/accounting/templates/accounting/report/income-expenses.html +++ b/src/accounting/templates/accounting/report/income-expenses.html @@ -62,26 +62,26 @@ First written: 2023/3/5
{% if report.brought_forward %} - {% with entry = report.brought_forward %} + {% with line_item = report.brought_forward %}
{% include "accounting/report/include/income-expenses-row-desktop.html" %}
{% endwith %} {% endif %} - {% for entry in report.entries %} - + {% for line_item in report.line_items %} + {% include "accounting/report/include/income-expenses-row-desktop.html" %} {% endfor %}
{% if report.total %} - {% with entry = report.total %} + {% with line_item = report.total %} {% endwith %} @@ -90,19 +90,19 @@ First written: 2023/3/5
{% if report.brought_forward %} - {% with entry = report.brought_forward %} + {% with line_item = report.brought_forward %}
{% include "accounting/report/include/income-expenses-row-mobile.html" %}
{% endwith %} {% endif %} - {% for entry in report.entries %} - + {% for line_item in report.line_items %} + {% include "accounting/report/include/income-expenses-row-mobile.html" %} {% endfor %} {% if report.total %} - {% with entry = report.total %} + {% with line_item = report.total %}
{% include "accounting/report/include/income-expenses-row-mobile.html" %}
diff --git a/src/accounting/templates/accounting/report/journal.html b/src/accounting/templates/accounting/report/journal.html index 791528b..109a085 100644 --- a/src/accounting/templates/accounting/report/journal.html +++ b/src/accounting/templates/accounting/report/journal.html @@ -59,41 +59,41 @@ First written: 2023/3/4
- {% for entry in report.entries %} - + {% for line_item in report.line_items %} +
{% if report.brought_forward %} - {% with entry = report.brought_forward %} + {% with line_item = report.brought_forward %}
{% include "accounting/report/include/ledger-row-desktop.html" %}
{% endwith %} {% endif %} - {% for entry in report.entries %} - + {% for line_item in report.line_items %} + {% include "accounting/report/include/ledger-row-desktop.html" %} {% endfor %}
{% if report.total %} - {% with entry = report.total %} + {% with line_item = report.total %} @@ -93,19 +93,19 @@ First written: 2023/3/5
{% if report.brought_forward %} - {% with entry = report.brought_forward %} + {% with line_item = report.brought_forward %}
{% include "accounting/report/include/ledger-row-mobile.html" %}
{% endwith %} {% endif %} - {% for entry in report.entries %} - + {% for line_item in report.line_items %} + {% include "accounting/report/include/ledger-row-mobile.html" %} {% endfor %} {% if report.total %} - {% with entry = report.total %} + {% with line_item = report.total %}
{% include "accounting/report/include/ledger-row-mobile.html" %}
diff --git a/src/accounting/templates/accounting/report/search.html b/src/accounting/templates/accounting/report/search.html index 686f1e3..e613bfc 100644 --- a/src/accounting/templates/accounting/report/search.html +++ b/src/accounting/templates/accounting/report/search.html @@ -56,41 +56,41 @@ First written: 2023/3/8
- {% for entry in report.entries %} - + {% for line_item in report.line_items %} +
-
+
- {{ entry.voucher.date|accounting_format_date }} - {{ entry.account.title|title }} - {% if entry.currency.code != accounting_default_currency_code() %} - {{ entry.currency.code }} + {{ line_item.voucher.date|accounting_format_date }} + {{ line_item.account.title|title }} + {% if line_item.currency.code != accounting_default_currency_code() %} + {{ line_item.currency.code }} {% endif %}
- {% if entry.summary is not none %} -
{{ entry.summary }}
+ {% if line_item.summary is not none %} +
{{ line_item.summary }}
{% endif %}
- {{ entry.amount|accounting_format_amount }} + {{ line_item.amount|accounting_format_amount }}
diff --git a/src/accounting/templates/accounting/voucher/disbursement/detail.html b/src/accounting/templates/accounting/voucher/disbursement/detail.html index 95c4587..be97d8f 100644 --- a/src/accounting/templates/accounting/voucher/disbursement/detail.html +++ b/src/accounting/templates/accounting/voucher/disbursement/detail.html @@ -34,11 +34,11 @@ First written: 2023/2/26
{{ currency.name }}
    -
  • {{ A_("Content") }}
  • - {% with entries = currency.debit %} - {% include "accounting/voucher/include/detail-entries.html" %} +
  • {{ A_("Content") }}
  • + {% with line_items = currency.debit %} + {% include "accounting/voucher/include/detail-line-items.html" %} {% endwith %} -
  • +
  • {{ A_("Total") }}
    {{ currency.debit_total|accounting_format_amount }}
    diff --git a/src/accounting/templates/accounting/voucher/disbursement/include/form-currency-item.html b/src/accounting/templates/accounting/voucher/disbursement/include/form-currency-item.html index 233d021..b72a387 100644 --- a/src/accounting/templates/accounting/voucher/disbursement/include/form-currency-item.html +++ b/src/accounting/templates/accounting/voucher/disbursement/include/form-currency-item.html @@ -44,31 +44,31 @@ First written: 2023/2/25
    -
      - {% for entry_form in debit_forms %} +
        + {% for line_item_form in debit_forms %} {% with currency_index = currency_index, - entry_type = "debit", - entry_index = loop.index, - entry_id = entry_form.eid.data, - only_one_entry_form = debit_forms|length == 1, - account_code_data = entry_form.account_code.data|accounting_default, - account_code_error = entry_form.account_code.errors, - account_text = entry_form.account_text, - summary_data = entry_form.summary.data|accounting_default, - summary_errors = entry_form.summary.errors, - original_entry_id_data = entry_form.original_entry_id.data|accounting_default, - original_entry_date = entry_form.original_entry_date|accounting_default, - original_entry_text = entry_form.original_entry_text|accounting_default, - is_need_offset = entry_form.is_need_offset, - offset_entries = entry_form.offsets, - offset_total = entry_form.offset_total|accounting_default("0"), - net_balance_data = entry_form.net_balance, - net_balance_text = entry_form.net_balance|accounting_format_amount, - amount_data = entry_form.amount.data|accounting_voucher_format_amount_input, - amount_errors = entry_form.amount.errors, - amount_text = entry_form.amount.data|accounting_format_amount, - entry_errors = entry_form.all_errors %} - {% include "accounting/voucher/include/form-entry-item.html" %} + side = "debit", + line_item_index = loop.index, + line_item_id = line_item_form.eid.data, + only_one_line_item_form = debit_forms|length == 1, + account_code_data = line_item_form.account_code.data|accounting_default, + account_code_error = line_item_form.account_code.errors, + account_text = line_item_form.account_text, + summary_data = line_item_form.summary.data|accounting_default, + summary_errors = line_item_form.summary.errors, + original_line_item_id_data = line_item_form.original_line_item_id.data|accounting_default, + original_line_item_date = line_item_form.original_line_item_date|accounting_default, + original_line_item_text = line_item_form.original_line_item_text|accounting_default, + is_need_offset = line_item_form.is_need_offset, + offset_items = line_item_form.offsets, + offset_total = line_item_form.offset_total|accounting_default("0"), + net_balance_data = line_item_form.net_balance, + net_balance_text = line_item_form.net_balance|accounting_format_amount, + amount_data = line_item_form.amount.data|accounting_voucher_format_amount_input, + amount_errors = line_item_form.amount.errors, + amount_text = line_item_form.amount.data|accounting_format_amount, + line_item_errors = line_item_form.all_errors %} + {% include "accounting/voucher/include/form-line-item.html" %} {% endwith %} {% endfor %}
      @@ -79,7 +79,7 @@ First written: 2023/2/25
    - diff --git a/src/accounting/templates/accounting/voucher/disbursement/include/form.html b/src/accounting/templates/accounting/voucher/disbursement/include/form.html index 05c58b1..dc7410c 100644 --- a/src/accounting/templates/accounting/voucher/disbursement/include/form.html +++ b/src/accounting/templates/accounting/voucher/disbursement/include/form.html @@ -50,7 +50,7 @@ First written: 2023/2/25 {% with summary_editor = form.summary_editor.debit %} {% include "accounting/voucher/include/summary-editor-modal.html" %} {% endwith %} - {% with entry_type = "debit", + {% with side = "debit", account_options = form.debit_account_options %} {% include "accounting/voucher/include/account-selector-modal.html" %} {% endwith %} diff --git a/src/accounting/templates/accounting/voucher/include/account-selector-modal.html b/src/accounting/templates/accounting/voucher/include/account-selector-modal.html index 7658244..f223311 100644 --- a/src/accounting/templates/accounting/voucher/include/account-selector-modal.html +++ b/src/accounting/templates/accounting/voucher/include/account-selector-modal.html @@ -19,35 +19,35 @@ account-selector-modal.html: The modal for the account selector Author: imacat@mail.imacat.idv.tw (imacat) First written: 2023/2/25 #} - -{% include "accounting/voucher/include/journal-entry-editor-modal.html" %} +{% include "accounting/voucher/include/voucher-line-item-editor-modal.html" %} {% block form_modals %}{% endblock %} -{% include "accounting/voucher/include/original-entry-selector-modal.html" %} +{% include "accounting/voucher/include/original-line-item-selector-modal.html" %} {% endblock %} diff --git a/src/accounting/templates/accounting/voucher/include/journal-entry-editor-modal.html b/src/accounting/templates/accounting/voucher/include/journal-entry-editor-modal.html deleted file mode 100644 index 4aaff95..0000000 --- a/src/accounting/templates/accounting/voucher/include/journal-entry-editor-modal.html +++ /dev/null @@ -1,76 +0,0 @@ -{# -The Mia! Accounting Flask Project -journal-entry-editor-modal.html: The modal of the journal entry editor - - 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. - -Author: imacat@mail.imacat.idv.tw (imacat) -First written: 2023/2/25 -#} -
    - -
    diff --git a/src/accounting/templates/accounting/voucher/include/original-entry-selector-modal.html b/src/accounting/templates/accounting/voucher/include/original-entry-selector-modal.html deleted file mode 100644 index 0fc8ed6..0000000 --- a/src/accounting/templates/accounting/voucher/include/original-entry-selector-modal.html +++ /dev/null @@ -1,56 +0,0 @@ -{# -The Mia! Accounting Flask Project -original-entry-selector-modal.html: The modal of the original entry selector - - 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. - -Author: imacat@mail.imacat.idv.tw (imacat) -First written: 2023/2/25 -#} - - diff --git a/src/accounting/templates/accounting/voucher/include/original-line-item-selector-modal.html b/src/accounting/templates/accounting/voucher/include/original-line-item-selector-modal.html new file mode 100644 index 0000000..075679f --- /dev/null +++ b/src/accounting/templates/accounting/voucher/include/original-line-item-selector-modal.html @@ -0,0 +1,56 @@ +{# +The Mia! Accounting Flask Project +original-line-item-selector-modal.html: The modal of the original line item selector + + 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. + +Author: imacat@mail.imacat.idv.tw (imacat) +First written: 2023/2/25 +#} + + diff --git a/src/accounting/templates/accounting/voucher/include/summary-editor-modal.html b/src/accounting/templates/accounting/voucher/include/summary-editor-modal.html index 30e6d26..b7fce85 100644 --- a/src/accounting/templates/accounting/voucher/include/summary-editor-modal.html +++ b/src/accounting/templates/accounting/voucher/include/summary-editor-modal.html @@ -19,20 +19,20 @@ summary-editor-modal.html: The modal of the summary editor Author: imacat@mail.imacat.idv.tw (imacat) First written: 2023/2/28 #} -
    -