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