Removed the period filter from the unapplied original line items and unmatched offsets. It does not make sense for these two reports.
This commit is contained in:
@ -26,7 +26,6 @@ from sqlalchemy.orm import selectinload
|
||||
from accounting.locale import lazy_gettext
|
||||
from accounting.models import Currency, Account, JournalEntry, \
|
||||
JournalEntryLineItem
|
||||
from accounting.report.period import Period
|
||||
from accounting.report.utils.unapplied import get_net_balances
|
||||
|
||||
|
||||
@ -49,37 +48,25 @@ class OffsetPair:
|
||||
class OffsetMatcher:
|
||||
"""The offset matcher."""
|
||||
|
||||
def __init__(self, currency: Currency, account: Account,
|
||||
period: Period | None):
|
||||
def __init__(self, currency: Currency, account: Account):
|
||||
"""Constructs the offset matcher.
|
||||
|
||||
:param currency: The currency.
|
||||
:param account: The account.
|
||||
:param period: The period, or None for all time.
|
||||
"""
|
||||
self.__currency: Account = currency
|
||||
"""The currency."""
|
||||
self.__account: Account = account
|
||||
"""The account."""
|
||||
self.__period: Period | None = period
|
||||
"""The period."""
|
||||
self.matched_pairs: list[OffsetPair] = []
|
||||
"""A list of matched pairs."""
|
||||
self.__all_line_items: list[JournalEntryLineItem] = []
|
||||
"""The unapplied debits or credits and unmatched offsets."""
|
||||
self.line_items: list[JournalEntryLineItem] = []
|
||||
"""The unapplied debits or credits and unmatched offsets in the
|
||||
period."""
|
||||
self.__all_unapplied: list[JournalEntryLineItem] = []
|
||||
"""The unapplied debits or credits."""
|
||||
"""The unapplied debits or credits and unmatched offsets."""
|
||||
self.unapplied: list[JournalEntryLineItem] = []
|
||||
"""The unapplied debits or credits in the period."""
|
||||
self.__all_unmatched: list[JournalEntryLineItem] = []
|
||||
"""The unmatched offsets."""
|
||||
"""The unapplied debits or credits."""
|
||||
self.unmatched: list[JournalEntryLineItem] = []
|
||||
"""The unmatched offsets in the period."""
|
||||
"""The unmatched offsets."""
|
||||
self.__find_matches()
|
||||
self.__filter_by_period()
|
||||
|
||||
def __find_matches(self) -> None:
|
||||
"""Finds the matched original line items and their offsets.
|
||||
@ -87,10 +74,10 @@ class OffsetMatcher:
|
||||
:return: None.
|
||||
"""
|
||||
self.__get_line_items()
|
||||
if len(self.__all_unapplied) == 0 or len(self.__all_unmatched) == 0:
|
||||
if len(self.unapplied) == 0 or len(self.unmatched) == 0:
|
||||
return
|
||||
remains: list[JournalEntryLineItem] = self.__all_unmatched.copy()
|
||||
for original_item in self.__all_unapplied:
|
||||
remains: list[JournalEntryLineItem] = self.unmatched.copy()
|
||||
for original_item in self.unapplied:
|
||||
offset_candidates: list[JournalEntryLineItem] \
|
||||
= [x for x in remains
|
||||
if (x.journal_entry.date > original_item.journal_entry.date
|
||||
@ -117,7 +104,7 @@ class OffsetMatcher:
|
||||
account.
|
||||
"""
|
||||
net_balances: dict[int, Decimal | None] \
|
||||
= get_net_balances(self.__currency, self.__account, None)
|
||||
= get_net_balances(self.__currency, self.__account)
|
||||
unmatched_offset_condition: sa.BinaryExpression \
|
||||
= sa.and_(Account.id == self.__account.id,
|
||||
JournalEntryLineItem.currency_code
|
||||
@ -127,7 +114,7 @@ class OffsetMatcher:
|
||||
JournalEntryLineItem.is_debit),
|
||||
sa.and_(Account.base_code.startswith("1"),
|
||||
sa.not_(JournalEntryLineItem.is_debit))))
|
||||
self.__all_line_items = JournalEntryLineItem.query \
|
||||
self.line_items = JournalEntryLineItem.query \
|
||||
.join(Account).join(JournalEntry) \
|
||||
.filter(sa.or_(JournalEntryLineItem.id.in_(net_balances),
|
||||
unmatched_offset_condition)) \
|
||||
@ -135,16 +122,16 @@ class OffsetMatcher:
|
||||
JournalEntryLineItem.is_debit, JournalEntryLineItem.no) \
|
||||
.options(selectinload(JournalEntryLineItem.currency),
|
||||
selectinload(JournalEntryLineItem.journal_entry)).all()
|
||||
for line_item in self.__all_line_items:
|
||||
for line_item in self.line_items:
|
||||
line_item.is_offset = line_item.id in net_balances
|
||||
self.__all_unapplied = [x for x in self.__all_line_items
|
||||
if x.is_offset]
|
||||
for line_item in self.__all_unapplied:
|
||||
self.unapplied = [x for x in self.line_items
|
||||
if x.is_offset]
|
||||
for line_item in self.unapplied:
|
||||
line_item.net_balance = line_item.amount \
|
||||
if net_balances[line_item.id] is None \
|
||||
else net_balances[line_item.id]
|
||||
self.__all_unmatched = [x for x in self.__all_line_items
|
||||
if not x.is_offset]
|
||||
self.unmatched = [x for x in self.line_items
|
||||
if not x.is_offset]
|
||||
self.__populate_accumulated_balances()
|
||||
|
||||
def __populate_accumulated_balances(self) -> None:
|
||||
@ -153,7 +140,7 @@ class OffsetMatcher:
|
||||
:return: None.
|
||||
"""
|
||||
balance: Decimal = Decimal("0")
|
||||
for line_item in self.__all_line_items:
|
||||
for line_item in self.line_items:
|
||||
amount: Decimal = line_item.amount if line_item.is_offset \
|
||||
else line_item.net_balance
|
||||
if line_item.is_debit:
|
||||
@ -166,41 +153,23 @@ class OffsetMatcher:
|
||||
balance = balance - amount
|
||||
line_item.balance = balance
|
||||
|
||||
def __filter_by_period(self) -> None:
|
||||
"""Filters the line items by the period.
|
||||
|
||||
:return: None.
|
||||
"""
|
||||
self.line_items = self.__all_line_items.copy()
|
||||
if self.__period is not None:
|
||||
if self.__period.start is not None:
|
||||
self.line_items \
|
||||
= [x for x in self.line_items
|
||||
if x.journal_entry.date >= self.__period.start]
|
||||
if self.__period.end is not None:
|
||||
self.line_items \
|
||||
= [x for x in self.line_items
|
||||
if x.journal_entry.date <= self.__period.end]
|
||||
self.unapplied = [x for x in self.line_items if x.is_offset]
|
||||
self.unmatched = [x for x in self.line_items if not x.is_offset]
|
||||
|
||||
@property
|
||||
def status(self) -> str | LazyString:
|
||||
"""Returns the match status message.
|
||||
|
||||
:return: The match status message.
|
||||
"""
|
||||
if len(self.__all_unmatched) == 0:
|
||||
if len(self.unmatched) == 0:
|
||||
return lazy_gettext("There is no unmatched offset.")
|
||||
if len(self.matched_pairs) == 0:
|
||||
return lazy_gettext(
|
||||
"%(total)s unmatched offsets without original items.",
|
||||
total=len(self.__all_unmatched))
|
||||
total=len(self.unmatched))
|
||||
return lazy_gettext(
|
||||
"%(matches)s unmatched offsets out of %(total)s"
|
||||
" can match with their original items.",
|
||||
matches=len(self.matched_pairs),
|
||||
total=len(self.__all_unmatched))
|
||||
total=len(self.unmatched))
|
||||
|
||||
def match(self) -> None:
|
||||
"""Matches the original line items with offsets.
|
||||
|
@ -165,13 +165,11 @@ class ReportChooser:
|
||||
account: Account = self.__account
|
||||
if not account.is_need_offset:
|
||||
return OptionLink(gettext("Unapplied Items"),
|
||||
unapplied_url(self.__currency, None,
|
||||
self.__period),
|
||||
unapplied_url(self.__currency, None),
|
||||
self.__active_report == ReportType.UNAPPLIED,
|
||||
fa_icon="fa-solid fa-link-slash")
|
||||
return OptionLink(gettext("Unapplied Items"),
|
||||
unapplied_url(self.__currency, self.__account,
|
||||
self.__period),
|
||||
unapplied_url(self.__currency, self.__account),
|
||||
self.__active_report == ReportType.UNAPPLIED,
|
||||
fa_icon="fa-solid fa-link-slash")
|
||||
|
||||
@ -184,13 +182,11 @@ class ReportChooser:
|
||||
account: Account = self.__account
|
||||
if not account.is_need_offset:
|
||||
return OptionLink(gettext("Unmatched Offsets"),
|
||||
unmatched_url(self.__currency, None,
|
||||
self.__period),
|
||||
unmatched_url(self.__currency, None),
|
||||
self.__active_report == ReportType.UNMATCHED,
|
||||
fa_icon="fa-solid fa-file-circle-question")
|
||||
return OptionLink(gettext("Unmatched Offsets"),
|
||||
unmatched_url(self.__currency, self.__account,
|
||||
self.__period),
|
||||
unmatched_url(self.__currency, self.__account),
|
||||
self.__active_report == ReportType.UNMATCHED,
|
||||
fa_icon="fa-solid fa-file-circle-question")
|
||||
|
||||
|
@ -24,17 +24,14 @@ import sqlalchemy as sa
|
||||
from accounting import db
|
||||
from accounting.models import Currency, Account, JournalEntry, \
|
||||
JournalEntryLineItem
|
||||
from accounting.report.period import Period
|
||||
from accounting.utils.cast import be
|
||||
from accounting.utils.offset_alias import offset_alias
|
||||
|
||||
|
||||
def get_accounts_with_unapplied(currency: Currency,
|
||||
period: Period) -> list[Account]:
|
||||
def get_accounts_with_unapplied(currency: Currency) -> list[Account]:
|
||||
"""Returns the accounts with unapplied original line items.
|
||||
|
||||
:param currency: The currency.
|
||||
:param period: The period.
|
||||
:return: The accounts with unapplied original line items.
|
||||
"""
|
||||
offset: sa.Alias = offset_alias()
|
||||
@ -44,24 +41,18 @@ def get_accounts_with_unapplied(currency: Currency,
|
||||
(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,
|
||||
be(JournalEntryLineItem.currency_code == currency.code),
|
||||
sa.or_(sa.and_(Account.base_code.startswith("2"),
|
||||
sa.not_(JournalEntryLineItem.is_debit)),
|
||||
sa.and_(Account.base_code.startswith("1"),
|
||||
JournalEntryLineItem.is_debit))]
|
||||
if period.start is not None:
|
||||
conditions.append(JournalEntry.date >= period.start)
|
||||
if period.end is not None:
|
||||
conditions.append(JournalEntry.date <= period.end)
|
||||
select_unapplied: sa.Select \
|
||||
= sa.select(JournalEntryLineItem.id)\
|
||||
.join(JournalEntry).join(Account)\
|
||||
.join(offset, be(JournalEntryLineItem.id
|
||||
== offset.c.original_line_item_id),
|
||||
isouter=True)\
|
||||
.filter(*conditions)\
|
||||
.filter(Account.is_need_offset,
|
||||
be(JournalEntryLineItem.currency_code == currency.code),
|
||||
sa.or_(sa.and_(Account.base_code.startswith("2"),
|
||||
sa.not_(JournalEntryLineItem.is_debit)),
|
||||
sa.and_(Account.base_code.startswith("1"),
|
||||
JournalEntryLineItem.is_debit)))\
|
||||
.group_by(JournalEntryLineItem.id)\
|
||||
.having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
|
||||
|
||||
@ -81,13 +72,12 @@ def get_accounts_with_unapplied(currency: Currency,
|
||||
return accounts
|
||||
|
||||
|
||||
def get_net_balances(currency: Currency, account: Account,
|
||||
period: Period | None) -> dict[int, Decimal | None]:
|
||||
def get_net_balances(currency: Currency, account: Account) \
|
||||
-> dict[int, Decimal | None]:
|
||||
"""Returns the net balances of the unapplied line items of the account.
|
||||
|
||||
:param currency: The currency.
|
||||
:param account: The account.
|
||||
:param period: The period, or None for all time.
|
||||
:return: The net balances of the unapplied line items of the account.
|
||||
"""
|
||||
offset: sa.Alias = offset_alias()
|
||||
@ -97,25 +87,18 @@ def get_net_balances(currency: Currency, account: Account,
|
||||
(be(offset.c.is_debit == JournalEntryLineItem.is_debit),
|
||||
offset.c.amount),
|
||||
else_=-offset.c.amount))).label("net_balance")
|
||||
conditions: list[sa.BinaryExpression] \
|
||||
= [be(Account.id == account.id),
|
||||
be(JournalEntryLineItem.currency_code == currency.code),
|
||||
sa.or_(sa.and_(Account.base_code.startswith("2"),
|
||||
sa.not_(JournalEntryLineItem.is_debit)),
|
||||
sa.and_(Account.base_code.startswith("1"),
|
||||
JournalEntryLineItem.is_debit))]
|
||||
if period is not None:
|
||||
if period.start is not None:
|
||||
conditions.append(be(JournalEntry.date >= period.start))
|
||||
if period.end is not None:
|
||||
conditions.append(be(JournalEntry.date <= period.end))
|
||||
select_net_balances: sa.Select \
|
||||
= sa.select(JournalEntryLineItem.id, net_balance) \
|
||||
.join(JournalEntry).join(Account) \
|
||||
.join(offset, be(JournalEntryLineItem.id
|
||||
== offset.c.original_line_item_id),
|
||||
isouter=True) \
|
||||
.filter(*conditions) \
|
||||
.filter(be(Account.id == account.id),
|
||||
be(JournalEntryLineItem.currency_code == currency.code),
|
||||
sa.or_(sa.and_(Account.base_code.startswith("2"),
|
||||
sa.not_(JournalEntryLineItem.is_debit)),
|
||||
sa.and_(Account.base_code.startswith("1"),
|
||||
JournalEntryLineItem.is_debit))) \
|
||||
.group_by(JournalEntryLineItem.id) \
|
||||
.having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
|
||||
return {x.id: x.net_balance
|
||||
|
@ -22,37 +22,28 @@ import sqlalchemy as sa
|
||||
from accounting import db
|
||||
from accounting.models import Currency, Account, JournalEntry, \
|
||||
JournalEntryLineItem
|
||||
from accounting.report.period import Period
|
||||
from accounting.utils.cast import be
|
||||
|
||||
|
||||
def get_accounts_with_unmatched(currency: Currency,
|
||||
period: Period) -> list[Account]:
|
||||
def get_accounts_with_unmatched(currency: Currency) -> list[Account]:
|
||||
"""Returns the accounts with unmatched offsets.
|
||||
|
||||
:param currency: The currency.
|
||||
:param period: The period.
|
||||
:return: The accounts with unmatched offsets, with the "count" property set
|
||||
to the number of unmatched offsets.
|
||||
"""
|
||||
count_func: sa.Label \
|
||||
= sa.func.count(JournalEntryLineItem.id).label("count")
|
||||
conditions: list[sa.BinaryExpression] \
|
||||
= [Account.is_need_offset,
|
||||
be(JournalEntryLineItem.currency_code == currency.code),
|
||||
JournalEntryLineItem.original_line_item_id.is_(None),
|
||||
sa.or_(sa.and_(Account.base_code.startswith("2"),
|
||||
JournalEntryLineItem.is_debit),
|
||||
sa.and_(Account.base_code.startswith("1"),
|
||||
sa.not_(JournalEntryLineItem.is_debit)))]
|
||||
if period.start is not None:
|
||||
conditions.append(JournalEntry.date >= period.start)
|
||||
if period.end is not None:
|
||||
conditions.append(JournalEntry.date <= period.end)
|
||||
select: sa.Select = sa.select(Account.id, count_func)\
|
||||
.select_from(Account)\
|
||||
.join(JournalEntryLineItem, isouter=True).join(JournalEntry)\
|
||||
.filter(*conditions)\
|
||||
.filter(Account.is_need_offset,
|
||||
be(JournalEntryLineItem.currency_code == currency.code),
|
||||
JournalEntryLineItem.original_line_item_id.is_(None),
|
||||
sa.or_(sa.and_(Account.base_code.startswith("2"),
|
||||
JournalEntryLineItem.is_debit),
|
||||
sa.and_(Account.base_code.startswith("1"),
|
||||
sa.not_(JournalEntryLineItem.is_debit))))\
|
||||
.group_by(Account.id)\
|
||||
.having(count_func > 0)
|
||||
counts: dict[int, int] \
|
||||
|
@ -113,39 +113,35 @@ def balance_sheet_url(currency: Currency, period: Period) -> str:
|
||||
currency=currency, period=period)
|
||||
|
||||
|
||||
def unapplied_url(currency: Currency, account: Account | None,
|
||||
period: Period) -> str:
|
||||
def unapplied_url(currency: Currency, account: Account | None) -> str:
|
||||
"""Returns the URL of the unapplied original line items.
|
||||
|
||||
:param currency: The currency.
|
||||
:param account: The account, or None to list the accounts with unapplied
|
||||
original line items.
|
||||
:param period: The period.
|
||||
:return: The URL of the unapplied original line items.
|
||||
"""
|
||||
if account is None:
|
||||
if currency.code == default_currency_code() and period.is_default:
|
||||
if currency.code == default_currency_code():
|
||||
return url_for("accounting-report.unapplied-accounts-default")
|
||||
return url_for("accounting-report.unapplied-accounts",
|
||||
currency=currency, period=period)
|
||||
currency=currency)
|
||||
return url_for("accounting-report.unapplied",
|
||||
currency=currency, account=account, period=period)
|
||||
currency=currency, account=account)
|
||||
|
||||
|
||||
def unmatched_url(currency: Currency, account: Account | None,
|
||||
period: Period) -> str:
|
||||
def unmatched_url(currency: Currency, account: Account | None) -> str:
|
||||
"""Returns the URL of the unmatched offset line items.
|
||||
|
||||
:param currency: The currency.
|
||||
:param account: The account, or None to list the accounts with unmatched
|
||||
offset line items.
|
||||
:param period: The period.
|
||||
:return: The URL of the unmatched offset line items.
|
||||
"""
|
||||
if account is None:
|
||||
if currency.code == default_currency_code() and period.is_default:
|
||||
if currency.code == default_currency_code():
|
||||
return url_for("accounting-report.unmatched-accounts-default")
|
||||
return url_for("accounting-report.unmatched-accounts",
|
||||
currency=currency, period=period)
|
||||
currency=currency)
|
||||
return url_for("accounting-report.unmatched",
|
||||
currency=currency, account=account, period=period)
|
||||
currency=currency, account=account)
|
||||
|
Reference in New Issue
Block a user