Renamed "voucher line item" to "journal entry line item".
This commit is contained in:
parent
e26af6f3fc
commit
8f909965a9
@ -100,11 +100,11 @@ def init_accounts_command(username: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def __is_need_offset(base_code: str) -> bool:
|
def __is_need_offset(base_code: str) -> bool:
|
||||||
"""Checks that whether voucher line items in the account need offset.
|
"""Checks that whether journal entry line items in the account need offset.
|
||||||
|
|
||||||
:param base_code: The code of the base account.
|
:param base_code: The code of the base account.
|
||||||
:return: True if voucher line items in the account need offset, or False
|
:return: True if journal entry line items in the account need offset, or
|
||||||
otherwise.
|
False otherwise.
|
||||||
"""
|
"""
|
||||||
# Assets
|
# Assets
|
||||||
if base_code[0] == "1":
|
if base_code[0] == "1":
|
||||||
|
@ -82,7 +82,7 @@ class AccountForm(FlaskForm):
|
|||||||
"""The title."""
|
"""The title."""
|
||||||
is_need_offset = BooleanField(
|
is_need_offset = BooleanField(
|
||||||
validators=[NoOffsetNominalAccount()])
|
validators=[NoOffsetNominalAccount()])
|
||||||
"""Whether the the voucher line items of this account need offset."""
|
"""Whether the the journal entry line items of this account need offset."""
|
||||||
|
|
||||||
def populate_obj(self, obj: Account) -> None:
|
def populate_obj(self, obj: Account) -> None:
|
||||||
"""Populates the form data into an account object.
|
"""Populates the form data into an account object.
|
||||||
|
@ -115,7 +115,7 @@ class Account(db.Model):
|
|||||||
title_l10n = db.Column("title", db.String, nullable=False)
|
title_l10n = db.Column("title", db.String, nullable=False)
|
||||||
"""The title."""
|
"""The title."""
|
||||||
is_need_offset = db.Column(db.Boolean, nullable=False, default=False)
|
is_need_offset = db.Column(db.Boolean, nullable=False, default=False)
|
||||||
"""Whether the voucher line items of this account need offset."""
|
"""Whether the journal entry line items of this account need offset."""
|
||||||
created_at = db.Column(db.DateTime(timezone=True), nullable=False,
|
created_at = db.Column(db.DateTime(timezone=True), nullable=False,
|
||||||
server_default=db.func.now())
|
server_default=db.func.now())
|
||||||
"""The time of creation."""
|
"""The time of creation."""
|
||||||
@ -139,8 +139,9 @@ class Account(db.Model):
|
|||||||
l10n = db.relationship("AccountL10n", back_populates="account",
|
l10n = db.relationship("AccountL10n", back_populates="account",
|
||||||
lazy=False)
|
lazy=False)
|
||||||
"""The localized titles."""
|
"""The localized titles."""
|
||||||
line_items = db.relationship("VoucherLineItem", back_populates="account")
|
line_items = db.relationship("JournalEntryLineItem",
|
||||||
"""The voucher line items."""
|
back_populates="account")
|
||||||
|
"""The journal entry line items."""
|
||||||
|
|
||||||
CASH_CODE: str = "1111-001"
|
CASH_CODE: str = "1111-001"
|
||||||
"""The code of the cash account,"""
|
"""The code of the cash account,"""
|
||||||
@ -363,8 +364,9 @@ class Currency(db.Model):
|
|||||||
l10n = db.relationship("CurrencyL10n", back_populates="currency",
|
l10n = db.relationship("CurrencyL10n", back_populates="currency",
|
||||||
lazy=False)
|
lazy=False)
|
||||||
"""The localized names."""
|
"""The localized names."""
|
||||||
line_items = db.relationship("VoucherLineItem", back_populates="currency")
|
line_items = db.relationship("JournalEntryLineItem",
|
||||||
"""The voucher line items."""
|
back_populates="currency")
|
||||||
|
"""The journal entry line items."""
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Returns the string representation of the currency.
|
"""Returns the string representation of the currency.
|
||||||
@ -450,8 +452,8 @@ class CurrencyL10n(db.Model):
|
|||||||
class VoucherCurrency:
|
class VoucherCurrency:
|
||||||
"""A currency in a voucher."""
|
"""A currency in a voucher."""
|
||||||
|
|
||||||
def __init__(self, code: str, debit: list[VoucherLineItem],
|
def __init__(self, code: str, debit: list[JournalEntryLineItem],
|
||||||
credit: list[VoucherLineItem]):
|
credit: list[JournalEntryLineItem]):
|
||||||
"""Constructs the currency in the voucher.
|
"""Constructs the currency in the voucher.
|
||||||
|
|
||||||
:param code: The currency code.
|
:param code: The currency code.
|
||||||
@ -460,9 +462,9 @@ class VoucherCurrency:
|
|||||||
"""
|
"""
|
||||||
self.code: str = code
|
self.code: str = code
|
||||||
"""The currency code."""
|
"""The currency code."""
|
||||||
self.debit: list[VoucherLineItem] = debit
|
self.debit: list[JournalEntryLineItem] = debit
|
||||||
"""The debit line items."""
|
"""The debit line items."""
|
||||||
self.credit: list[VoucherLineItem] = credit
|
self.credit: list[JournalEntryLineItem] = credit
|
||||||
"""The credit line items."""
|
"""The credit line items."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -523,7 +525,8 @@ class Voucher(db.Model):
|
|||||||
"""The ID of the updator."""
|
"""The ID of the updator."""
|
||||||
updated_by = db.relationship(user_cls, foreign_keys=updated_by_id)
|
updated_by = db.relationship(user_cls, foreign_keys=updated_by_id)
|
||||||
"""The updator."""
|
"""The updator."""
|
||||||
line_items = db.relationship("VoucherLineItem", back_populates="voucher")
|
line_items = db.relationship("JournalEntryLineItem",
|
||||||
|
back_populates="voucher")
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
@ -543,10 +546,10 @@ class Voucher(db.Model):
|
|||||||
|
|
||||||
:return: The currency categories.
|
:return: The currency categories.
|
||||||
"""
|
"""
|
||||||
line_items: list[VoucherLineItem] = sorted(self.line_items,
|
line_items: list[JournalEntryLineItem] = sorted(self.line_items,
|
||||||
key=lambda x: x.no)
|
key=lambda x: x.no)
|
||||||
codes: list[str] = []
|
codes: list[str] = []
|
||||||
by_currency: dict[str, list[VoucherLineItem]] = {}
|
by_currency: dict[str, list[JournalEntryLineItem]] = {}
|
||||||
for line_item in line_items:
|
for line_item in line_items:
|
||||||
if line_item.currency_code not in by_currency:
|
if line_item.currency_code not in by_currency:
|
||||||
codes.append(line_item.currency_code)
|
codes.append(line_item.currency_code)
|
||||||
@ -606,14 +609,14 @@ class Voucher(db.Model):
|
|||||||
|
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
VoucherLineItem.query\
|
JournalEntryLineItem.query\
|
||||||
.filter(VoucherLineItem.voucher_id == self.id).delete()
|
.filter(JournalEntryLineItem.voucher_id == self.id).delete()
|
||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
|
|
||||||
|
|
||||||
class VoucherLineItem(db.Model):
|
class JournalEntryLineItem(db.Model):
|
||||||
"""A line item in the voucher."""
|
"""A line item in the journal entry."""
|
||||||
__tablename__ = "accounting_voucher_line_items"
|
__tablename__ = "accounting_journal_entry_line_items"
|
||||||
"""The table name."""
|
"""The table name."""
|
||||||
id = db.Column(db.Integer, nullable=False, primary_key=True,
|
id = db.Column(db.Integer, nullable=False, primary_key=True,
|
||||||
autoincrement=False)
|
autoincrement=False)
|
||||||
@ -633,11 +636,11 @@ class VoucherLineItem(db.Model):
|
|||||||
db.ForeignKey(id, onupdate="CASCADE"),
|
db.ForeignKey(id, onupdate="CASCADE"),
|
||||||
nullable=True)
|
nullable=True)
|
||||||
"""The ID of the original line item."""
|
"""The ID of the original line item."""
|
||||||
original_line_item = db.relationship("VoucherLineItem",
|
original_line_item = db.relationship("JournalEntryLineItem",
|
||||||
back_populates="offsets",
|
back_populates="offsets",
|
||||||
remote_side=id, passive_deletes=True)
|
remote_side=id, passive_deletes=True)
|
||||||
"""The original line item."""
|
"""The original line item."""
|
||||||
offsets = db.relationship("VoucherLineItem",
|
offsets = db.relationship("JournalEntryLineItem",
|
||||||
back_populates="original_line_item")
|
back_populates="original_line_item")
|
||||||
"""The offset items."""
|
"""The offset items."""
|
||||||
currency_code = db.Column(db.String,
|
currency_code = db.Column(db.String,
|
||||||
|
@ -25,7 +25,7 @@ from flask import render_template, Response
|
|||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, BaseAccount, Account, Voucher, \
|
from accounting.models import Currency, BaseAccount, Account, Voucher, \
|
||||||
VoucherLineItem
|
JournalEntryLineItem
|
||||||
from accounting.report.period import Period, PeriodChooser
|
from accounting.report.period import Period, PeriodChooser
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
@ -124,13 +124,13 @@ class AccountCollector:
|
|||||||
sub_conditions: list[sa.BinaryExpression] \
|
sub_conditions: list[sa.BinaryExpression] \
|
||||||
= [Account.base_code.startswith(x) for x in {"1", "2", "3"}]
|
= [Account.base_code.startswith(x) for x in {"1", "2", "3"}]
|
||||||
conditions: list[sa.BinaryExpression] \
|
conditions: list[sa.BinaryExpression] \
|
||||||
= [VoucherLineItem.currency_code == self.__currency.code,
|
= [JournalEntryLineItem.currency_code == self.__currency.code,
|
||||||
sa.or_(*sub_conditions)]
|
sa.or_(*sub_conditions)]
|
||||||
if self.__period.end is not None:
|
if self.__period.end is not None:
|
||||||
conditions.append(Voucher.date <= self.__period.end)
|
conditions.append(Voucher.date <= self.__period.end)
|
||||||
balance_func: sa.Function = sa.func.sum(sa.case(
|
balance_func: sa.Function = sa.func.sum(sa.case(
|
||||||
(VoucherLineItem.is_debit, VoucherLineItem.amount),
|
(JournalEntryLineItem.is_debit, JournalEntryLineItem.amount),
|
||||||
else_=-VoucherLineItem.amount)).label("balance")
|
else_=-JournalEntryLineItem.amount)).label("balance")
|
||||||
select_balance: sa.Select \
|
select_balance: sa.Select \
|
||||||
= sa.select(Account.id, Account.base_code, Account.no,
|
= sa.select(Account.id, Account.base_code, Account.no,
|
||||||
balance_func)\
|
balance_func)\
|
||||||
@ -178,7 +178,7 @@ class AccountCollector:
|
|||||||
if self.__period.start is None:
|
if self.__period.start is None:
|
||||||
return None
|
return None
|
||||||
conditions: list[sa.BinaryExpression] \
|
conditions: list[sa.BinaryExpression] \
|
||||||
= [VoucherLineItem.currency_code == self.__currency.code,
|
= [JournalEntryLineItem.currency_code == self.__currency.code,
|
||||||
Voucher.date < self.__period.start]
|
Voucher.date < self.__period.start]
|
||||||
return self.__query_balance(conditions)
|
return self.__query_balance(conditions)
|
||||||
|
|
||||||
@ -197,7 +197,7 @@ class AccountCollector:
|
|||||||
:return: The net income or loss for current period.
|
:return: The net income or loss for current period.
|
||||||
"""
|
"""
|
||||||
conditions: list[sa.BinaryExpression] \
|
conditions: list[sa.BinaryExpression] \
|
||||||
= [VoucherLineItem.currency_code == self.__currency.code]
|
= [JournalEntryLineItem.currency_code == self.__currency.code]
|
||||||
if self.__period.start is not None:
|
if self.__period.start is not None:
|
||||||
conditions.append(Voucher.date >= self.__period.start)
|
conditions.append(Voucher.date >= self.__period.start)
|
||||||
if self.__period.end is not None:
|
if self.__period.end is not None:
|
||||||
@ -215,8 +215,8 @@ class AccountCollector:
|
|||||||
conditions.extend([sa.not_(Account.base_code.startswith(x))
|
conditions.extend([sa.not_(Account.base_code.startswith(x))
|
||||||
for x in {"1", "2", "3"}])
|
for x in {"1", "2", "3"}])
|
||||||
balance_func: sa.Function = sa.func.sum(sa.case(
|
balance_func: sa.Function = sa.func.sum(sa.case(
|
||||||
(VoucherLineItem.is_debit, VoucherLineItem.amount),
|
(JournalEntryLineItem.is_debit, JournalEntryLineItem.amount),
|
||||||
else_=-VoucherLineItem.amount))
|
else_=-JournalEntryLineItem.amount))
|
||||||
select_balance: sa.Select = sa.select(balance_func)\
|
select_balance: sa.Select = sa.select(balance_func)\
|
||||||
.join(Voucher).join(Account).filter(*conditions)
|
.join(Voucher).join(Account).filter(*conditions)
|
||||||
return db.session.scalar(select_balance)
|
return db.session.scalar(select_balance)
|
||||||
|
@ -26,7 +26,7 @@ from sqlalchemy.orm import selectinload
|
|||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, Account, Voucher, VoucherLineItem
|
from accounting.models import Currency, Account, Voucher, JournalEntryLineItem
|
||||||
from accounting.report.period import Period, PeriodChooser
|
from accounting.report.period import Period, PeriodChooser
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
@ -44,10 +44,10 @@ from accounting.utils.pagination import Pagination
|
|||||||
class ReportLineItem:
|
class ReportLineItem:
|
||||||
"""A line item in the report."""
|
"""A line item in the report."""
|
||||||
|
|
||||||
def __init__(self, line_item: VoucherLineItem | None = None):
|
def __init__(self, line_item: JournalEntryLineItem | None = None):
|
||||||
"""Constructs the line item in the report.
|
"""Constructs the line item in the report.
|
||||||
|
|
||||||
:param line_item: The voucher line item.
|
:param line_item: The journal entry line item.
|
||||||
"""
|
"""
|
||||||
self.is_brought_forward: bool = False
|
self.is_brought_forward: bool = False
|
||||||
"""Whether this is the brought-forward line item."""
|
"""Whether this is the brought-forward line item."""
|
||||||
@ -68,7 +68,7 @@ class ReportLineItem:
|
|||||||
self.note: str | None = None
|
self.note: str | None = None
|
||||||
"""The note."""
|
"""The note."""
|
||||||
self.url: str | None = None
|
self.url: str | None = None
|
||||||
"""The URL to the voucher line item."""
|
"""The URL to the journal entry line item."""
|
||||||
if line_item is not None:
|
if line_item is not None:
|
||||||
self.date = line_item.voucher.date
|
self.date = line_item.voucher.date
|
||||||
self.account = line_item.account
|
self.account = line_item.account
|
||||||
@ -117,11 +117,12 @@ class LineItemCollector:
|
|||||||
if self.__period.start is None:
|
if self.__period.start is None:
|
||||||
return None
|
return None
|
||||||
balance_func: sa.Function = sa.func.sum(sa.case(
|
balance_func: sa.Function = sa.func.sum(sa.case(
|
||||||
(VoucherLineItem.is_debit, VoucherLineItem.amount),
|
(JournalEntryLineItem.is_debit, JournalEntryLineItem.amount),
|
||||||
else_=-VoucherLineItem.amount))
|
else_=-JournalEntryLineItem.amount))
|
||||||
select: sa.Select = sa.Select(balance_func)\
|
select: sa.Select = sa.Select(balance_func)\
|
||||||
.join(Voucher).join(Account)\
|
.join(Voucher).join(Account)\
|
||||||
.filter(be(VoucherLineItem.currency_code == self.__currency.code),
|
.filter(be(JournalEntryLineItem.currency_code
|
||||||
|
== self.__currency.code),
|
||||||
self.__account_condition,
|
self.__account_condition,
|
||||||
Voucher.date < self.__period.start)
|
Voucher.date < self.__period.start)
|
||||||
balance: int | None = db.session.scalar(select)
|
balance: int | None = db.session.scalar(select)
|
||||||
@ -145,26 +146,28 @@ class LineItemCollector:
|
|||||||
:return: The line items.
|
:return: The line items.
|
||||||
"""
|
"""
|
||||||
conditions: list[sa.BinaryExpression] \
|
conditions: list[sa.BinaryExpression] \
|
||||||
= [VoucherLineItem.currency_code == self.__currency.code,
|
= [JournalEntryLineItem.currency_code == self.__currency.code,
|
||||||
self.__account_condition]
|
self.__account_condition]
|
||||||
if self.__period.start is not None:
|
if self.__period.start is not None:
|
||||||
conditions.append(Voucher.date >= self.__period.start)
|
conditions.append(Voucher.date >= self.__period.start)
|
||||||
if self.__period.end is not None:
|
if self.__period.end is not None:
|
||||||
conditions.append(Voucher.date <= self.__period.end)
|
conditions.append(Voucher.date <= self.__period.end)
|
||||||
voucher_with_account: sa.Select = sa.Select(Voucher.id).\
|
voucher_with_account: sa.Select = sa.Select(Voucher.id).\
|
||||||
join(VoucherLineItem).join(Account).filter(*conditions)
|
join(JournalEntryLineItem).join(Account).filter(*conditions)
|
||||||
|
|
||||||
return [ReportLineItem(x)
|
return [ReportLineItem(x)
|
||||||
for x in VoucherLineItem.query.join(Voucher).join(Account)
|
for x in JournalEntryLineItem.query.join(Voucher).join(Account)
|
||||||
.filter(VoucherLineItem.voucher_id.in_(voucher_with_account),
|
.filter(JournalEntryLineItem.voucher_id
|
||||||
VoucherLineItem.currency_code == self.__currency.code,
|
.in_(voucher_with_account),
|
||||||
|
JournalEntryLineItem.currency_code
|
||||||
|
== self.__currency.code,
|
||||||
sa.not_(self.__account_condition))
|
sa.not_(self.__account_condition))
|
||||||
.order_by(Voucher.date,
|
.order_by(Voucher.date,
|
||||||
Voucher.no,
|
Voucher.no,
|
||||||
VoucherLineItem.is_debit,
|
JournalEntryLineItem.is_debit,
|
||||||
VoucherLineItem.no)
|
JournalEntryLineItem.no)
|
||||||
.options(selectinload(VoucherLineItem.account),
|
.options(selectinload(JournalEntryLineItem.account),
|
||||||
selectinload(VoucherLineItem.voucher))]
|
selectinload(JournalEntryLineItem.voucher))]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __account_condition(self) -> sa.BinaryExpression:
|
def __account_condition(self) -> sa.BinaryExpression:
|
||||||
@ -343,14 +346,15 @@ class PageParams(BasePageParams):
|
|||||||
income_expenses_url(self.currency, current_al,
|
income_expenses_url(self.currency, current_al,
|
||||||
self.period),
|
self.period),
|
||||||
self.account.id == 0)]
|
self.account.id == 0)]
|
||||||
in_use: sa.Select = sa.Select(VoucherLineItem.account_id)\
|
in_use: sa.Select = sa.Select(JournalEntryLineItem.account_id)\
|
||||||
.join(Account)\
|
.join(Account)\
|
||||||
.filter(be(VoucherLineItem.currency_code == self.currency.code),
|
.filter(be(JournalEntryLineItem.currency_code
|
||||||
|
== self.currency.code),
|
||||||
sa.or_(Account.base_code.startswith("11"),
|
sa.or_(Account.base_code.startswith("11"),
|
||||||
Account.base_code.startswith("12"),
|
Account.base_code.startswith("12"),
|
||||||
Account.base_code.startswith("21"),
|
Account.base_code.startswith("21"),
|
||||||
Account.base_code.startswith("22")))\
|
Account.base_code.startswith("22")))\
|
||||||
.group_by(VoucherLineItem.account_id)
|
.group_by(JournalEntryLineItem.account_id)
|
||||||
options.extend([OptionLink(str(x),
|
options.extend([OptionLink(str(x),
|
||||||
income_expenses_url(
|
income_expenses_url(
|
||||||
self.currency,
|
self.currency,
|
||||||
|
@ -25,7 +25,7 @@ from flask import render_template, Response
|
|||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, BaseAccount, Account, Voucher, \
|
from accounting.models import Currency, BaseAccount, Account, Voucher, \
|
||||||
VoucherLineItem
|
JournalEntryLineItem
|
||||||
from accounting.report.period import Period, PeriodChooser
|
from accounting.report.period import Period, PeriodChooser
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
@ -256,15 +256,15 @@ class IncomeStatement(BaseReport):
|
|||||||
sub_conditions: list[sa.BinaryExpression] \
|
sub_conditions: list[sa.BinaryExpression] \
|
||||||
= [Account.base_code.startswith(str(x)) for x in range(4, 10)]
|
= [Account.base_code.startswith(str(x)) for x in range(4, 10)]
|
||||||
conditions: list[sa.BinaryExpression] \
|
conditions: list[sa.BinaryExpression] \
|
||||||
= [VoucherLineItem.currency_code == self.__currency.code,
|
= [JournalEntryLineItem.currency_code == self.__currency.code,
|
||||||
sa.or_(*sub_conditions)]
|
sa.or_(*sub_conditions)]
|
||||||
if self.__period.start is not None:
|
if self.__period.start is not None:
|
||||||
conditions.append(Voucher.date >= self.__period.start)
|
conditions.append(Voucher.date >= self.__period.start)
|
||||||
if self.__period.end is not None:
|
if self.__period.end is not None:
|
||||||
conditions.append(Voucher.date <= self.__period.end)
|
conditions.append(Voucher.date <= self.__period.end)
|
||||||
balance_func: sa.Function = sa.func.sum(sa.case(
|
balance_func: sa.Function = sa.func.sum(sa.case(
|
||||||
(VoucherLineItem.is_debit, -VoucherLineItem.amount),
|
(JournalEntryLineItem.is_debit, -JournalEntryLineItem.amount),
|
||||||
else_=VoucherLineItem.amount)).label("balance")
|
else_=JournalEntryLineItem.amount)).label("balance")
|
||||||
select_balances: sa.Select = sa.select(Account.id, balance_func)\
|
select_balances: sa.Select = sa.select(Account.id, balance_func)\
|
||||||
.join(Voucher).join(Account)\
|
.join(Voucher).join(Account)\
|
||||||
.filter(*conditions)\
|
.filter(*conditions)\
|
||||||
|
@ -25,7 +25,7 @@ from flask import render_template, Response
|
|||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, Account, Voucher, VoucherLineItem
|
from accounting.models import Currency, Account, Voucher, JournalEntryLineItem
|
||||||
from accounting.report.period import Period, PeriodChooser
|
from accounting.report.period import Period, PeriodChooser
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
@ -40,13 +40,13 @@ from accounting.utils.pagination import Pagination
|
|||||||
class ReportLineItem:
|
class ReportLineItem:
|
||||||
"""A line item in the report."""
|
"""A line item in the report."""
|
||||||
|
|
||||||
def __init__(self, line_item: VoucherLineItem):
|
def __init__(self, line_item: JournalEntryLineItem):
|
||||||
"""Constructs the line item in the report.
|
"""Constructs the line item in the report.
|
||||||
|
|
||||||
:param line_item: The voucher line item.
|
:param line_item: The journal entry line item.
|
||||||
"""
|
"""
|
||||||
self.line_item: VoucherLineItem = line_item
|
self.line_item: JournalEntryLineItem = line_item
|
||||||
"""The voucher line item."""
|
"""The journal entry line item."""
|
||||||
self.voucher: Voucher = line_item.voucher
|
self.voucher: Voucher = line_item.voucher
|
||||||
"""The voucher."""
|
"""The voucher."""
|
||||||
self.currency: Currency = line_item.currency
|
self.currency: Currency = line_item.currency
|
||||||
@ -110,8 +110,8 @@ class PageParams(BasePageParams):
|
|||||||
"""The HTML page parameters."""
|
"""The HTML page parameters."""
|
||||||
|
|
||||||
def __init__(self, period: Period,
|
def __init__(self, period: Period,
|
||||||
pagination: Pagination[VoucherLineItem],
|
pagination: Pagination[JournalEntryLineItem],
|
||||||
line_items: list[VoucherLineItem]):
|
line_items: list[JournalEntryLineItem]):
|
||||||
"""Constructs the HTML page parameters.
|
"""Constructs the HTML page parameters.
|
||||||
|
|
||||||
:param period: The period.
|
:param period: The period.
|
||||||
@ -119,9 +119,9 @@ class PageParams(BasePageParams):
|
|||||||
"""
|
"""
|
||||||
self.period: Period = period
|
self.period: Period = period
|
||||||
"""The period."""
|
"""The period."""
|
||||||
self.pagination: Pagination[VoucherLineItem] = pagination
|
self.pagination: Pagination[JournalEntryLineItem] = pagination
|
||||||
"""The pagination."""
|
"""The pagination."""
|
||||||
self.line_items: list[VoucherLineItem] = line_items
|
self.line_items: list[JournalEntryLineItem] = line_items
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
self.period_chooser: PeriodChooser = PeriodChooser(
|
self.period_chooser: PeriodChooser = PeriodChooser(
|
||||||
lambda x: journal_url(x))
|
lambda x: journal_url(x))
|
||||||
@ -145,7 +145,7 @@ class PageParams(BasePageParams):
|
|||||||
period=self.period)
|
period=self.period)
|
||||||
|
|
||||||
|
|
||||||
def get_csv_rows(line_items: list[VoucherLineItem]) -> list[CSVRow]:
|
def get_csv_rows(line_items: list[JournalEntryLineItem]) -> list[CSVRow]:
|
||||||
"""Composes and returns the CSV rows from the line items.
|
"""Composes and returns the CSV rows from the line items.
|
||||||
|
|
||||||
:param line_items: The line items.
|
:param line_items: The line items.
|
||||||
@ -172,10 +172,11 @@ class Journal(BaseReport):
|
|||||||
"""
|
"""
|
||||||
self.__period: Period = period
|
self.__period: Period = period
|
||||||
"""The period."""
|
"""The period."""
|
||||||
self.__line_items: list[VoucherLineItem] = self.__query_line_items()
|
self.__line_items: list[JournalEntryLineItem] \
|
||||||
|
= self.__query_line_items()
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
|
|
||||||
def __query_line_items(self) -> list[VoucherLineItem]:
|
def __query_line_items(self) -> list[JournalEntryLineItem]:
|
||||||
"""Queries and returns the line items.
|
"""Queries and returns the line items.
|
||||||
|
|
||||||
:return: The line items.
|
:return: The line items.
|
||||||
@ -185,15 +186,15 @@ class Journal(BaseReport):
|
|||||||
conditions.append(Voucher.date >= self.__period.start)
|
conditions.append(Voucher.date >= self.__period.start)
|
||||||
if self.__period.end is not None:
|
if self.__period.end is not None:
|
||||||
conditions.append(Voucher.date <= self.__period.end)
|
conditions.append(Voucher.date <= self.__period.end)
|
||||||
return VoucherLineItem.query.join(Voucher)\
|
return JournalEntryLineItem.query.join(Voucher)\
|
||||||
.filter(*conditions)\
|
.filter(*conditions)\
|
||||||
.order_by(Voucher.date,
|
.order_by(Voucher.date,
|
||||||
Voucher.no,
|
Voucher.no,
|
||||||
VoucherLineItem.is_debit.desc(),
|
JournalEntryLineItem.is_debit.desc(),
|
||||||
VoucherLineItem.no)\
|
JournalEntryLineItem.no)\
|
||||||
.options(selectinload(VoucherLineItem.account),
|
.options(selectinload(JournalEntryLineItem.account),
|
||||||
selectinload(VoucherLineItem.currency),
|
selectinload(JournalEntryLineItem.currency),
|
||||||
selectinload(VoucherLineItem.voucher)).all()
|
selectinload(JournalEntryLineItem.voucher)).all()
|
||||||
|
|
||||||
def csv(self) -> Response:
|
def csv(self) -> Response:
|
||||||
"""Returns the report as CSV for download.
|
"""Returns the report as CSV for download.
|
||||||
@ -208,8 +209,9 @@ class Journal(BaseReport):
|
|||||||
|
|
||||||
:return: The report as HTML.
|
:return: The report as HTML.
|
||||||
"""
|
"""
|
||||||
pagination: Pagination[VoucherLineItem] \
|
pagination: Pagination[JournalEntryLineItem] \
|
||||||
= Pagination[VoucherLineItem](self.__line_items, is_reversed=True)
|
= Pagination[JournalEntryLineItem](self.__line_items,
|
||||||
|
is_reversed=True)
|
||||||
params: PageParams = PageParams(period=self.__period,
|
params: PageParams = PageParams(period=self.__period,
|
||||||
pagination=pagination,
|
pagination=pagination,
|
||||||
line_items=pagination.list)
|
line_items=pagination.list)
|
||||||
|
@ -26,7 +26,7 @@ from sqlalchemy.orm import selectinload
|
|||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, Account, Voucher, VoucherLineItem
|
from accounting.models import Currency, Account, Voucher, JournalEntryLineItem
|
||||||
from accounting.report.period import Period, PeriodChooser
|
from accounting.report.period import Period, PeriodChooser
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
@ -43,10 +43,10 @@ from accounting.utils.pagination import Pagination
|
|||||||
class ReportLineItem:
|
class ReportLineItem:
|
||||||
"""A line item in the report."""
|
"""A line item in the report."""
|
||||||
|
|
||||||
def __init__(self, line_item: VoucherLineItem | None = None):
|
def __init__(self, line_item: JournalEntryLineItem | None = None):
|
||||||
"""Constructs the line item in the report.
|
"""Constructs the line item in the report.
|
||||||
|
|
||||||
:param line_item: The voucher line item.
|
:param line_item: The journal entry line item.
|
||||||
"""
|
"""
|
||||||
self.is_brought_forward: bool = False
|
self.is_brought_forward: bool = False
|
||||||
"""Whether this is the brought-forward line item."""
|
"""Whether this is the brought-forward line item."""
|
||||||
@ -65,7 +65,7 @@ class ReportLineItem:
|
|||||||
self.note: str | None = None
|
self.note: str | None = None
|
||||||
"""The note."""
|
"""The note."""
|
||||||
self.url: str | None = None
|
self.url: str | None = None
|
||||||
"""The URL to the voucher line item."""
|
"""The URL to the journal entry line item."""
|
||||||
if line_item is not None:
|
if line_item is not None:
|
||||||
self.date = line_item.voucher.date
|
self.date = line_item.voucher.date
|
||||||
self.description = line_item.description
|
self.description = line_item.description
|
||||||
@ -114,11 +114,13 @@ class LineItemCollector:
|
|||||||
if self.__account.is_nominal:
|
if self.__account.is_nominal:
|
||||||
return None
|
return None
|
||||||
balance_func: sa.Function = sa.func.sum(sa.case(
|
balance_func: sa.Function = sa.func.sum(sa.case(
|
||||||
(VoucherLineItem.is_debit, VoucherLineItem.amount),
|
(JournalEntryLineItem.is_debit, JournalEntryLineItem.amount),
|
||||||
else_=-VoucherLineItem.amount))
|
else_=-JournalEntryLineItem.amount))
|
||||||
select: sa.Select = sa.Select(balance_func).join(Voucher)\
|
select: sa.Select = sa.Select(balance_func).join(Voucher)\
|
||||||
.filter(be(VoucherLineItem.currency_code == self.__currency.code),
|
.filter(be(JournalEntryLineItem.currency_code
|
||||||
be(VoucherLineItem.account_id == self.__account.id),
|
== self.__currency.code),
|
||||||
|
be(JournalEntryLineItem.account_id
|
||||||
|
== self.__account.id),
|
||||||
Voucher.date < self.__period.start)
|
Voucher.date < self.__period.start)
|
||||||
balance: int | None = db.session.scalar(select)
|
balance: int | None = db.session.scalar(select)
|
||||||
if balance is None:
|
if balance is None:
|
||||||
@ -140,19 +142,20 @@ class LineItemCollector:
|
|||||||
:return: The line items.
|
:return: The line items.
|
||||||
"""
|
"""
|
||||||
conditions: list[sa.BinaryExpression] \
|
conditions: list[sa.BinaryExpression] \
|
||||||
= [VoucherLineItem.currency_code == self.__currency.code,
|
= [JournalEntryLineItem.currency_code == self.__currency.code,
|
||||||
VoucherLineItem.account_id == self.__account.id]
|
JournalEntryLineItem.account_id == self.__account.id]
|
||||||
if self.__period.start is not None:
|
if self.__period.start is not None:
|
||||||
conditions.append(Voucher.date >= self.__period.start)
|
conditions.append(Voucher.date >= self.__period.start)
|
||||||
if self.__period.end is not None:
|
if self.__period.end is not None:
|
||||||
conditions.append(Voucher.date <= self.__period.end)
|
conditions.append(Voucher.date <= self.__period.end)
|
||||||
return [ReportLineItem(x) for x in VoucherLineItem.query.join(Voucher)
|
return [ReportLineItem(x) for x in JournalEntryLineItem.query
|
||||||
|
.join(Voucher)
|
||||||
.filter(*conditions)
|
.filter(*conditions)
|
||||||
.order_by(Voucher.date,
|
.order_by(Voucher.date,
|
||||||
Voucher.no,
|
Voucher.no,
|
||||||
VoucherLineItem.is_debit.desc(),
|
JournalEntryLineItem.is_debit.desc(),
|
||||||
VoucherLineItem.no)
|
JournalEntryLineItem.no)
|
||||||
.options(selectinload(VoucherLineItem.voucher)).all()]
|
.options(selectinload(JournalEntryLineItem.voucher)).all()]
|
||||||
|
|
||||||
def __get_total(self) -> ReportLineItem | None:
|
def __get_total(self) -> ReportLineItem | None:
|
||||||
"""Composes the total line item.
|
"""Composes the total line item.
|
||||||
@ -307,9 +310,10 @@ class PageParams(BasePageParams):
|
|||||||
|
|
||||||
:return: The account options.
|
:return: The account options.
|
||||||
"""
|
"""
|
||||||
in_use: sa.Select = sa.Select(VoucherLineItem.account_id)\
|
in_use: sa.Select = sa.Select(JournalEntryLineItem.account_id)\
|
||||||
.filter(be(VoucherLineItem.currency_code == self.currency.code))\
|
.filter(be(JournalEntryLineItem.currency_code
|
||||||
.group_by(VoucherLineItem.account_id)
|
== self.currency.code))\
|
||||||
|
.group_by(JournalEntryLineItem.account_id)
|
||||||
return [OptionLink(str(x), ledger_url(self.currency, x, self.period),
|
return [OptionLink(str(x), ledger_url(self.currency, x, self.period),
|
||||||
x.id == self.account.id)
|
x.id == self.account.id)
|
||||||
for x in Account.query.filter(Account.id.in_(in_use))
|
for x in Account.query.filter(Account.id.in_(in_use))
|
||||||
|
@ -26,7 +26,7 @@ from sqlalchemy.orm import selectinload
|
|||||||
|
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, CurrencyL10n, Account, AccountL10n, \
|
from accounting.models import Currency, CurrencyL10n, Account, AccountL10n, \
|
||||||
Voucher, VoucherLineItem
|
Voucher, JournalEntryLineItem
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
from accounting.report.utils.csv_export import csv_download
|
from accounting.report.utils.csv_export import csv_download
|
||||||
@ -43,10 +43,10 @@ class LineItemCollector:
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Constructs the line item collector."""
|
"""Constructs the line item collector."""
|
||||||
self.line_items: list[VoucherLineItem] = self.__query_line_items()
|
self.line_items: list[JournalEntryLineItem] = self.__query_line_items()
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
|
|
||||||
def __query_line_items(self) -> list[VoucherLineItem]:
|
def __query_line_items(self) -> list[JournalEntryLineItem]:
|
||||||
"""Queries and returns the line items.
|
"""Queries and returns the line items.
|
||||||
|
|
||||||
:return: The line items.
|
:return: The line items.
|
||||||
@ -57,26 +57,27 @@ class LineItemCollector:
|
|||||||
conditions: list[sa.BinaryExpression] = []
|
conditions: list[sa.BinaryExpression] = []
|
||||||
for k in keywords:
|
for k in keywords:
|
||||||
sub_conditions: list[sa.BinaryExpression] \
|
sub_conditions: list[sa.BinaryExpression] \
|
||||||
= [VoucherLineItem.description.contains(k),
|
= [JournalEntryLineItem.description.contains(k),
|
||||||
VoucherLineItem.account_id.in_(
|
JournalEntryLineItem.account_id.in_(
|
||||||
self.__get_account_condition(k)),
|
self.__get_account_condition(k)),
|
||||||
VoucherLineItem.currency_code.in_(
|
JournalEntryLineItem.currency_code.in_(
|
||||||
self.__get_currency_condition(k)),
|
self.__get_currency_condition(k)),
|
||||||
VoucherLineItem.voucher_id.in_(
|
JournalEntryLineItem.voucher_id.in_(
|
||||||
self.__get_voucher_condition(k))]
|
self.__get_voucher_condition(k))]
|
||||||
try:
|
try:
|
||||||
sub_conditions.append(VoucherLineItem.amount == Decimal(k))
|
sub_conditions.append(
|
||||||
|
JournalEntryLineItem.amount == Decimal(k))
|
||||||
except ArithmeticError:
|
except ArithmeticError:
|
||||||
pass
|
pass
|
||||||
conditions.append(sa.or_(*sub_conditions))
|
conditions.append(sa.or_(*sub_conditions))
|
||||||
return VoucherLineItem.query.join(Voucher).filter(*conditions)\
|
return JournalEntryLineItem.query.join(Voucher).filter(*conditions)\
|
||||||
.order_by(Voucher.date,
|
.order_by(Voucher.date,
|
||||||
Voucher.no,
|
Voucher.no,
|
||||||
VoucherLineItem.is_debit,
|
JournalEntryLineItem.is_debit,
|
||||||
VoucherLineItem.no)\
|
JournalEntryLineItem.no)\
|
||||||
.options(selectinload(VoucherLineItem.account),
|
.options(selectinload(JournalEntryLineItem.account),
|
||||||
selectinload(VoucherLineItem.currency),
|
selectinload(JournalEntryLineItem.currency),
|
||||||
selectinload(VoucherLineItem.voucher)).all()
|
selectinload(JournalEntryLineItem.voucher)).all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_account_condition(k: str) -> sa.Select:
|
def __get_account_condition(k: str) -> sa.Select:
|
||||||
@ -149,15 +150,15 @@ class LineItemCollector:
|
|||||||
class PageParams(BasePageParams):
|
class PageParams(BasePageParams):
|
||||||
"""The HTML page parameters."""
|
"""The HTML page parameters."""
|
||||||
|
|
||||||
def __init__(self, pagination: Pagination[VoucherLineItem],
|
def __init__(self, pagination: Pagination[JournalEntryLineItem],
|
||||||
line_items: list[VoucherLineItem]):
|
line_items: list[JournalEntryLineItem]):
|
||||||
"""Constructs the HTML page parameters.
|
"""Constructs the HTML page parameters.
|
||||||
|
|
||||||
:param line_items: The search result line items.
|
:param line_items: The search result line items.
|
||||||
"""
|
"""
|
||||||
self.pagination: Pagination[VoucherLineItem] = pagination
|
self.pagination: Pagination[JournalEntryLineItem] = pagination
|
||||||
"""The pagination."""
|
"""The pagination."""
|
||||||
self.line_items: list[VoucherLineItem] = line_items
|
self.line_items: list[JournalEntryLineItem] = line_items
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -182,7 +183,7 @@ class Search(BaseReport):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Constructs a search."""
|
"""Constructs a search."""
|
||||||
self.__line_items: list[VoucherLineItem] \
|
self.__line_items: list[JournalEntryLineItem] \
|
||||||
= LineItemCollector().line_items
|
= LineItemCollector().line_items
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
|
|
||||||
@ -199,8 +200,9 @@ class Search(BaseReport):
|
|||||||
|
|
||||||
:return: The report as HTML.
|
:return: The report as HTML.
|
||||||
"""
|
"""
|
||||||
pagination: Pagination[VoucherLineItem] \
|
pagination: Pagination[JournalEntryLineItem] \
|
||||||
= Pagination[VoucherLineItem](self.__line_items, is_reversed=True)
|
= Pagination[JournalEntryLineItem](self.__line_items,
|
||||||
|
is_reversed=True)
|
||||||
params: PageParams = PageParams(pagination=pagination,
|
params: PageParams = PageParams(pagination=pagination,
|
||||||
line_items=pagination.list)
|
line_items=pagination.list)
|
||||||
return render_template("accounting/report/search.html",
|
return render_template("accounting/report/search.html",
|
||||||
|
@ -24,7 +24,7 @@ from flask import Response, render_template
|
|||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, Account, Voucher, VoucherLineItem
|
from accounting.models import Currency, Account, Voucher, JournalEntryLineItem
|
||||||
from accounting.report.period import Period, PeriodChooser
|
from accounting.report.period import Period, PeriodChooser
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
@ -178,14 +178,14 @@ class TrialBalance(BaseReport):
|
|||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
conditions: list[sa.BinaryExpression] \
|
conditions: list[sa.BinaryExpression] \
|
||||||
= [VoucherLineItem.currency_code == self.__currency.code]
|
= [JournalEntryLineItem.currency_code == self.__currency.code]
|
||||||
if self.__period.start is not None:
|
if self.__period.start is not None:
|
||||||
conditions.append(Voucher.date >= self.__period.start)
|
conditions.append(Voucher.date >= self.__period.start)
|
||||||
if self.__period.end is not None:
|
if self.__period.end is not None:
|
||||||
conditions.append(Voucher.date <= self.__period.end)
|
conditions.append(Voucher.date <= self.__period.end)
|
||||||
balance_func: sa.Function = sa.func.sum(sa.case(
|
balance_func: sa.Function = sa.func.sum(sa.case(
|
||||||
(VoucherLineItem.is_debit, VoucherLineItem.amount),
|
(JournalEntryLineItem.is_debit, JournalEntryLineItem.amount),
|
||||||
else_=-VoucherLineItem.amount)).label("balance")
|
else_=-JournalEntryLineItem.amount)).label("balance")
|
||||||
select_balances: sa.Select = sa.select(Account.id, balance_func)\
|
select_balances: sa.Select = sa.select(Account.id, balance_func)\
|
||||||
.join(Voucher).join(Account)\
|
.join(Voucher).join(Account)\
|
||||||
.filter(*conditions)\
|
.filter(*conditions)\
|
||||||
|
@ -26,7 +26,7 @@ import sqlalchemy as sa
|
|||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.models import Currency, VoucherLineItem
|
from accounting.models import Currency, JournalEntryLineItem
|
||||||
from accounting.utils.voucher_types import VoucherType
|
from accounting.utils.voucher_types import VoucherType
|
||||||
from .option_link import OptionLink
|
from .option_link import OptionLink
|
||||||
from .report_chooser import ReportChooser
|
from .report_chooser import ReportChooser
|
||||||
@ -81,8 +81,8 @@ class BasePageParams(ABC):
|
|||||||
:return: The currency options.
|
:return: The currency options.
|
||||||
"""
|
"""
|
||||||
in_use: set[str] = set(db.session.scalars(
|
in_use: set[str] = set(db.session.scalars(
|
||||||
sa.select(VoucherLineItem.currency_code)
|
sa.select(JournalEntryLineItem.currency_code)
|
||||||
.group_by(VoucherLineItem.currency_code)).all())
|
.group_by(JournalEntryLineItem.currency_code)).all())
|
||||||
return [OptionLink(str(x), get_url(x), x.code == active_currency.code)
|
return [OptionLink(str(x), get_url(x), x.code == active_currency.code)
|
||||||
for x in Currency.query.filter(Currency.code.in_(in_use))
|
for x in Currency.query.filter(Currency.code.in_(in_use))
|
||||||
.order_by(Currency.code).all()]
|
.order_by(Currency.code).all()]
|
||||||
|
@ -166,14 +166,14 @@
|
|||||||
.accounting-list-group-hover .list-group-item:hover {
|
.accounting-list-group-hover .list-group-item:hover {
|
||||||
background-color: #ececec;
|
background-color: #ececec;
|
||||||
}
|
}
|
||||||
.accounting-voucher-line-item {
|
.accounting-journal-entry-line-item {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.accounting-voucher-line-item-header {
|
.accounting-journal-entry-line-item-header {
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
border-bottom: thick double slategray;
|
border-bottom: thick double slategray;
|
||||||
}
|
}
|
||||||
.list-group-item.accounting-voucher-line-item-total {
|
.list-group-item.accounting-journal-entry-line-item-total {
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
border-top: thick double slategray;
|
border-top: thick double slategray;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ class AccountSelector {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The line item editor
|
* The line item editor
|
||||||
* @type {VoucherLineItemEditor}
|
* @type {JournalEntryLineItemEditor}
|
||||||
*/
|
*/
|
||||||
#lineItemEditor;
|
#lineItemEditor;
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class AccountSelector {
|
|||||||
/**
|
/**
|
||||||
* Constructs an account selector.
|
* Constructs an account selector.
|
||||||
*
|
*
|
||||||
* @param lineItemEditor {VoucherLineItemEditor} the line item editor
|
* @param lineItemEditor {JournalEntryLineItemEditor} the line item editor
|
||||||
* @param debitCredit {string} either "debit" or "credit"
|
* @param debitCredit {string} either "debit" or "credit"
|
||||||
*/
|
*/
|
||||||
constructor(lineItemEditor, debitCredit) {
|
constructor(lineItemEditor, debitCredit) {
|
||||||
@ -210,7 +210,7 @@ class AccountSelector {
|
|||||||
/**
|
/**
|
||||||
* Returns the account selector instances.
|
* Returns the account selector instances.
|
||||||
*
|
*
|
||||||
* @param lineItemEditor {VoucherLineItemEditor} the line item editor
|
* @param lineItemEditor {JournalEntryLineItemEditor} the line item editor
|
||||||
* @return {{debit: AccountSelector, credit: AccountSelector}}
|
* @return {{debit: AccountSelector, credit: AccountSelector}}
|
||||||
*/
|
*/
|
||||||
static getInstances(lineItemEditor) {
|
static getInstances(lineItemEditor) {
|
||||||
|
@ -30,7 +30,7 @@ class DescriptionEditor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The line item editor
|
* The line item editor
|
||||||
* @type {VoucherLineItemEditor}
|
* @type {JournalEntryLineItemEditor}
|
||||||
*/
|
*/
|
||||||
#lineItemEditor;
|
#lineItemEditor;
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ class DescriptionEditor {
|
|||||||
/**
|
/**
|
||||||
* Constructs a description editor.
|
* Constructs a description editor.
|
||||||
*
|
*
|
||||||
* @param lineItemEditor {VoucherLineItemEditor} the line item editor
|
* @param lineItemEditor {JournalEntryLineItemEditor} the line item editor
|
||||||
* @param debitCredit {string} either "debit" or "credit"
|
* @param debitCredit {string} either "debit" or "credit"
|
||||||
*/
|
*/
|
||||||
constructor(lineItemEditor, debitCredit) {
|
constructor(lineItemEditor, debitCredit) {
|
||||||
@ -246,7 +246,7 @@ class DescriptionEditor {
|
|||||||
/**
|
/**
|
||||||
* Returns the description editor instances.
|
* Returns the description editor instances.
|
||||||
*
|
*
|
||||||
* @param lineItemEditor {VoucherLineItemEditor} the line item editor
|
* @param lineItemEditor {JournalEntryLineItemEditor} the line item editor
|
||||||
* @return {{debit: DescriptionEditor, credit: DescriptionEditor}}
|
* @return {{debit: DescriptionEditor, credit: DescriptionEditor}}
|
||||||
*/
|
*/
|
||||||
static getInstances(lineItemEditor) {
|
static getInstances(lineItemEditor) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/* The Mia! Accounting Flask Project
|
/* The Mia! Accounting Flask Project
|
||||||
* voucher-line-item-editor.js: The JavaScript for the voucher line item editor
|
* journal-entry-line-item-editor.js: The JavaScript for the journal entry line item editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Copyright (c) 2023 imacat.
|
/* Copyright (c) 2023 imacat.
|
||||||
@ -23,10 +23,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voucher line item editor.
|
* The journal entry line item editor.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class VoucherLineItemEditor {
|
class JournalEntryLineItemEditor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voucher form
|
* The voucher form
|
||||||
@ -35,7 +35,7 @@ class VoucherLineItemEditor {
|
|||||||
form;
|
form;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voucher line item editor
|
* The journal entry line item editor
|
||||||
* @type {HTMLFormElement}
|
* @type {HTMLFormElement}
|
||||||
*/
|
*/
|
||||||
#element;
|
#element;
|
||||||
@ -137,7 +137,7 @@ class VoucherLineItemEditor {
|
|||||||
#amountError;
|
#amountError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The voucher line item to edit
|
* The journal entry line item to edit
|
||||||
* @type {LineItemSubForm|null}
|
* @type {LineItemSubForm|null}
|
||||||
*/
|
*/
|
||||||
lineItem;
|
lineItem;
|
||||||
@ -149,7 +149,7 @@ class VoucherLineItemEditor {
|
|||||||
#debitCreditSubForm;
|
#debitCreditSubForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the voucher line item needs offset
|
* Whether the journal entry line item needs offset
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
isNeedOffset = false;
|
isNeedOffset = false;
|
||||||
@ -215,7 +215,7 @@ class VoucherLineItemEditor {
|
|||||||
originalLineItemSelector;
|
originalLineItemSelector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new voucher line item editor.
|
* Constructs a new journal entry line item editor.
|
||||||
*
|
*
|
||||||
* @param form {VoucherForm} the voucher form
|
* @param form {VoucherForm} the voucher form
|
||||||
*/
|
*/
|
||||||
@ -476,7 +476,7 @@ class VoucherLineItemEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callback when adding a new voucher line item.
|
* The callback when adding a new journal entry line item.
|
||||||
*
|
*
|
||||||
* @param debitCredit {DebitCreditSubForm} the debit or credit sub-form
|
* @param debitCredit {DebitCreditSubForm} the debit or credit sub-form
|
||||||
*/
|
*/
|
||||||
@ -512,9 +512,9 @@ class VoucherLineItemEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callback when editing a voucher line item.
|
* The callback when editing a journal entry line item.
|
||||||
*
|
*
|
||||||
* @param lineItem {LineItemSubForm} the voucher line item sub-form
|
* @param lineItem {LineItemSubForm} the journal entry line item sub-form
|
||||||
*/
|
*/
|
||||||
onEdit(lineItem) {
|
onEdit(lineItem) {
|
||||||
this.lineItem = lineItem;
|
this.lineItem = lineItem;
|
@ -30,7 +30,7 @@ class OriginalLineItemSelector {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The line item editor
|
* The line item editor
|
||||||
* @type {VoucherLineItemEditor}
|
* @type {JournalEntryLineItemEditor}
|
||||||
*/
|
*/
|
||||||
lineItemEditor;
|
lineItemEditor;
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ class OriginalLineItemSelector {
|
|||||||
/**
|
/**
|
||||||
* Constructs an original line item selector.
|
* Constructs an original line item selector.
|
||||||
*
|
*
|
||||||
* @param lineItemEditor {VoucherLineItemEditor} the line item editor
|
* @param lineItemEditor {JournalEntryLineItemEditor} the line item editor
|
||||||
*/
|
*/
|
||||||
constructor(lineItemEditor) {
|
constructor(lineItemEditor) {
|
||||||
this.lineItemEditor = lineItemEditor;
|
this.lineItemEditor = lineItemEditor;
|
||||||
|
@ -100,7 +100,7 @@ class VoucherForm {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The line item editor
|
* The line item editor
|
||||||
* @type {VoucherLineItemEditor}
|
* @type {JournalEntryLineItemEditor}
|
||||||
*/
|
*/
|
||||||
lineItemEditor;
|
lineItemEditor;
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ class VoucherForm {
|
|||||||
this.#addCurrencyButton = document.getElementById("accounting-add-currency");
|
this.#addCurrencyButton = document.getElementById("accounting-add-currency");
|
||||||
this.#note = document.getElementById("accounting-note");
|
this.#note = document.getElementById("accounting-note");
|
||||||
this.#noteError = document.getElementById("accounting-note-error");
|
this.#noteError = document.getElementById("accounting-note-error");
|
||||||
this.lineItemEditor = new VoucherLineItemEditor(this);
|
this.lineItemEditor = new JournalEntryLineItemEditor(this);
|
||||||
|
|
||||||
this.#addCurrencyButton.onclick = () => {
|
this.#addCurrencyButton.onclick = () => {
|
||||||
const newIndex = 1 + (this.#currencies.length === 0? 0: Math.max(...this.#currencies.map((currency) => currency.index)));
|
const newIndex = 1 + (this.#currencies.length === 0? 0: Math.max(...this.#currencies.map((currency) => currency.index)));
|
||||||
@ -993,7 +993,7 @@ class LineItemSubForm {
|
|||||||
/**
|
/**
|
||||||
* Stores the data into the line item sub-form.
|
* Stores the data into the line item sub-form.
|
||||||
*
|
*
|
||||||
* @param editor {VoucherLineItemEditor} the line item editor
|
* @param editor {JournalEntryLineItemEditor} the line item editor
|
||||||
*/
|
*/
|
||||||
save(editor) {
|
save(editor) {
|
||||||
if (editor.isNeedOffset) {
|
if (editor.isNeedOffset) {
|
||||||
|
@ -34,11 +34,11 @@ First written: 2023/2/26
|
|||||||
<div class="mb-2 fw-bolder">{{ currency.name }}</div>
|
<div class="mb-2 fw-bolder">{{ currency.name }}</div>
|
||||||
|
|
||||||
<ul class="list-group accounting-list-group-stripped accounting-list-group-hover">
|
<ul class="list-group accounting-list-group-stripped accounting-list-group-hover">
|
||||||
<li class="list-group-item accounting-voucher-line-item accounting-voucher-line-item-header">{{ A_("Content") }}</li>
|
<li class="list-group-item accounting-journal-entry-line-item accounting-journal-entry-line-item-header">{{ A_("Content") }}</li>
|
||||||
{% with line_items = currency.debit %}
|
{% with line_items = currency.debit %}
|
||||||
{% include "accounting/voucher/include/detail-line-items.html" %}
|
{% include "accounting/voucher/include/detail-line-items.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<li class="list-group-item accounting-voucher-line-item accounting-voucher-line-item-total">
|
<li class="list-group-item accounting-journal-entry-line-item accounting-journal-entry-line-item-total">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div>{{ A_("Total") }}</div>
|
<div>{{ A_("Total") }}</div>
|
||||||
<div>{{ currency.debit_total|accounting_format_amount }}</div>
|
<div>{{ currency.debit_total|accounting_format_amount }}</div>
|
||||||
|
@ -21,7 +21,7 @@ First written: 2023/3/14
|
|||||||
#}
|
#}
|
||||||
{# <ul> For SonarQube not to complain about incorrect HTML #}
|
{# <ul> For SonarQube not to complain about incorrect HTML #}
|
||||||
{% for line_item in line_items %}
|
{% for line_item in line_items %}
|
||||||
<li class="list-group-item accounting-voucher-line-item">
|
<li class="list-group-item accounting-journal-entry-line-item">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
<div class="small">{{ line_item.account }}</div>
|
<div class="small">{{ line_item.account }}</div>
|
||||||
|
@ -24,7 +24,7 @@ First written: 2023/2/26
|
|||||||
{% block accounting_scripts %}
|
{% block accounting_scripts %}
|
||||||
<script src="{{ url_for("accounting.static", filename="js/drag-and-drop-reorder.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/drag-and-drop-reorder.js") }}"></script>
|
||||||
<script src="{{ url_for("accounting.static", filename="js/voucher-form.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/voucher-form.js") }}"></script>
|
||||||
<script src="{{ url_for("accounting.static", filename="js/voucher-line-item-editor.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/journal-entry-line-item-editor.js") }}"></script>
|
||||||
<script src="{{ url_for("accounting.static", filename="js/account-selector.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/account-selector.js") }}"></script>
|
||||||
<script src="{{ url_for("accounting.static", filename="js/original-line-item-selector.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/original-line-item-selector.js") }}"></script>
|
||||||
<script src="{{ url_for("accounting.static", filename="js/description-editor.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/description-editor.js") }}"></script>
|
||||||
@ -88,7 +88,7 @@ First written: 2023/2/26
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% include "accounting/voucher/include/voucher-line-item-editor-modal.html" %}
|
{% include "accounting/voucher/include/journal-entry-line-item-editor-modal.html" %}
|
||||||
{% block form_modals %}{% endblock %}
|
{% block form_modals %}{% endblock %}
|
||||||
{% include "accounting/voucher/include/original-line-item-selector-modal.html" %}
|
{% include "accounting/voucher/include/original-line-item-selector-modal.html" %}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{#
|
{#
|
||||||
The Mia! Accounting Flask Project
|
The Mia! Accounting Flask Project
|
||||||
voucher-line-item-editor-modal.html: The modal of the voucher line item editor
|
journal-entry-line-item-editor-modal: The modal of the journal entry line item editor
|
||||||
|
|
||||||
Copyright (c) 2023 imacat.
|
Copyright (c) 2023 imacat.
|
||||||
|
|
@ -34,11 +34,11 @@ First written: 2023/2/26
|
|||||||
<div class="mb-2 fw-bolder">{{ currency.name }}</div>
|
<div class="mb-2 fw-bolder">{{ currency.name }}</div>
|
||||||
|
|
||||||
<ul class="list-group accounting-list-group-stripped accounting-list-group-hover">
|
<ul class="list-group accounting-list-group-stripped accounting-list-group-hover">
|
||||||
<li class="list-group-item accounting-voucher-line-item accounting-voucher-line-item-header">{{ A_("Content") }}</li>
|
<li class="list-group-item accounting-journal-entry-line-item accounting-journal-entry-line-item-header">{{ A_("Content") }}</li>
|
||||||
{% with line_items = currency.credit %}
|
{% with line_items = currency.credit %}
|
||||||
{% include "accounting/voucher/include/detail-line-items.html" %}
|
{% include "accounting/voucher/include/detail-line-items.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<li class="list-group-item accounting-voucher-line-item accounting-voucher-line-item-total">
|
<li class="list-group-item accounting-journal-entry-line-item accounting-journal-entry-line-item-total">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div>{{ A_("Total") }}</div>
|
<div>{{ A_("Total") }}</div>
|
||||||
<div>{{ currency.debit_total|accounting_format_amount }}</div>
|
<div>{{ currency.debit_total|accounting_format_amount }}</div>
|
||||||
|
@ -30,11 +30,11 @@ First written: 2023/2/26
|
|||||||
{# The debit line items #}
|
{# The debit line items #}
|
||||||
<div class="col-sm-6 mb-2">
|
<div class="col-sm-6 mb-2">
|
||||||
<ul class="list-group accounting-list-group-stripped accounting-list-group-hover">
|
<ul class="list-group accounting-list-group-stripped accounting-list-group-hover">
|
||||||
<li class="list-group-item accounting-voucher-line-item accounting-voucher-line-item-header">{{ A_("Debit") }}</li>
|
<li class="list-group-item accounting-journal-entry-line-item accounting-journal-entry-line-item-header">{{ A_("Debit") }}</li>
|
||||||
{% with line_items = currency.debit %}
|
{% with line_items = currency.debit %}
|
||||||
{% include "accounting/voucher/include/detail-line-items.html" %}
|
{% include "accounting/voucher/include/detail-line-items.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<li class="list-group-item accounting-voucher-line-item accounting-voucher-line-item-total">
|
<li class="list-group-item accounting-journal-entry-line-item accounting-journal-entry-line-item-total">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div>{{ A_("Total") }}</div>
|
<div>{{ A_("Total") }}</div>
|
||||||
<div>{{ currency.debit_total|accounting_format_amount }}</div>
|
<div>{{ currency.debit_total|accounting_format_amount }}</div>
|
||||||
@ -46,11 +46,11 @@ First written: 2023/2/26
|
|||||||
{# The credit line items #}
|
{# The credit line items #}
|
||||||
<div class="col-sm-6 mb-2">
|
<div class="col-sm-6 mb-2">
|
||||||
<ul class="list-group accounting-list-group-stripped accounting-list-group-hover">
|
<ul class="list-group accounting-list-group-stripped accounting-list-group-hover">
|
||||||
<li class="list-group-item accounting-voucher-line-item accounting-voucher-line-item-header">{{ A_("Credit") }}</li>
|
<li class="list-group-item accounting-journal-entry-line-item accounting-journal-entry-line-item-header">{{ A_("Credit") }}</li>
|
||||||
{% with line_items = currency.credit %}
|
{% with line_items = currency.credit %}
|
||||||
{% include "accounting/voucher/include/detail-line-items.html" %}
|
{% include "accounting/voucher/include/detail-line-items.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<li class="list-group-item accounting-voucher-line-item accounting-voucher-line-item-total">
|
<li class="list-group-item accounting-journal-entry-line-item accounting-journal-entry-line-item-total">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div>{{ A_("Total") }}</div>
|
<div>{{ A_("Total") }}</div>
|
||||||
<div>{{ currency.debit_total|accounting_format_amount }}</div>
|
<div>{{ currency.debit_total|accounting_format_amount }}</div>
|
||||||
|
@ -23,7 +23,7 @@ from flask import abort
|
|||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
from werkzeug.routing import BaseConverter
|
from werkzeug.routing import BaseConverter
|
||||||
|
|
||||||
from accounting.models import Voucher, VoucherLineItem
|
from accounting.models import Voucher, JournalEntryLineItem
|
||||||
from accounting.utils.voucher_types import VoucherType
|
from accounting.utils.voucher_types import VoucherType
|
||||||
|
|
||||||
|
|
||||||
@ -37,11 +37,11 @@ class VoucherConverter(BaseConverter):
|
|||||||
:param value: The voucher ID.
|
:param value: The voucher ID.
|
||||||
:return: The corresponding voucher.
|
:return: The corresponding voucher.
|
||||||
"""
|
"""
|
||||||
voucher: Voucher | None = Voucher.query.join(VoucherLineItem)\
|
voucher: Voucher | None = Voucher.query.join(JournalEntryLineItem)\
|
||||||
.filter(Voucher.id == value)\
|
.filter(Voucher.id == value)\
|
||||||
.options(selectinload(Voucher.line_items)
|
.options(selectinload(Voucher.line_items)
|
||||||
.selectinload(VoucherLineItem.offsets)
|
.selectinload(JournalEntryLineItem.offsets)
|
||||||
.selectinload(VoucherLineItem.voucher))\
|
.selectinload(JournalEntryLineItem.voucher))\
|
||||||
.first()
|
.first()
|
||||||
if voucher is None:
|
if voucher is None:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
@ -28,7 +28,7 @@ from wtforms.validators import DataRequired
|
|||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import lazy_gettext
|
from accounting.locale import lazy_gettext
|
||||||
from accounting.models import Currency, VoucherLineItem
|
from accounting.models import Currency, JournalEntryLineItem
|
||||||
from accounting.voucher.utils.offset_alias import offset_alias
|
from accounting.voucher.utils.offset_alias import offset_alias
|
||||||
from accounting.utils.cast import be
|
from accounting.utils.cast import be
|
||||||
from accounting.utils.strip_text import strip_text
|
from accounting.utils.strip_text import strip_text
|
||||||
@ -65,8 +65,8 @@ class SameCurrencyAsOriginalLineItems:
|
|||||||
if len(original_line_item_id) == 0:
|
if len(original_line_item_id) == 0:
|
||||||
return
|
return
|
||||||
original_line_item_currency_codes: set[str] = set(db.session.scalars(
|
original_line_item_currency_codes: set[str] = set(db.session.scalars(
|
||||||
sa.select(VoucherLineItem.currency_code)
|
sa.select(JournalEntryLineItem.currency_code)
|
||||||
.filter(VoucherLineItem.id.in_(original_line_item_id))).all())
|
.filter(JournalEntryLineItem.id.in_(original_line_item_id))).all())
|
||||||
for currency_code in original_line_item_currency_codes:
|
for currency_code in original_line_item_currency_codes:
|
||||||
if field.data != currency_code:
|
if field.data != currency_code:
|
||||||
raise ValidationError(lazy_gettext(
|
raise ValidationError(lazy_gettext(
|
||||||
@ -83,13 +83,16 @@ class KeepCurrencyWhenHavingOffset:
|
|||||||
if field.data is None:
|
if field.data is None:
|
||||||
return
|
return
|
||||||
offset: sa.Alias = offset_alias()
|
offset: sa.Alias = offset_alias()
|
||||||
original_line_items: list[VoucherLineItem] = VoucherLineItem.query\
|
original_line_items: list[JournalEntryLineItem]\
|
||||||
.join(offset, be(VoucherLineItem.id
|
= JournalEntryLineItem.query\
|
||||||
|
.join(offset, be(JournalEntryLineItem.id
|
||||||
== offset.c.original_line_item_id),
|
== offset.c.original_line_item_id),
|
||||||
isouter=True)\
|
isouter=True)\
|
||||||
.filter(VoucherLineItem.id.in_({x.eid.data for x in form.line_items
|
.filter(JournalEntryLineItem.id
|
||||||
if x.eid.data is not None}))\
|
.in_({x.eid.data for x in form.line_items
|
||||||
.group_by(VoucherLineItem.id, VoucherLineItem.currency_code)\
|
if x.eid.data is not None}))\
|
||||||
|
.group_by(JournalEntryLineItem.id,
|
||||||
|
JournalEntryLineItem.currency_code)\
|
||||||
.having(sa.func.count(offset.c.id) > 0).all()
|
.having(sa.func.count(offset.c.id) > 0).all()
|
||||||
for original_line_item in original_line_items:
|
for original_line_item in original_line_items:
|
||||||
if original_line_item.currency_code != field.data:
|
if original_line_item.currency_code != field.data:
|
||||||
@ -159,8 +162,9 @@ class CurrencyForm(FlaskForm):
|
|||||||
return True
|
return True
|
||||||
line_item_id: set[int] = {x.eid.data for x in line_item_forms
|
line_item_id: set[int] = {x.eid.data for x in line_item_forms
|
||||||
if x.eid.data is not None}
|
if x.eid.data is not None}
|
||||||
select: sa.Select = sa.select(sa.func.count(VoucherLineItem.id))\
|
select: sa.Select = sa.select(sa.func.count(JournalEntryLineItem.id))\
|
||||||
.filter(VoucherLineItem.original_line_item_id.in_(line_item_id))
|
.filter(JournalEntryLineItem.original_line_item_id
|
||||||
|
.in_(line_item_id))
|
||||||
return db.session.scalar(select) > 0
|
return db.session.scalar(select) > 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ from wtforms.validators import DataRequired, Optional
|
|||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import lazy_gettext
|
from accounting.locale import lazy_gettext
|
||||||
from accounting.models import Account, VoucherLineItem
|
from accounting.models import Account, JournalEntryLineItem
|
||||||
from accounting.template_filters import format_amount
|
from accounting.template_filters import format_amount
|
||||||
from accounting.utils.cast import be
|
from accounting.utils.cast import be
|
||||||
from accounting.utils.random_id import new_id
|
from accounting.utils.random_id import new_id
|
||||||
@ -48,7 +48,7 @@ class OriginalLineItemExists:
|
|||||||
def __call__(self, form: FlaskForm, field: IntegerField) -> None:
|
def __call__(self, form: FlaskForm, field: IntegerField) -> None:
|
||||||
if field.data is None:
|
if field.data is None:
|
||||||
return
|
return
|
||||||
if db.session.get(VoucherLineItem, field.data) is None:
|
if db.session.get(JournalEntryLineItem, field.data) is None:
|
||||||
raise ValidationError(lazy_gettext(
|
raise ValidationError(lazy_gettext(
|
||||||
"The original line item does not exist."))
|
"The original line item does not exist."))
|
||||||
|
|
||||||
@ -60,8 +60,8 @@ class OriginalLineItemOppositeDebitCredit:
|
|||||||
def __call__(self, form: FlaskForm, field: IntegerField) -> None:
|
def __call__(self, form: FlaskForm, field: IntegerField) -> None:
|
||||||
if field.data is None:
|
if field.data is None:
|
||||||
return
|
return
|
||||||
original_line_item: VoucherLineItem | None \
|
original_line_item: JournalEntryLineItem | None \
|
||||||
= db.session.get(VoucherLineItem, field.data)
|
= db.session.get(JournalEntryLineItem, field.data)
|
||||||
if original_line_item is None:
|
if original_line_item is None:
|
||||||
return
|
return
|
||||||
if isinstance(form, CreditLineItemForm) \
|
if isinstance(form, CreditLineItemForm) \
|
||||||
@ -80,8 +80,8 @@ class OriginalLineItemNeedOffset:
|
|||||||
def __call__(self, form: FlaskForm, field: IntegerField) -> None:
|
def __call__(self, form: FlaskForm, field: IntegerField) -> None:
|
||||||
if field.data is None:
|
if field.data is None:
|
||||||
return
|
return
|
||||||
original_line_item: VoucherLineItem | None \
|
original_line_item: JournalEntryLineItem | None \
|
||||||
= db.session.get(VoucherLineItem, field.data)
|
= db.session.get(JournalEntryLineItem, field.data)
|
||||||
if original_line_item is None:
|
if original_line_item is None:
|
||||||
return
|
return
|
||||||
if not original_line_item.account.is_need_offset:
|
if not original_line_item.account.is_need_offset:
|
||||||
@ -96,8 +96,8 @@ class OriginalLineItemNotOffset:
|
|||||||
def __call__(self, form: FlaskForm, field: IntegerField) -> None:
|
def __call__(self, form: FlaskForm, field: IntegerField) -> None:
|
||||||
if field.data is None:
|
if field.data is None:
|
||||||
return
|
return
|
||||||
original_line_item: VoucherLineItem | None \
|
original_line_item: JournalEntryLineItem | None \
|
||||||
= db.session.get(VoucherLineItem, field.data)
|
= db.session.get(JournalEntryLineItem, field.data)
|
||||||
if original_line_item is None:
|
if original_line_item is None:
|
||||||
return
|
return
|
||||||
if original_line_item.original_line_item_id is not None:
|
if original_line_item.original_line_item_id is not None:
|
||||||
@ -152,8 +152,9 @@ class SameAccountAsOriginalLineItem:
|
|||||||
assert isinstance(form, LineItemForm)
|
assert isinstance(form, LineItemForm)
|
||||||
if field.data is None or form.original_line_item_id.data is None:
|
if field.data is None or form.original_line_item_id.data is None:
|
||||||
return
|
return
|
||||||
original_line_item: VoucherLineItem | None \
|
original_line_item: JournalEntryLineItem | None \
|
||||||
= db.session.get(VoucherLineItem, form.original_line_item_id.data)
|
= db.session.get(JournalEntryLineItem,
|
||||||
|
form.original_line_item_id.data)
|
||||||
if original_line_item is None:
|
if original_line_item is None:
|
||||||
return
|
return
|
||||||
if field.data != original_line_item.account_code:
|
if field.data != original_line_item.account_code:
|
||||||
@ -168,9 +169,10 @@ class KeepAccountWhenHavingOffset:
|
|||||||
assert isinstance(form, LineItemForm)
|
assert isinstance(form, LineItemForm)
|
||||||
if field.data is None or form.eid.data is None:
|
if field.data is None or form.eid.data is None:
|
||||||
return
|
return
|
||||||
line_item: VoucherLineItem | None = db.session.query(VoucherLineItem)\
|
line_item: JournalEntryLineItem | None = db.session\
|
||||||
.filter(VoucherLineItem.id == form.eid.data)\
|
.query(JournalEntryLineItem)\
|
||||||
.options(selectinload(VoucherLineItem.offsets)).first()
|
.filter(JournalEntryLineItem.id == form.eid.data)\
|
||||||
|
.options(selectinload(JournalEntryLineItem.offsets)).first()
|
||||||
if line_item is None or len(line_item.offsets) == 0:
|
if line_item is None or len(line_item.offsets) == 0:
|
||||||
return
|
return
|
||||||
if field.data != line_item.account_code:
|
if field.data != line_item.account_code:
|
||||||
@ -229,8 +231,9 @@ class NotExceedingOriginalLineItemNetBalance:
|
|||||||
assert isinstance(form, LineItemForm)
|
assert isinstance(form, LineItemForm)
|
||||||
if field.data is None or form.original_line_item_id.data is None:
|
if field.data is None or form.original_line_item_id.data is None:
|
||||||
return
|
return
|
||||||
original_line_item: VoucherLineItem | None \
|
original_line_item: JournalEntryLineItem | None \
|
||||||
= db.session.get(VoucherLineItem, form.original_line_item_id.data)
|
= db.session.get(JournalEntryLineItem,
|
||||||
|
form.original_line_item_id.data)
|
||||||
if original_line_item is None:
|
if original_line_item is None:
|
||||||
return
|
return
|
||||||
is_debit: bool = isinstance(form, DebitLineItemForm)
|
is_debit: bool = isinstance(form, DebitLineItemForm)
|
||||||
@ -239,13 +242,14 @@ class NotExceedingOriginalLineItemNetBalance:
|
|||||||
existing_line_item_id \
|
existing_line_item_id \
|
||||||
= {x.id for x in form.voucher_form.obj.line_items}
|
= {x.id for x in form.voucher_form.obj.line_items}
|
||||||
offset_total_func: sa.Function = sa.func.sum(sa.case(
|
offset_total_func: sa.Function = sa.func.sum(sa.case(
|
||||||
(be(VoucherLineItem.is_debit == is_debit), VoucherLineItem.amount),
|
(be(JournalEntryLineItem.is_debit == is_debit),
|
||||||
else_=-VoucherLineItem.amount))
|
JournalEntryLineItem.amount),
|
||||||
|
else_=-JournalEntryLineItem.amount))
|
||||||
offset_total_but_form: Decimal | None = db.session.scalar(
|
offset_total_but_form: Decimal | None = db.session.scalar(
|
||||||
sa.select(offset_total_func)
|
sa.select(offset_total_func)
|
||||||
.filter(be(VoucherLineItem.original_line_item_id
|
.filter(be(JournalEntryLineItem.original_line_item_id
|
||||||
== original_line_item.id),
|
== original_line_item.id),
|
||||||
VoucherLineItem.id.not_in(existing_line_item_id)))
|
JournalEntryLineItem.id.not_in(existing_line_item_id)))
|
||||||
if offset_total_but_form is None:
|
if offset_total_but_form is None:
|
||||||
offset_total_but_form = Decimal("0")
|
offset_total_but_form = Decimal("0")
|
||||||
offset_total_on_form: Decimal = sum(
|
offset_total_on_form: Decimal = sum(
|
||||||
@ -269,9 +273,11 @@ class NotLessThanOffsetTotal:
|
|||||||
return
|
return
|
||||||
is_debit: bool = isinstance(form, DebitLineItemForm)
|
is_debit: bool = isinstance(form, DebitLineItemForm)
|
||||||
select_offset_total: sa.Select = sa.select(sa.func.sum(sa.case(
|
select_offset_total: sa.Select = sa.select(sa.func.sum(sa.case(
|
||||||
(VoucherLineItem.is_debit != is_debit, VoucherLineItem.amount),
|
(JournalEntryLineItem.is_debit != is_debit,
|
||||||
else_=-VoucherLineItem.amount)))\
|
JournalEntryLineItem.amount),
|
||||||
.filter(be(VoucherLineItem.original_line_item_id == form.eid.data))
|
else_=-JournalEntryLineItem.amount)))\
|
||||||
|
.filter(be(JournalEntryLineItem.original_line_item_id
|
||||||
|
== form.eid.data))
|
||||||
offset_total: Decimal | None = db.session.scalar(select_offset_total)
|
offset_total: Decimal | None = db.session.scalar(select_offset_total)
|
||||||
if offset_total is not None and field.data < offset_total:
|
if offset_total is not None and field.data < offset_total:
|
||||||
raise ValidationError(lazy_gettext(
|
raise ValidationError(lazy_gettext(
|
||||||
@ -319,16 +325,16 @@ class LineItemForm(FlaskForm):
|
|||||||
return str(account)
|
return str(account)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __original_line_item(self) -> VoucherLineItem | None:
|
def __original_line_item(self) -> JournalEntryLineItem | None:
|
||||||
"""Returns the original line item.
|
"""Returns the original line item.
|
||||||
|
|
||||||
:return: The original line item.
|
:return: The original line item.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, "____original_line_item"):
|
if not hasattr(self, "____original_line_item"):
|
||||||
def get_line_item() -> VoucherLineItem | None:
|
def get_line_item() -> JournalEntryLineItem | None:
|
||||||
if self.original_line_item_id.data is None:
|
if self.original_line_item_id.data is None:
|
||||||
return None
|
return None
|
||||||
return db.session.get(VoucherLineItem,
|
return db.session.get(JournalEntryLineItem,
|
||||||
self.original_line_item_id.data)
|
self.original_line_item_id.data)
|
||||||
setattr(self, "____original_line_item", get_line_item())
|
setattr(self, "____original_line_item", get_line_item())
|
||||||
return getattr(self, "____original_line_item")
|
return getattr(self, "____original_line_item")
|
||||||
@ -371,22 +377,22 @@ class LineItemForm(FlaskForm):
|
|||||||
return account is not None and account.is_need_offset
|
return account is not None and account.is_need_offset
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def offsets(self) -> list[VoucherLineItem]:
|
def offsets(self) -> list[JournalEntryLineItem]:
|
||||||
"""Returns the offsets.
|
"""Returns the offsets.
|
||||||
|
|
||||||
:return: The offsets.
|
:return: The offsets.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, "__offsets"):
|
if not hasattr(self, "__offsets"):
|
||||||
def get_offsets() -> list[VoucherLineItem]:
|
def get_offsets() -> list[JournalEntryLineItem]:
|
||||||
if not self.is_need_offset or self.eid.data is None:
|
if not self.is_need_offset or self.eid.data is None:
|
||||||
return []
|
return []
|
||||||
return VoucherLineItem.query\
|
return JournalEntryLineItem.query\
|
||||||
.filter(VoucherLineItem.original_line_item_id
|
.filter(JournalEntryLineItem.original_line_item_id
|
||||||
== self.eid.data)\
|
== self.eid.data)\
|
||||||
.options(selectinload(VoucherLineItem.voucher),
|
.options(selectinload(JournalEntryLineItem.voucher),
|
||||||
selectinload(VoucherLineItem.account),
|
selectinload(JournalEntryLineItem.account),
|
||||||
selectinload(VoucherLineItem.offsets)
|
selectinload(JournalEntryLineItem.offsets)
|
||||||
.selectinload(VoucherLineItem.voucher)).all()
|
.selectinload(JournalEntryLineItem.voucher)).all()
|
||||||
setattr(self, "__offsets", get_offsets())
|
setattr(self, "__offsets", get_offsets())
|
||||||
return getattr(self, "__offsets")
|
return getattr(self, "__offsets")
|
||||||
|
|
||||||
@ -460,7 +466,7 @@ class DebitLineItemForm(LineItemForm):
|
|||||||
NotLessThanOffsetTotal()])
|
NotLessThanOffsetTotal()])
|
||||||
"""The amount."""
|
"""The amount."""
|
||||||
|
|
||||||
def populate_obj(self, obj: VoucherLineItem) -> None:
|
def populate_obj(self, obj: JournalEntryLineItem) -> None:
|
||||||
"""Populates the form data into a line item object.
|
"""Populates the form data into a line item object.
|
||||||
|
|
||||||
:param obj: The line item object.
|
:param obj: The line item object.
|
||||||
@ -468,7 +474,7 @@ class DebitLineItemForm(LineItemForm):
|
|||||||
"""
|
"""
|
||||||
is_new: bool = obj.id is None
|
is_new: bool = obj.id is None
|
||||||
if is_new:
|
if is_new:
|
||||||
obj.id = new_id(VoucherLineItem)
|
obj.id = new_id(JournalEntryLineItem)
|
||||||
obj.original_line_item_id = self.original_line_item_id.data
|
obj.original_line_item_id = self.original_line_item_id.data
|
||||||
obj.account_id = Account.find_by_code(self.account_code.data).id
|
obj.account_id = Account.find_by_code(self.account_code.data).id
|
||||||
obj.description = self.description.data
|
obj.description = self.description.data
|
||||||
@ -510,7 +516,7 @@ class CreditLineItemForm(LineItemForm):
|
|||||||
NotLessThanOffsetTotal()])
|
NotLessThanOffsetTotal()])
|
||||||
"""The amount."""
|
"""The amount."""
|
||||||
|
|
||||||
def populate_obj(self, obj: VoucherLineItem) -> None:
|
def populate_obj(self, obj: JournalEntryLineItem) -> None:
|
||||||
"""Populates the form data into a line item object.
|
"""Populates the form data into a line item object.
|
||||||
|
|
||||||
:param obj: The line item object.
|
:param obj: The line item object.
|
||||||
@ -518,7 +524,7 @@ class CreditLineItemForm(LineItemForm):
|
|||||||
"""
|
"""
|
||||||
is_new: bool = obj.id is None
|
is_new: bool = obj.id is None
|
||||||
if is_new:
|
if is_new:
|
||||||
obj.id = new_id(VoucherLineItem)
|
obj.id = new_id(JournalEntryLineItem)
|
||||||
obj.original_line_item_id = self.original_line_item_id.data
|
obj.original_line_item_id = self.original_line_item_id.data
|
||||||
obj.account_id = Account.find_by_code(self.account_code.data).id
|
obj.account_id = Account.find_by_code(self.account_code.data).id
|
||||||
obj.description = self.description.data
|
obj.description = self.description.data
|
||||||
|
@ -30,7 +30,7 @@ from wtforms.validators import DataRequired, ValidationError
|
|||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.locale import lazy_gettext
|
from accounting.locale import lazy_gettext
|
||||||
from accounting.models import Voucher, Account, VoucherLineItem, \
|
from accounting.models import Voucher, Account, JournalEntryLineItem, \
|
||||||
VoucherCurrency
|
VoucherCurrency
|
||||||
from accounting.voucher.utils.account_option import AccountOption
|
from accounting.voucher.utils.account_option import AccountOption
|
||||||
from accounting.voucher.utils.original_line_items import \
|
from accounting.voucher.utils.original_line_items import \
|
||||||
@ -133,7 +133,8 @@ class VoucherForm(FlaskForm):
|
|||||||
"""Whether we need the payable original line items."""
|
"""Whether we need the payable original line items."""
|
||||||
self._is_need_receivable: bool = False
|
self._is_need_receivable: bool = False
|
||||||
"""Whether we need the receivable original line items."""
|
"""Whether we need the receivable original line items."""
|
||||||
self.__original_line_item_options: list[VoucherLineItem] | None = None
|
self.__original_line_item_options: list[JournalEntryLineItem] | None \
|
||||||
|
= None
|
||||||
"""The options of the original line items."""
|
"""The options of the original line items."""
|
||||||
self.__net_balance_exceeded: dict[int, LazyString] | None = None
|
self.__net_balance_exceeded: dict[int, LazyString] | None = None
|
||||||
"""The original line items whose net balances were exceeded by the
|
"""The original line items whose net balances were exceeded by the
|
||||||
@ -161,8 +162,8 @@ class VoucherForm(FlaskForm):
|
|||||||
to_delete: set[int] = {x.id for x in obj.line_items
|
to_delete: set[int] = {x.id for x in obj.line_items
|
||||||
if x.id not in collector.to_keep}
|
if x.id not in collector.to_keep}
|
||||||
if len(to_delete) > 0:
|
if len(to_delete) > 0:
|
||||||
VoucherLineItem.query\
|
JournalEntryLineItem.query\
|
||||||
.filter(VoucherLineItem.id.in_(to_delete)).delete()
|
.filter(JournalEntryLineItem.id.in_(to_delete)).delete()
|
||||||
self.is_modified = True
|
self.is_modified = True
|
||||||
|
|
||||||
if is_new or db.session.is_modified(obj):
|
if is_new or db.session.is_modified(obj):
|
||||||
@ -222,9 +223,9 @@ class VoucherForm(FlaskForm):
|
|||||||
= [AccountOption(x) for x in Account.debit()
|
= [AccountOption(x) for x in Account.debit()
|
||||||
if not (x.code[0] == "2" and x.is_need_offset)]
|
if not (x.code[0] == "2" and x.is_need_offset)]
|
||||||
in_use: set[int] = set(db.session.scalars(
|
in_use: set[int] = set(db.session.scalars(
|
||||||
sa.select(VoucherLineItem.account_id)
|
sa.select(JournalEntryLineItem.account_id)
|
||||||
.filter(VoucherLineItem.is_debit)
|
.filter(JournalEntryLineItem.is_debit)
|
||||||
.group_by(VoucherLineItem.account_id)).all())
|
.group_by(JournalEntryLineItem.account_id)).all())
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
account.is_in_use = account.id in in_use
|
account.is_in_use = account.id in in_use
|
||||||
return accounts
|
return accounts
|
||||||
@ -239,9 +240,9 @@ class VoucherForm(FlaskForm):
|
|||||||
= [AccountOption(x) for x in Account.credit()
|
= [AccountOption(x) for x in Account.credit()
|
||||||
if not (x.code[0] == "1" and x.is_need_offset)]
|
if not (x.code[0] == "1" and x.is_need_offset)]
|
||||||
in_use: set[int] = set(db.session.scalars(
|
in_use: set[int] = set(db.session.scalars(
|
||||||
sa.select(VoucherLineItem.account_id)
|
sa.select(JournalEntryLineItem.account_id)
|
||||||
.filter(sa.not_(VoucherLineItem.is_debit))
|
.filter(sa.not_(JournalEntryLineItem.is_debit))
|
||||||
.group_by(VoucherLineItem.account_id)).all())
|
.group_by(JournalEntryLineItem.account_id)).all())
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
account.is_in_use = account.id in in_use
|
account.is_in_use = account.id in in_use
|
||||||
return accounts
|
return accounts
|
||||||
@ -264,7 +265,7 @@ class VoucherForm(FlaskForm):
|
|||||||
return DescriptionEditor()
|
return DescriptionEditor()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def original_line_item_options(self) -> list[VoucherLineItem]:
|
def original_line_item_options(self) -> list[JournalEntryLineItem]:
|
||||||
"""Returns the selectable original line items.
|
"""Returns the selectable original line items.
|
||||||
|
|
||||||
:return: The selectable original line items.
|
:return: The selectable original line items.
|
||||||
@ -289,8 +290,8 @@ class VoucherForm(FlaskForm):
|
|||||||
if len(original_line_item_id) == 0:
|
if len(original_line_item_id) == 0:
|
||||||
return None
|
return None
|
||||||
select: sa.Select = sa.select(sa.func.max(Voucher.date))\
|
select: sa.Select = sa.select(sa.func.max(Voucher.date))\
|
||||||
.join(VoucherLineItem)\
|
.join(JournalEntryLineItem)\
|
||||||
.filter(VoucherLineItem.id.in_(original_line_item_id))
|
.filter(JournalEntryLineItem.id.in_(original_line_item_id))
|
||||||
return db.session.scalar(select)
|
return db.session.scalar(select)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -302,8 +303,9 @@ class VoucherForm(FlaskForm):
|
|||||||
line_item_id: set[int] = {x.eid.data for x in self.line_items
|
line_item_id: set[int] = {x.eid.data for x in self.line_items
|
||||||
if x.eid.data is not None}
|
if x.eid.data is not None}
|
||||||
select: sa.Select = sa.select(sa.func.min(Voucher.date))\
|
select: sa.Select = sa.select(sa.func.min(Voucher.date))\
|
||||||
.join(VoucherLineItem)\
|
.join(JournalEntryLineItem)\
|
||||||
.filter(VoucherLineItem.original_line_item_id.in_(line_item_id))
|
.filter(JournalEntryLineItem.original_line_item_id
|
||||||
|
.in_(line_item_id))
|
||||||
return db.session.scalar(select)
|
return db.session.scalar(select)
|
||||||
|
|
||||||
|
|
||||||
@ -324,9 +326,9 @@ class LineItemCollector(t.Generic[T], ABC):
|
|||||||
"""The voucher form."""
|
"""The voucher form."""
|
||||||
self.__obj: Voucher = obj
|
self.__obj: Voucher = obj
|
||||||
"""The voucher object."""
|
"""The voucher object."""
|
||||||
self.__line_items: list[VoucherLineItem] = list(obj.line_items)
|
self.__line_items: list[JournalEntryLineItem] = list(obj.line_items)
|
||||||
"""The existing line items."""
|
"""The existing line items."""
|
||||||
self.__line_items_by_id: dict[int, VoucherLineItem] \
|
self.__line_items_by_id: dict[int, JournalEntryLineItem] \
|
||||||
= {x.id: x for x in self.__line_items}
|
= {x.id: x for x in self.__line_items}
|
||||||
"""A dictionary from the line item ID to their line items."""
|
"""A dictionary from the line item ID to their line items."""
|
||||||
self.__no_by_id: dict[int, int] \
|
self.__no_by_id: dict[int, int] \
|
||||||
@ -357,7 +359,7 @@ class LineItemCollector(t.Generic[T], ABC):
|
|||||||
:param no: The number of the line item.
|
:param no: The number of the line item.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
line_item: VoucherLineItem | None \
|
line_item: JournalEntryLineItem | None \
|
||||||
= self.__line_items_by_id.get(form.eid.data)
|
= self.__line_items_by_id.get(form.eid.data)
|
||||||
if line_item is not None:
|
if line_item is not None:
|
||||||
line_item.currency_code = currency_code
|
line_item.currency_code = currency_code
|
||||||
@ -366,7 +368,7 @@ class LineItemCollector(t.Generic[T], ABC):
|
|||||||
if db.session.is_modified(line_item):
|
if db.session.is_modified(line_item):
|
||||||
self.form.is_modified = True
|
self.form.is_modified = True
|
||||||
else:
|
else:
|
||||||
line_item = VoucherLineItem()
|
line_item = JournalEntryLineItem()
|
||||||
line_item.currency_code = currency_code
|
line_item.currency_code = currency_code
|
||||||
form.populate_obj(line_item)
|
form.populate_obj(line_item)
|
||||||
line_item.no = no
|
line_item.no = no
|
||||||
@ -386,10 +388,10 @@ class LineItemCollector(t.Generic[T], ABC):
|
|||||||
:param no: The number of the line item.
|
:param no: The number of the line item.
|
||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
candidates: list[VoucherLineItem] \
|
candidates: list[JournalEntryLineItem] \
|
||||||
= [x for x in self.__line_items
|
= [x for x in self.__line_items
|
||||||
if x.is_debit == is_debit and x.currency_code == currency_code]
|
if x.is_debit == is_debit and x.currency_code == currency_code]
|
||||||
line_item: VoucherLineItem
|
line_item: JournalEntryLineItem
|
||||||
if len(candidates) > 0:
|
if len(candidates) > 0:
|
||||||
candidates.sort(key=lambda x: x.no)
|
candidates.sort(key=lambda x: x.no)
|
||||||
line_item = candidates[0]
|
line_item = candidates[0]
|
||||||
@ -400,8 +402,8 @@ class LineItemCollector(t.Generic[T], ABC):
|
|||||||
if db.session.is_modified(line_item):
|
if db.session.is_modified(line_item):
|
||||||
self.form.is_modified = True
|
self.form.is_modified = True
|
||||||
else:
|
else:
|
||||||
line_item = VoucherLineItem()
|
line_item = JournalEntryLineItem()
|
||||||
line_item.id = new_id(VoucherLineItem)
|
line_item.id = new_id(JournalEntryLineItem)
|
||||||
line_item.is_debit = is_debit
|
line_item.is_debit = is_debit
|
||||||
line_item.currency_code = currency_code
|
line_item.currency_code = currency_code
|
||||||
line_item.account_id = Account.cash().id
|
line_item.account_id = Account.cash().id
|
||||||
|
@ -22,7 +22,7 @@ import typing as t
|
|||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.models import Account, VoucherLineItem
|
from accounting.models import Account, JournalEntryLineItem
|
||||||
|
|
||||||
|
|
||||||
class DescriptionAccount:
|
class DescriptionAccount:
|
||||||
@ -206,22 +206,25 @@ class DescriptionEditor:
|
|||||||
"""The debit tags."""
|
"""The debit tags."""
|
||||||
self.credit: DescriptionDebitCredit = DescriptionDebitCredit("credit")
|
self.credit: DescriptionDebitCredit = DescriptionDebitCredit("credit")
|
||||||
"""The credit tags."""
|
"""The credit tags."""
|
||||||
debit_credit: sa.Label = sa.case((VoucherLineItem.is_debit, "debit"),
|
debit_credit: sa.Label = sa.case(
|
||||||
else_="credit").label("debit_credit")
|
(JournalEntryLineItem.is_debit, "debit"),
|
||||||
|
else_="credit").label("debit_credit")
|
||||||
tag_type: sa.Label = sa.case(
|
tag_type: sa.Label = sa.case(
|
||||||
(VoucherLineItem.description.like("_%—_%—_%→_%"), "bus"),
|
(JournalEntryLineItem.description.like("_%—_%—_%→_%"), "bus"),
|
||||||
(sa.or_(VoucherLineItem.description.like("_%—_%→_%"),
|
(sa.or_(JournalEntryLineItem.description.like("_%—_%→_%"),
|
||||||
VoucherLineItem.description.like("_%—_%↔_%")), "travel"),
|
JournalEntryLineItem.description.like("_%—_%↔_%")),
|
||||||
|
"travel"),
|
||||||
else_="general").label("tag_type")
|
else_="general").label("tag_type")
|
||||||
tag: sa.Label = get_prefix(VoucherLineItem.description, "—")\
|
tag: sa.Label = get_prefix(JournalEntryLineItem.description, "—")\
|
||||||
.label("tag")
|
.label("tag")
|
||||||
select: sa.Select = sa.Select(debit_credit, tag_type, tag,
|
select: sa.Select = sa.Select(debit_credit, tag_type, tag,
|
||||||
VoucherLineItem.account_id,
|
JournalEntryLineItem.account_id,
|
||||||
sa.func.count().label("freq"))\
|
sa.func.count().label("freq"))\
|
||||||
.filter(VoucherLineItem.description.is_not(None),
|
.filter(JournalEntryLineItem.description.is_not(None),
|
||||||
VoucherLineItem.description.like("_%—_%"),
|
JournalEntryLineItem.description.like("_%—_%"),
|
||||||
VoucherLineItem.original_line_item_id.is_(None))\
|
JournalEntryLineItem.original_line_item_id.is_(None))\
|
||||||
.group_by(debit_credit, tag_type, tag, VoucherLineItem.account_id)
|
.group_by(debit_credit, tag_type, tag,
|
||||||
|
JournalEntryLineItem.account_id)
|
||||||
result: list[sa.Row] = db.session.execute(select).all()
|
result: list[sa.Row] = db.session.execute(select).all()
|
||||||
accounts: dict[int, Account] \
|
accounts: dict[int, Account] \
|
||||||
= {x.id: x for x in Account.query
|
= {x.id: x for x in Account.query
|
||||||
|
@ -21,7 +21,7 @@ import typing as t
|
|||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from accounting.models import VoucherLineItem
|
from accounting.models import JournalEntryLineItem
|
||||||
|
|
||||||
|
|
||||||
def offset_alias() -> sa.Alias:
|
def offset_alias() -> sa.Alias:
|
||||||
@ -36,4 +36,4 @@ def offset_alias() -> sa.Alias:
|
|||||||
def as_alias(alias: t.Any) -> sa.Alias:
|
def as_alias(alias: t.Any) -> sa.Alias:
|
||||||
return alias
|
return alias
|
||||||
|
|
||||||
return as_alias(sa.alias(as_from(VoucherLineItem), name="offset"))
|
return as_alias(sa.alias(as_from(JournalEntryLineItem), name="offset"))
|
||||||
|
@ -23,14 +23,14 @@ import sqlalchemy as sa
|
|||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.models import Account, Voucher, VoucherLineItem
|
from accounting.models import Account, Voucher, JournalEntryLineItem
|
||||||
from accounting.utils.cast import be
|
from accounting.utils.cast import be
|
||||||
from .offset_alias import offset_alias
|
from .offset_alias import offset_alias
|
||||||
|
|
||||||
|
|
||||||
def get_selectable_original_line_items(
|
def get_selectable_original_line_items(
|
||||||
line_item_id_on_form: set[int], is_payable: bool,
|
line_item_id_on_form: set[int], is_payable: bool,
|
||||||
is_receivable: bool) -> list[VoucherLineItem]:
|
is_receivable: bool) -> list[JournalEntryLineItem]:
|
||||||
"""Queries and returns the selectable original line items, with their net
|
"""Queries and returns the selectable original line items, with their net
|
||||||
balances. The offset amounts of the form is excluded.
|
balances. The offset amounts of the form is excluded.
|
||||||
|
|
||||||
@ -43,37 +43,40 @@ def get_selectable_original_line_items(
|
|||||||
"""
|
"""
|
||||||
assert is_payable or is_receivable
|
assert is_payable or is_receivable
|
||||||
offset: sa.Alias = offset_alias()
|
offset: sa.Alias = offset_alias()
|
||||||
net_balance: sa.Label = (VoucherLineItem.amount + sa.func.sum(sa.case(
|
net_balance: sa.Label = (JournalEntryLineItem.amount + sa.func.sum(sa.case(
|
||||||
(offset.c.id.in_(line_item_id_on_form), 0),
|
(offset.c.id.in_(line_item_id_on_form), 0),
|
||||||
(be(offset.c.is_debit == VoucherLineItem.is_debit), offset.c.amount),
|
(be(offset.c.is_debit == JournalEntryLineItem.is_debit),
|
||||||
|
offset.c.amount),
|
||||||
else_=-offset.c.amount))).label("net_balance")
|
else_=-offset.c.amount))).label("net_balance")
|
||||||
conditions: list[sa.BinaryExpression] = [Account.is_need_offset]
|
conditions: list[sa.BinaryExpression] = [Account.is_need_offset]
|
||||||
sub_conditions: list[sa.BinaryExpression] = []
|
sub_conditions: list[sa.BinaryExpression] = []
|
||||||
if is_payable:
|
if is_payable:
|
||||||
sub_conditions.append(sa.and_(Account.base_code.startswith("2"),
|
sub_conditions.append(sa.and_(Account.base_code.startswith("2"),
|
||||||
sa.not_(VoucherLineItem.is_debit)))
|
sa.not_(JournalEntryLineItem.is_debit)))
|
||||||
if is_receivable:
|
if is_receivable:
|
||||||
sub_conditions.append(sa.and_(Account.base_code.startswith("1"),
|
sub_conditions.append(sa.and_(Account.base_code.startswith("1"),
|
||||||
VoucherLineItem.is_debit))
|
JournalEntryLineItem.is_debit))
|
||||||
conditions.append(sa.or_(*sub_conditions))
|
conditions.append(sa.or_(*sub_conditions))
|
||||||
select_net_balances: sa.Select \
|
select_net_balances: sa.Select \
|
||||||
= sa.select(VoucherLineItem.id, net_balance)\
|
= sa.select(JournalEntryLineItem.id, net_balance)\
|
||||||
.join(Account)\
|
.join(Account)\
|
||||||
.join(offset, be(VoucherLineItem.id == offset.c.original_line_item_id),
|
.join(offset, be(JournalEntryLineItem.id
|
||||||
|
== offset.c.original_line_item_id),
|
||||||
isouter=True)\
|
isouter=True)\
|
||||||
.filter(*conditions)\
|
.filter(*conditions)\
|
||||||
.group_by(VoucherLineItem.id)\
|
.group_by(JournalEntryLineItem.id)\
|
||||||
.having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
|
.having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
|
||||||
net_balances: dict[int, Decimal] \
|
net_balances: dict[int, Decimal] \
|
||||||
= {x.id: x.net_balance
|
= {x.id: x.net_balance
|
||||||
for x in db.session.execute(select_net_balances).all()}
|
for x in db.session.execute(select_net_balances).all()}
|
||||||
line_items: list[VoucherLineItem] = VoucherLineItem.query\
|
line_items: list[JournalEntryLineItem] = JournalEntryLineItem.query\
|
||||||
.filter(VoucherLineItem.id.in_({x for x in net_balances}))\
|
.filter(JournalEntryLineItem.id.in_({x for x in net_balances}))\
|
||||||
.join(Voucher)\
|
.join(Voucher)\
|
||||||
.order_by(Voucher.date, VoucherLineItem.is_debit, VoucherLineItem.no)\
|
.order_by(Voucher.date, JournalEntryLineItem.is_debit,
|
||||||
.options(selectinload(VoucherLineItem.currency),
|
JournalEntryLineItem.no)\
|
||||||
selectinload(VoucherLineItem.account),
|
.options(selectinload(JournalEntryLineItem.currency),
|
||||||
selectinload(VoucherLineItem.voucher)).all()
|
selectinload(JournalEntryLineItem.account),
|
||||||
|
selectinload(JournalEntryLineItem.voucher)).all()
|
||||||
for line_item in line_items:
|
for line_item in line_items:
|
||||||
line_item.net_balance = line_item.amount \
|
line_item.net_balance = line_item.amount \
|
||||||
if net_balances[line_item.id] is None \
|
if net_balances[line_item.id] is None \
|
||||||
|
@ -42,7 +42,7 @@ class DescriptionEditorTestCase(unittest.TestCase):
|
|||||||
runner: FlaskCliRunner = self.app.test_cli_runner()
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import BaseAccount, Voucher, \
|
from accounting.models import BaseAccount, Voucher, \
|
||||||
VoucherLineItem
|
JournalEntryLineItem
|
||||||
result: Result
|
result: Result
|
||||||
result = runner.invoke(args="init-db")
|
result = runner.invoke(args="init-db")
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
@ -56,7 +56,7 @@ class DescriptionEditorTestCase(unittest.TestCase):
|
|||||||
"-u", "editor"])
|
"-u", "editor"])
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
Voucher.query.delete()
|
Voucher.query.delete()
|
||||||
VoucherLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
self.client, self.csrf_token = get_client(self.app, "editor")
|
self.client, self.csrf_token = get_client(self.app, "editor")
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ from flask.testing import FlaskCliRunner
|
|||||||
|
|
||||||
from test_site import db
|
from test_site import db
|
||||||
from testlib import create_test_app, get_client
|
from testlib import create_test_app, get_client
|
||||||
from testlib_offset import TestData, VoucherLineItemData, VoucherData, \
|
from testlib_offset import TestData, JournalEntryLineItemData, VoucherData, \
|
||||||
CurrencyData
|
CurrencyData
|
||||||
from testlib_voucher import Accounts, match_voucher_detail
|
from testlib_voucher import Accounts, match_voucher_detail
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ class OffsetTestCase(unittest.TestCase):
|
|||||||
runner: FlaskCliRunner = self.app.test_cli_runner()
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import BaseAccount, Voucher, \
|
from accounting.models import BaseAccount, Voucher, \
|
||||||
VoucherLineItem
|
JournalEntryLineItem
|
||||||
result: Result
|
result: Result
|
||||||
result = runner.invoke(args="init-db")
|
result = runner.invoke(args="init-db")
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
@ -63,7 +63,7 @@ class OffsetTestCase(unittest.TestCase):
|
|||||||
"-u", "editor"])
|
"-u", "editor"])
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
Voucher.query.delete()
|
Voucher.query.delete()
|
||||||
VoucherLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
self.client, self.csrf_token = get_client(self.app, "editor")
|
self.client, self.csrf_token = get_client(self.app, "editor")
|
||||||
self.data: TestData = TestData(self.app, self.client, self.csrf_token)
|
self.data: TestData = TestData(self.app, self.client, self.csrf_token)
|
||||||
@ -84,15 +84,15 @@ class OffsetTestCase(unittest.TestCase):
|
|||||||
self.data.e_r_or3d.voucher.days, [CurrencyData(
|
self.data.e_r_or3d.voucher.days, [CurrencyData(
|
||||||
"USD",
|
"USD",
|
||||||
[],
|
[],
|
||||||
[VoucherLineItemData(Accounts.RECEIVABLE,
|
[JournalEntryLineItemData(Accounts.RECEIVABLE,
|
||||||
self.data.e_r_or1d.description, "300",
|
self.data.e_r_or1d.description, "300",
|
||||||
original_line_item=self.data.e_r_or1d),
|
original_line_item=self.data.e_r_or1d),
|
||||||
VoucherLineItemData(Accounts.RECEIVABLE,
|
JournalEntryLineItemData(Accounts.RECEIVABLE,
|
||||||
self.data.e_r_or1d.description, "100",
|
self.data.e_r_or1d.description, "100",
|
||||||
original_line_item=self.data.e_r_or1d),
|
original_line_item=self.data.e_r_or1d),
|
||||||
VoucherLineItemData(Accounts.RECEIVABLE,
|
JournalEntryLineItemData(Accounts.RECEIVABLE,
|
||||||
self.data.e_r_or3d.description, "100",
|
self.data.e_r_or3d.description, "100",
|
||||||
original_line_item=self.data.e_r_or3d)])])
|
original_line_item=self.data.e_r_or3d)])])
|
||||||
|
|
||||||
# Non-existing original line item ID
|
# Non-existing original line item ID
|
||||||
form = voucher_data.new_form(self.csrf_token)
|
form = voucher_data.new_form(self.csrf_token)
|
||||||
@ -399,15 +399,15 @@ class OffsetTestCase(unittest.TestCase):
|
|||||||
voucher_data: VoucherData = VoucherData(
|
voucher_data: VoucherData = VoucherData(
|
||||||
self.data.e_p_or3c.voucher.days, [CurrencyData(
|
self.data.e_p_or3c.voucher.days, [CurrencyData(
|
||||||
"USD",
|
"USD",
|
||||||
[VoucherLineItemData(Accounts.PAYABLE,
|
[JournalEntryLineItemData(Accounts.PAYABLE,
|
||||||
self.data.e_p_or1c.description, "500",
|
self.data.e_p_or1c.description, "500",
|
||||||
original_line_item=self.data.e_p_or1c),
|
original_line_item=self.data.e_p_or1c),
|
||||||
VoucherLineItemData(Accounts.PAYABLE,
|
JournalEntryLineItemData(Accounts.PAYABLE,
|
||||||
self.data.e_p_or1c.description, "300",
|
self.data.e_p_or1c.description, "300",
|
||||||
original_line_item=self.data.e_p_or1c),
|
original_line_item=self.data.e_p_or1c),
|
||||||
VoucherLineItemData(Accounts.PAYABLE,
|
JournalEntryLineItemData(Accounts.PAYABLE,
|
||||||
self.data.e_p_or3c.description, "120",
|
self.data.e_p_or3c.description, "120",
|
||||||
original_line_item=self.data.e_p_or3c)],
|
original_line_item=self.data.e_p_or3c)],
|
||||||
[])])
|
[])])
|
||||||
|
|
||||||
# Non-existing original line item ID
|
# Non-existing original line item ID
|
||||||
|
@ -53,7 +53,7 @@ class CashReceiptVoucherTestCase(unittest.TestCase):
|
|||||||
runner: FlaskCliRunner = self.app.test_cli_runner()
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import BaseAccount, Voucher, \
|
from accounting.models import BaseAccount, Voucher, \
|
||||||
VoucherLineItem
|
JournalEntryLineItem
|
||||||
result: Result
|
result: Result
|
||||||
result = runner.invoke(args="init-db")
|
result = runner.invoke(args="init-db")
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
@ -67,7 +67,7 @@ class CashReceiptVoucherTestCase(unittest.TestCase):
|
|||||||
"-u", "editor"])
|
"-u", "editor"])
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
Voucher.query.delete()
|
Voucher.query.delete()
|
||||||
VoucherLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
self.client, self.csrf_token = get_client(self.app, "editor")
|
self.client, self.csrf_token = get_client(self.app, "editor")
|
||||||
|
|
||||||
@ -625,7 +625,7 @@ class CashDisbursementVoucherTestCase(unittest.TestCase):
|
|||||||
runner: FlaskCliRunner = self.app.test_cli_runner()
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import BaseAccount, Voucher, \
|
from accounting.models import BaseAccount, Voucher, \
|
||||||
VoucherLineItem
|
JournalEntryLineItem
|
||||||
result: Result
|
result: Result
|
||||||
result = runner.invoke(args="init-db")
|
result = runner.invoke(args="init-db")
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
@ -639,7 +639,7 @@ class CashDisbursementVoucherTestCase(unittest.TestCase):
|
|||||||
"-u", "editor"])
|
"-u", "editor"])
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
Voucher.query.delete()
|
Voucher.query.delete()
|
||||||
VoucherLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
self.client, self.csrf_token = get_client(self.app, "editor")
|
self.client, self.csrf_token = get_client(self.app, "editor")
|
||||||
|
|
||||||
@ -1204,7 +1204,7 @@ class TransferVoucherTestCase(unittest.TestCase):
|
|||||||
runner: FlaskCliRunner = self.app.test_cli_runner()
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import BaseAccount, Voucher, \
|
from accounting.models import BaseAccount, Voucher, \
|
||||||
VoucherLineItem
|
JournalEntryLineItem
|
||||||
result: Result
|
result: Result
|
||||||
result = runner.invoke(args="init-db")
|
result = runner.invoke(args="init-db")
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
@ -1218,7 +1218,7 @@ class TransferVoucherTestCase(unittest.TestCase):
|
|||||||
"-u", "editor"])
|
"-u", "editor"])
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
Voucher.query.delete()
|
Voucher.query.delete()
|
||||||
VoucherLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
self.client, self.csrf_token = get_client(self.app, "editor")
|
self.client, self.csrf_token = get_client(self.app, "editor")
|
||||||
|
|
||||||
@ -2056,7 +2056,7 @@ class VoucherReorderTestCase(unittest.TestCase):
|
|||||||
runner: FlaskCliRunner = self.app.test_cli_runner()
|
runner: FlaskCliRunner = self.app.test_cli_runner()
|
||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
from accounting.models import BaseAccount, Voucher, \
|
from accounting.models import BaseAccount, Voucher, \
|
||||||
VoucherLineItem
|
JournalEntryLineItem
|
||||||
result: Result
|
result: Result
|
||||||
result = runner.invoke(args="init-db")
|
result = runner.invoke(args="init-db")
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
@ -2070,7 +2070,7 @@ class VoucherReorderTestCase(unittest.TestCase):
|
|||||||
"-u", "editor"])
|
"-u", "editor"])
|
||||||
self.assertEqual(result.exit_code, 0)
|
self.assertEqual(result.exit_code, 0)
|
||||||
Voucher.query.delete()
|
Voucher.query.delete()
|
||||||
VoucherLineItem.query.delete()
|
JournalEntryLineItem.query.delete()
|
||||||
|
|
||||||
self.client, self.csrf_token = get_client(self.app, "editor")
|
self.client, self.csrf_token = get_client(self.app, "editor")
|
||||||
|
|
||||||
|
@ -29,22 +29,22 @@ from test_site import db
|
|||||||
from testlib_voucher import Accounts, match_voucher_detail, NEXT_URI
|
from testlib_voucher import Accounts, match_voucher_detail, NEXT_URI
|
||||||
|
|
||||||
|
|
||||||
class VoucherLineItemData:
|
class JournalEntryLineItemData:
|
||||||
"""The voucher line item data."""
|
"""The journal entry line item data."""
|
||||||
|
|
||||||
def __init__(self, account: str, description: str, amount: str,
|
def __init__(self, account: str, description: str, amount: str,
|
||||||
original_line_item: VoucherLineItemData | None = None):
|
original_line_item: JournalEntryLineItemData | None = None):
|
||||||
"""Constructs the voucher line item data.
|
"""Constructs the journal entry line item data.
|
||||||
|
|
||||||
:param account: The account code.
|
:param account: The account code.
|
||||||
:param description: The description.
|
:param description: The description.
|
||||||
:param amount: The amount.
|
:param amount: The amount.
|
||||||
:param original_line_item: The original voucher line item.
|
:param original_line_item: The original journal entry line item.
|
||||||
"""
|
"""
|
||||||
self.voucher: VoucherData | None = None
|
self.voucher: VoucherData | None = None
|
||||||
self.id: int = -1
|
self.id: int = -1
|
||||||
self.no: int = -1
|
self.no: int = -1
|
||||||
self.original_line_item: VoucherLineItemData | None \
|
self.original_line_item: JournalEntryLineItemData | None \
|
||||||
= original_line_item
|
= original_line_item
|
||||||
self.account: str = account
|
self.account: str = account
|
||||||
self.description: str = description
|
self.description: str = description
|
||||||
@ -77,8 +77,8 @@ class VoucherLineItemData:
|
|||||||
class CurrencyData:
|
class CurrencyData:
|
||||||
"""The voucher currency data."""
|
"""The voucher currency data."""
|
||||||
|
|
||||||
def __init__(self, currency: str, debit: list[VoucherLineItemData],
|
def __init__(self, currency: str, debit: list[JournalEntryLineItemData],
|
||||||
credit: list[VoucherLineItemData]):
|
credit: list[JournalEntryLineItemData]):
|
||||||
"""Constructs the voucher currency data.
|
"""Constructs the voucher currency data.
|
||||||
|
|
||||||
:param currency: The currency code.
|
:param currency: The currency code.
|
||||||
@ -86,8 +86,8 @@ class CurrencyData:
|
|||||||
:param credit: The credit line items.
|
:param credit: The credit line items.
|
||||||
"""
|
"""
|
||||||
self.code: str = currency
|
self.code: str = currency
|
||||||
self.debit: list[VoucherLineItemData] = debit
|
self.debit: list[JournalEntryLineItemData] = debit
|
||||||
self.credit: list[VoucherLineItemData] = credit
|
self.credit: list[JournalEntryLineItemData] = credit
|
||||||
|
|
||||||
def form(self, index: int, is_update: bool) -> dict[str, str]:
|
def form(self, index: int, is_update: bool) -> dict[str, str]:
|
||||||
"""Returns the currency as form data.
|
"""Returns the currency as form data.
|
||||||
@ -175,7 +175,7 @@ class TestData:
|
|||||||
self.csrf_token: str = csrf_token
|
self.csrf_token: str = csrf_token
|
||||||
|
|
||||||
def couple(description: str, amount: str, debit: str, credit: str) \
|
def couple(description: str, amount: str, debit: str, credit: str) \
|
||||||
-> tuple[VoucherLineItemData, VoucherLineItemData]:
|
-> tuple[JournalEntryLineItemData, JournalEntryLineItemData]:
|
||||||
"""Returns a couple of debit-credit line items.
|
"""Returns a couple of debit-credit line items.
|
||||||
|
|
||||||
:param description: The description.
|
:param description: The description.
|
||||||
@ -184,8 +184,8 @@ class TestData:
|
|||||||
:param credit: The credit account code.
|
:param credit: The credit account code.
|
||||||
:return: The debit line item and credit line item.
|
:return: The debit line item and credit line item.
|
||||||
"""
|
"""
|
||||||
return VoucherLineItemData(debit, description, amount),\
|
return JournalEntryLineItemData(debit, description, amount),\
|
||||||
VoucherLineItemData(credit, description, amount)
|
JournalEntryLineItemData(credit, description, amount)
|
||||||
|
|
||||||
# Receivable original line items
|
# Receivable original line items
|
||||||
self.e_r_or1d, self.e_r_or1c = couple(
|
self.e_r_or1d, self.e_r_or1c = couple(
|
||||||
|
Loading…
Reference in New Issue
Block a user