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:
parent
26b4d4388f
commit
014d67f7b8
@ -26,11 +26,9 @@ from sqlalchemy.orm import selectinload
|
|||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, Account, JournalEntry, \
|
from accounting.models import Currency, Account, JournalEntry, \
|
||||||
JournalEntryLineItem
|
JournalEntryLineItem
|
||||||
from accounting.report.period import Period, PeriodChooser
|
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
from accounting.report.utils.csv_export import BaseCSVRow, csv_download, \
|
from accounting.report.utils.csv_export import BaseCSVRow, csv_download
|
||||||
period_spec
|
|
||||||
from accounting.report.utils.option_link import OptionLink
|
from accounting.report.utils.option_link import OptionLink
|
||||||
from accounting.report.utils.report_chooser import ReportChooser
|
from accounting.report.utils.report_chooser import ReportChooser
|
||||||
from accounting.report.utils.report_type import ReportType
|
from accounting.report.utils.report_type import ReportType
|
||||||
@ -80,14 +78,12 @@ class PageParams(BasePageParams):
|
|||||||
|
|
||||||
def __init__(self, currency: Currency,
|
def __init__(self, currency: Currency,
|
||||||
account: Account,
|
account: Account,
|
||||||
period: Period,
|
|
||||||
pagination: Pagination[JournalEntryLineItem],
|
pagination: Pagination[JournalEntryLineItem],
|
||||||
line_items: list[JournalEntryLineItem]):
|
line_items: list[JournalEntryLineItem]):
|
||||||
"""Constructs the HTML page parameters.
|
"""Constructs the HTML page parameters.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The account.
|
:param account: The account.
|
||||||
:param period: The period.
|
|
||||||
:param pagination: The pagination.
|
:param pagination: The pagination.
|
||||||
:param line_items: The line items.
|
:param line_items: The line items.
|
||||||
"""
|
"""
|
||||||
@ -95,15 +91,10 @@ class PageParams(BasePageParams):
|
|||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.account: Account = account
|
self.account: Account = account
|
||||||
"""The account."""
|
"""The account."""
|
||||||
self.period: Period = period
|
|
||||||
"""The period."""
|
|
||||||
self.pagination: Pagination[JournalEntryLineItem] = pagination
|
self.pagination: Pagination[JournalEntryLineItem] = pagination
|
||||||
"""The pagination."""
|
"""The pagination."""
|
||||||
self.line_items: list[JournalEntryLineItem] = line_items
|
self.line_items: list[JournalEntryLineItem] = line_items
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
self.period_chooser: PeriodChooser = PeriodChooser(
|
|
||||||
lambda x: unapplied_url(currency, account, x))
|
|
||||||
"""The period chooser."""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_data(self) -> bool:
|
def has_data(self) -> bool:
|
||||||
@ -120,7 +111,7 @@ class PageParams(BasePageParams):
|
|||||||
:return: The report chooser.
|
:return: The report chooser.
|
||||||
"""
|
"""
|
||||||
return ReportChooser(ReportType.UNAPPLIED, currency=self.currency,
|
return ReportChooser(ReportType.UNAPPLIED, currency=self.currency,
|
||||||
account=self.account, period=self.period)
|
account=self.account)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def currency_options(self) -> list[OptionLink]:
|
def currency_options(self) -> list[OptionLink]:
|
||||||
@ -129,8 +120,7 @@ class PageParams(BasePageParams):
|
|||||||
:return: The currency options.
|
:return: The currency options.
|
||||||
"""
|
"""
|
||||||
return self._get_currency_options(
|
return self._get_currency_options(
|
||||||
lambda x: unapplied_url(x, self.account, self.period),
|
lambda x: unapplied_url(x, self.account), self.currency)
|
||||||
self.currency)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def account_options(self) -> list[OptionLink]:
|
def account_options(self) -> list[OptionLink]:
|
||||||
@ -140,13 +130,12 @@ class PageParams(BasePageParams):
|
|||||||
"""
|
"""
|
||||||
options: list[OptionLink] \
|
options: list[OptionLink] \
|
||||||
= [OptionLink(gettext("Accounts"),
|
= [OptionLink(gettext("Accounts"),
|
||||||
unapplied_url(self.currency, None, self.period),
|
unapplied_url(self.currency, None),
|
||||||
False)]
|
False)]
|
||||||
options.extend(
|
options.extend(
|
||||||
[OptionLink(str(x),
|
[OptionLink(str(x), unapplied_url(self.currency, x),
|
||||||
unapplied_url(self.currency, x, self.period),
|
|
||||||
x.id == self.account.id)
|
x.id == self.account.id)
|
||||||
for x in get_accounts_with_unapplied(self.currency, self.period)])
|
for x in get_accounts_with_unapplied(self.currency)])
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
|
||||||
@ -168,19 +157,16 @@ def get_csv_rows(line_items: list[JournalEntryLineItem]) -> list[CSVRow]:
|
|||||||
class UnappliedOriginalLineItems(BaseReport):
|
class UnappliedOriginalLineItems(BaseReport):
|
||||||
"""The unapplied original line items."""
|
"""The unapplied original line items."""
|
||||||
|
|
||||||
def __init__(self, currency: Currency, account: Account, period: Period):
|
def __init__(self, currency: Currency, account: Account):
|
||||||
"""Constructs the unapplied original line items.
|
"""Constructs the unapplied original line items.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The account.
|
:param account: The account.
|
||||||
:param period: The period.
|
|
||||||
"""
|
"""
|
||||||
self.__currency: Currency = currency
|
self.__currency: Currency = currency
|
||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.__account: Account = account
|
self.__account: Account = account
|
||||||
"""The account."""
|
"""The account."""
|
||||||
self.__period: Period = period
|
|
||||||
"""The period."""
|
|
||||||
self.__line_items: list[JournalEntryLineItem] \
|
self.__line_items: list[JournalEntryLineItem] \
|
||||||
= self.__query_line_items()
|
= self.__query_line_items()
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
@ -191,7 +177,7 @@ class UnappliedOriginalLineItems(BaseReport):
|
|||||||
:return: The line items.
|
:return: The line items.
|
||||||
"""
|
"""
|
||||||
net_balances: dict[int, Decimal | None] \
|
net_balances: dict[int, Decimal | None] \
|
||||||
= get_net_balances(self.__currency, self.__account, self.__period)
|
= get_net_balances(self.__currency, self.__account)
|
||||||
line_items: list[JournalEntryLineItem] = JournalEntryLineItem.query \
|
line_items: list[JournalEntryLineItem] = JournalEntryLineItem.query \
|
||||||
.join(Account).join(JournalEntry) \
|
.join(Account).join(JournalEntry) \
|
||||||
.filter(JournalEntryLineItem.id.in_(net_balances)) \
|
.filter(JournalEntryLineItem.id.in_(net_balances)) \
|
||||||
@ -210,9 +196,8 @@ class UnappliedOriginalLineItems(BaseReport):
|
|||||||
|
|
||||||
:return: The response of the report for download.
|
:return: The response of the report for download.
|
||||||
"""
|
"""
|
||||||
filename: str = "unapplied-{currency}-{account}-{period}.csv"\
|
filename: str = "unapplied-{currency}-{account}.csv"\
|
||||||
.format(currency=self.__currency.code, account=self.__account.code,
|
.format(currency=self.__currency.code, account=self.__account.code)
|
||||||
period=period_spec(self.__period))
|
|
||||||
return csv_download(filename, get_csv_rows(self.__line_items))
|
return csv_download(filename, get_csv_rows(self.__line_items))
|
||||||
|
|
||||||
def html(self) -> str:
|
def html(self) -> str:
|
||||||
@ -225,7 +210,6 @@ class UnappliedOriginalLineItems(BaseReport):
|
|||||||
is_reversed=True)
|
is_reversed=True)
|
||||||
params: PageParams = PageParams(currency=self.__currency,
|
params: PageParams = PageParams(currency=self.__currency,
|
||||||
account=self.__account,
|
account=self.__account,
|
||||||
period=self.__period,
|
|
||||||
pagination=pagination,
|
pagination=pagination,
|
||||||
line_items=pagination.list)
|
line_items=pagination.list)
|
||||||
return render_template("accounting/report/unapplied.html",
|
return render_template("accounting/report/unapplied.html",
|
||||||
|
@ -24,7 +24,6 @@ from flask import render_template, Response
|
|||||||
|
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, Account
|
from accounting.models import Currency, Account
|
||||||
from accounting.report.period import Period, PeriodChooser
|
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
from accounting.report.utils.csv_export import BaseCSVRow, csv_download
|
from accounting.report.utils.csv_export import BaseCSVRow, csv_download
|
||||||
@ -61,24 +60,16 @@ class CSVRow(BaseCSVRow):
|
|||||||
class PageParams(BasePageParams):
|
class PageParams(BasePageParams):
|
||||||
"""The HTML page parameters."""
|
"""The HTML page parameters."""
|
||||||
|
|
||||||
def __init__(self, currency: Currency,
|
def __init__(self, currency: Currency, accounts: list[Account]):
|
||||||
period: Period,
|
|
||||||
accounts: list[Account]):
|
|
||||||
"""Constructs the HTML page parameters.
|
"""Constructs the HTML page parameters.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
:param accounts: The accounts.
|
:param accounts: The accounts.
|
||||||
"""
|
"""
|
||||||
self.currency: Currency = currency
|
self.currency: Currency = currency
|
||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.period: Period = period
|
|
||||||
"""The period."""
|
|
||||||
self.accounts: list[Account] = accounts
|
self.accounts: list[Account] = accounts
|
||||||
"""The accounts."""
|
"""The accounts."""
|
||||||
self.period_chooser: PeriodChooser = PeriodChooser(
|
|
||||||
lambda x: unapplied_url(currency, None, x))
|
|
||||||
"""The period chooser."""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_data(self) -> bool:
|
def has_data(self) -> bool:
|
||||||
@ -95,7 +86,7 @@ class PageParams(BasePageParams):
|
|||||||
:return: The report chooser.
|
:return: The report chooser.
|
||||||
"""
|
"""
|
||||||
return ReportChooser(ReportType.UNAPPLIED, currency=self.currency,
|
return ReportChooser(ReportType.UNAPPLIED, currency=self.currency,
|
||||||
account=None, period=self.period)
|
account=None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def currency_options(self) -> list[OptionLink]:
|
def currency_options(self) -> list[OptionLink]:
|
||||||
@ -103,8 +94,7 @@ class PageParams(BasePageParams):
|
|||||||
|
|
||||||
:return: The currency options.
|
:return: The currency options.
|
||||||
"""
|
"""
|
||||||
return self._get_currency_options(
|
return self._get_currency_options(lambda x: unapplied_url(x, None),
|
||||||
lambda x: unapplied_url(x, None, self.period),
|
|
||||||
self.currency)
|
self.currency)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -115,12 +105,10 @@ class PageParams(BasePageParams):
|
|||||||
"""
|
"""
|
||||||
options: list[OptionLink] \
|
options: list[OptionLink] \
|
||||||
= [OptionLink(gettext("Accounts"),
|
= [OptionLink(gettext("Accounts"),
|
||||||
unapplied_url(self.currency, None, self.period),
|
unapplied_url(self.currency, None),
|
||||||
True)]
|
True)]
|
||||||
options.extend(
|
options.extend(
|
||||||
[OptionLink(str(x),
|
[OptionLink(str(x), unapplied_url(self.currency, x), False)
|
||||||
unapplied_url(self.currency, x, self.period),
|
|
||||||
False)
|
|
||||||
for x in self.accounts])
|
for x in self.accounts])
|
||||||
return options
|
return options
|
||||||
|
|
||||||
@ -140,18 +128,14 @@ def get_csv_rows(accounts: list[Account]) -> list[CSVRow]:
|
|||||||
class AccountsWithUnappliedOriginalLineItems(BaseReport):
|
class AccountsWithUnappliedOriginalLineItems(BaseReport):
|
||||||
"""The accounts with unapplied original line items."""
|
"""The accounts with unapplied original line items."""
|
||||||
|
|
||||||
def __init__(self, currency: Currency, period: Period):
|
def __init__(self, currency: Currency):
|
||||||
"""Constructs the outstanding balances.
|
"""Constructs the outstanding balances.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
"""
|
"""
|
||||||
self.__currency: Currency = currency
|
self.__currency: Currency = currency
|
||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.__period: Period = period
|
self.__accounts: list[Account] = get_accounts_with_unapplied(currency)
|
||||||
"""The period."""
|
|
||||||
self.__accounts: list[Account] \
|
|
||||||
= get_accounts_with_unapplied(currency, period)
|
|
||||||
"""The accounts."""
|
"""The accounts."""
|
||||||
|
|
||||||
def csv(self) -> Response:
|
def csv(self) -> Response:
|
||||||
@ -169,5 +153,4 @@ class AccountsWithUnappliedOriginalLineItems(BaseReport):
|
|||||||
"""
|
"""
|
||||||
return render_template("accounting/report/unapplied-accounts.html",
|
return render_template("accounting/report/unapplied-accounts.html",
|
||||||
report=PageParams(currency=self.__currency,
|
report=PageParams(currency=self.__currency,
|
||||||
period=self.__period,
|
|
||||||
accounts=self.__accounts))
|
accounts=self.__accounts))
|
||||||
|
@ -25,11 +25,9 @@ from flask_babel import LazyString
|
|||||||
|
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, Account, JournalEntryLineItem
|
from accounting.models import Currency, Account, JournalEntryLineItem
|
||||||
from accounting.report.period import Period, PeriodChooser
|
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
from accounting.report.utils.csv_export import BaseCSVRow, csv_download, \
|
from accounting.report.utils.csv_export import BaseCSVRow, csv_download
|
||||||
period_spec
|
|
||||||
from accounting.report.utils.offset_matcher import OffsetMatcher, OffsetPair
|
from accounting.report.utils.offset_matcher import OffsetMatcher, OffsetPair
|
||||||
from accounting.report.utils.option_link import OptionLink
|
from accounting.report.utils.option_link import OptionLink
|
||||||
from accounting.report.utils.report_chooser import ReportChooser
|
from accounting.report.utils.report_chooser import ReportChooser
|
||||||
@ -82,7 +80,6 @@ class PageParams(BasePageParams):
|
|||||||
|
|
||||||
def __init__(self, currency: Currency,
|
def __init__(self, currency: Currency,
|
||||||
account: Account,
|
account: Account,
|
||||||
period: Period,
|
|
||||||
match_status: str | LazyString,
|
match_status: str | LazyString,
|
||||||
matched_pairs: list[OffsetPair],
|
matched_pairs: list[OffsetPair],
|
||||||
pagination: Pagination[JournalEntryLineItem],
|
pagination: Pagination[JournalEntryLineItem],
|
||||||
@ -91,7 +88,6 @@ class PageParams(BasePageParams):
|
|||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The account.
|
:param account: The account.
|
||||||
:param period: The period.
|
|
||||||
:param match_status: The match status message.
|
:param match_status: The match status message.
|
||||||
:param matched_pairs: A list of matched pairs.
|
:param matched_pairs: A list of matched pairs.
|
||||||
:param pagination: The pagination.
|
:param pagination: The pagination.
|
||||||
@ -101,8 +97,6 @@ class PageParams(BasePageParams):
|
|||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.account: Account = account
|
self.account: Account = account
|
||||||
"""The account."""
|
"""The account."""
|
||||||
self.period: Period = period
|
|
||||||
"""The period."""
|
|
||||||
self.match_status: str | LazyString = match_status
|
self.match_status: str | LazyString = match_status
|
||||||
"""The match status message."""
|
"""The match status message."""
|
||||||
self.matched_pairs: list[OffsetPair] = matched_pairs
|
self.matched_pairs: list[OffsetPair] = matched_pairs
|
||||||
@ -111,9 +105,6 @@ class PageParams(BasePageParams):
|
|||||||
"""The pagination."""
|
"""The pagination."""
|
||||||
self.line_items: list[JournalEntryLineItem] = line_items
|
self.line_items: list[JournalEntryLineItem] = line_items
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
self.period_chooser: PeriodChooser = PeriodChooser(
|
|
||||||
lambda x: unmatched_url(currency, account, x))
|
|
||||||
"""The period chooser."""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_data(self) -> bool:
|
def has_data(self) -> bool:
|
||||||
@ -139,8 +130,7 @@ class PageParams(BasePageParams):
|
|||||||
:return: The currency options.
|
:return: The currency options.
|
||||||
"""
|
"""
|
||||||
return self._get_currency_options(
|
return self._get_currency_options(
|
||||||
lambda x: unmatched_url(x, self.account, self.period),
|
lambda x: unmatched_url(x, self.account), self.currency)
|
||||||
self.currency)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def account_options(self) -> list[OptionLink]:
|
def account_options(self) -> list[OptionLink]:
|
||||||
@ -150,13 +140,12 @@ class PageParams(BasePageParams):
|
|||||||
"""
|
"""
|
||||||
options: list[OptionLink] \
|
options: list[OptionLink] \
|
||||||
= [OptionLink(gettext("Accounts"),
|
= [OptionLink(gettext("Accounts"),
|
||||||
unmatched_url(self.currency, None, self.period),
|
unmatched_url(self.currency, None),
|
||||||
False)]
|
False)]
|
||||||
options.extend(
|
options.extend(
|
||||||
[OptionLink(str(x),
|
[OptionLink(str(x), unmatched_url(self.currency, x),
|
||||||
unmatched_url(self.currency, x, self.period),
|
|
||||||
x.id == self.account.id)
|
x.id == self.account.id)
|
||||||
for x in get_accounts_with_unmatched(self.currency, self.period)])
|
for x in get_accounts_with_unmatched(self.currency)])
|
||||||
return options
|
return options
|
||||||
|
|
||||||
|
|
||||||
@ -178,21 +167,18 @@ def get_csv_rows(line_items: list[JournalEntryLineItem]) -> list[CSVRow]:
|
|||||||
class UnmatchedOffsets(BaseReport):
|
class UnmatchedOffsets(BaseReport):
|
||||||
"""The unmatched offsets."""
|
"""The unmatched offsets."""
|
||||||
|
|
||||||
def __init__(self, currency: Currency, account: Account, period: Period):
|
def __init__(self, currency: Currency, account: Account):
|
||||||
"""Constructs the unmatched offsets.
|
"""Constructs the unmatched offsets.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The account.
|
:param account: The account.
|
||||||
:param period: The period.
|
|
||||||
"""
|
"""
|
||||||
self.__currency: Currency = currency
|
self.__currency: Currency = currency
|
||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.__account: Account = account
|
self.__account: Account = account
|
||||||
"""The account."""
|
"""The account."""
|
||||||
self.__period: Period = period
|
|
||||||
"""The period."""
|
|
||||||
offset_matcher: OffsetMatcher \
|
offset_matcher: OffsetMatcher \
|
||||||
= OffsetMatcher(self.__currency, self.__account, self.__period)
|
= OffsetMatcher(self.__currency, self.__account)
|
||||||
self.__line_items: list[JournalEntryLineItem] \
|
self.__line_items: list[JournalEntryLineItem] \
|
||||||
= offset_matcher.line_items
|
= offset_matcher.line_items
|
||||||
"""The line items."""
|
"""The line items."""
|
||||||
@ -206,9 +192,8 @@ class UnmatchedOffsets(BaseReport):
|
|||||||
|
|
||||||
:return: The response of the report for download.
|
:return: The response of the report for download.
|
||||||
"""
|
"""
|
||||||
filename: str = "unmatched-{currency}-{account}-{period}.csv"\
|
filename: str = "unmatched-{currency}-{account}.csv"\
|
||||||
.format(currency=self.__currency.code, account=self.__account.code,
|
.format(currency=self.__currency.code, account=self.__account.code)
|
||||||
period=period_spec(self.__period))
|
|
||||||
return csv_download(filename, get_csv_rows(self.__line_items))
|
return csv_download(filename, get_csv_rows(self.__line_items))
|
||||||
|
|
||||||
def html(self) -> str:
|
def html(self) -> str:
|
||||||
@ -221,7 +206,6 @@ class UnmatchedOffsets(BaseReport):
|
|||||||
is_reversed=True)
|
is_reversed=True)
|
||||||
params: PageParams = PageParams(currency=self.__currency,
|
params: PageParams = PageParams(currency=self.__currency,
|
||||||
account=self.__account,
|
account=self.__account,
|
||||||
period=self.__period,
|
|
||||||
match_status=self.__match_status,
|
match_status=self.__match_status,
|
||||||
matched_pairs=self.__matched_pairs,
|
matched_pairs=self.__matched_pairs,
|
||||||
pagination=pagination,
|
pagination=pagination,
|
||||||
|
@ -24,7 +24,6 @@ from flask import render_template, Response
|
|||||||
|
|
||||||
from accounting.locale import gettext
|
from accounting.locale import gettext
|
||||||
from accounting.models import Currency, Account
|
from accounting.models import Currency, Account
|
||||||
from accounting.report.period import Period, PeriodChooser
|
|
||||||
from accounting.report.utils.base_page_params import BasePageParams
|
from accounting.report.utils.base_page_params import BasePageParams
|
||||||
from accounting.report.utils.base_report import BaseReport
|
from accounting.report.utils.base_report import BaseReport
|
||||||
from accounting.report.utils.csv_export import BaseCSVRow, csv_download
|
from accounting.report.utils.csv_export import BaseCSVRow, csv_download
|
||||||
@ -61,24 +60,16 @@ class CSVRow(BaseCSVRow):
|
|||||||
class PageParams(BasePageParams):
|
class PageParams(BasePageParams):
|
||||||
"""The HTML page parameters."""
|
"""The HTML page parameters."""
|
||||||
|
|
||||||
def __init__(self, currency: Currency,
|
def __init__(self, currency: Currency, accounts: list[Account]):
|
||||||
period: Period,
|
|
||||||
accounts: list[Account]):
|
|
||||||
"""Constructs the HTML page parameters.
|
"""Constructs the HTML page parameters.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
:param accounts: The accounts.
|
:param accounts: The accounts.
|
||||||
"""
|
"""
|
||||||
self.currency: Currency = currency
|
self.currency: Currency = currency
|
||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.period: Period = period
|
|
||||||
"""The period."""
|
|
||||||
self.accounts: list[Account] = accounts
|
self.accounts: list[Account] = accounts
|
||||||
"""The accounts."""
|
"""The accounts."""
|
||||||
self.period_chooser: PeriodChooser = PeriodChooser(
|
|
||||||
lambda x: unmatched_url(currency, None, x))
|
|
||||||
"""The period chooser."""
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_data(self) -> bool:
|
def has_data(self) -> bool:
|
||||||
@ -95,7 +86,7 @@ class PageParams(BasePageParams):
|
|||||||
:return: The report chooser.
|
:return: The report chooser.
|
||||||
"""
|
"""
|
||||||
return ReportChooser(ReportType.UNMATCHED, currency=self.currency,
|
return ReportChooser(ReportType.UNMATCHED, currency=self.currency,
|
||||||
account=None, period=self.period)
|
account=None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def currency_options(self) -> list[OptionLink]:
|
def currency_options(self) -> list[OptionLink]:
|
||||||
@ -103,8 +94,7 @@ class PageParams(BasePageParams):
|
|||||||
|
|
||||||
:return: The currency options.
|
:return: The currency options.
|
||||||
"""
|
"""
|
||||||
return self._get_currency_options(
|
return self._get_currency_options(lambda x: unmatched_url(x, None),
|
||||||
lambda x: unmatched_url(x, None, self.period),
|
|
||||||
self.currency)
|
self.currency)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -115,12 +105,10 @@ class PageParams(BasePageParams):
|
|||||||
"""
|
"""
|
||||||
options: list[OptionLink] \
|
options: list[OptionLink] \
|
||||||
= [OptionLink(gettext("Accounts"),
|
= [OptionLink(gettext("Accounts"),
|
||||||
unmatched_url(self.currency, None, self.period),
|
unmatched_url(self.currency, None),
|
||||||
True)]
|
True)]
|
||||||
options.extend(
|
options.extend(
|
||||||
[OptionLink(str(x),
|
[OptionLink(str(x), unmatched_url(self.currency, x), False)
|
||||||
unmatched_url(self.currency, x, self.period),
|
|
||||||
False)
|
|
||||||
for x in self.accounts])
|
for x in self.accounts])
|
||||||
return options
|
return options
|
||||||
|
|
||||||
@ -140,18 +128,15 @@ def get_csv_rows(accounts: list[Account]) -> list[CSVRow]:
|
|||||||
class AccountsWithUnmatchedOffsets(BaseReport):
|
class AccountsWithUnmatchedOffsets(BaseReport):
|
||||||
"""The accounts with unmatched offsets."""
|
"""The accounts with unmatched offsets."""
|
||||||
|
|
||||||
def __init__(self, currency: Currency, period: Period):
|
def __init__(self, currency: Currency):
|
||||||
"""Constructs the outstanding balances.
|
"""Constructs the outstanding balances.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
"""
|
"""
|
||||||
self.__currency: Currency = currency
|
self.__currency: Currency = currency
|
||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.__period: Period = period
|
|
||||||
"""The period."""
|
|
||||||
self.__accounts: list[Account] \
|
self.__accounts: list[Account] \
|
||||||
= get_accounts_with_unmatched(currency, period)
|
= get_accounts_with_unmatched(currency)
|
||||||
"""The accounts."""
|
"""The accounts."""
|
||||||
|
|
||||||
def csv(self) -> Response:
|
def csv(self) -> Response:
|
||||||
@ -169,5 +154,4 @@ class AccountsWithUnmatchedOffsets(BaseReport):
|
|||||||
"""
|
"""
|
||||||
return render_template("accounting/report/unmatched-accounts.html",
|
return render_template("accounting/report/unmatched-accounts.html",
|
||||||
report=PageParams(currency=self.__currency,
|
report=PageParams(currency=self.__currency,
|
||||||
period=self.__period,
|
|
||||||
accounts=self.__accounts))
|
accounts=self.__accounts))
|
||||||
|
@ -26,7 +26,6 @@ from sqlalchemy.orm import selectinload
|
|||||||
from accounting.locale import lazy_gettext
|
from accounting.locale import lazy_gettext
|
||||||
from accounting.models import Currency, Account, JournalEntry, \
|
from accounting.models import Currency, Account, JournalEntry, \
|
||||||
JournalEntryLineItem
|
JournalEntryLineItem
|
||||||
from accounting.report.period import Period
|
|
||||||
from accounting.report.utils.unapplied import get_net_balances
|
from accounting.report.utils.unapplied import get_net_balances
|
||||||
|
|
||||||
|
|
||||||
@ -49,37 +48,25 @@ class OffsetPair:
|
|||||||
class OffsetMatcher:
|
class OffsetMatcher:
|
||||||
"""The offset matcher."""
|
"""The offset matcher."""
|
||||||
|
|
||||||
def __init__(self, currency: Currency, account: Account,
|
def __init__(self, currency: Currency, account: Account):
|
||||||
period: Period | None):
|
|
||||||
"""Constructs the offset matcher.
|
"""Constructs the offset matcher.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The account.
|
:param account: The account.
|
||||||
:param period: The period, or None for all time.
|
|
||||||
"""
|
"""
|
||||||
self.__currency: Account = currency
|
self.__currency: Account = currency
|
||||||
"""The currency."""
|
"""The currency."""
|
||||||
self.__account: Account = account
|
self.__account: Account = account
|
||||||
"""The account."""
|
"""The account."""
|
||||||
self.__period: Period | None = period
|
|
||||||
"""The period."""
|
|
||||||
self.matched_pairs: list[OffsetPair] = []
|
self.matched_pairs: list[OffsetPair] = []
|
||||||
"""A list of matched pairs."""
|
"""A list of matched pairs."""
|
||||||
self.__all_line_items: list[JournalEntryLineItem] = []
|
|
||||||
"""The unapplied debits or credits and unmatched offsets."""
|
|
||||||
self.line_items: list[JournalEntryLineItem] = []
|
self.line_items: list[JournalEntryLineItem] = []
|
||||||
"""The unapplied debits or credits and unmatched offsets in the
|
"""The unapplied debits or credits and unmatched offsets."""
|
||||||
period."""
|
|
||||||
self.__all_unapplied: list[JournalEntryLineItem] = []
|
|
||||||
"""The unapplied debits or credits."""
|
|
||||||
self.unapplied: list[JournalEntryLineItem] = []
|
self.unapplied: list[JournalEntryLineItem] = []
|
||||||
"""The unapplied debits or credits in the period."""
|
"""The unapplied debits or credits."""
|
||||||
self.__all_unmatched: list[JournalEntryLineItem] = []
|
|
||||||
"""The unmatched offsets."""
|
|
||||||
self.unmatched: list[JournalEntryLineItem] = []
|
self.unmatched: list[JournalEntryLineItem] = []
|
||||||
"""The unmatched offsets in the period."""
|
"""The unmatched offsets."""
|
||||||
self.__find_matches()
|
self.__find_matches()
|
||||||
self.__filter_by_period()
|
|
||||||
|
|
||||||
def __find_matches(self) -> None:
|
def __find_matches(self) -> None:
|
||||||
"""Finds the matched original line items and their offsets.
|
"""Finds the matched original line items and their offsets.
|
||||||
@ -87,10 +74,10 @@ class OffsetMatcher:
|
|||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
self.__get_line_items()
|
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
|
return
|
||||||
remains: list[JournalEntryLineItem] = self.__all_unmatched.copy()
|
remains: list[JournalEntryLineItem] = self.unmatched.copy()
|
||||||
for original_item in self.__all_unapplied:
|
for original_item in self.unapplied:
|
||||||
offset_candidates: list[JournalEntryLineItem] \
|
offset_candidates: list[JournalEntryLineItem] \
|
||||||
= [x for x in remains
|
= [x for x in remains
|
||||||
if (x.journal_entry.date > original_item.journal_entry.date
|
if (x.journal_entry.date > original_item.journal_entry.date
|
||||||
@ -117,7 +104,7 @@ class OffsetMatcher:
|
|||||||
account.
|
account.
|
||||||
"""
|
"""
|
||||||
net_balances: dict[int, Decimal | None] \
|
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 \
|
unmatched_offset_condition: sa.BinaryExpression \
|
||||||
= sa.and_(Account.id == self.__account.id,
|
= sa.and_(Account.id == self.__account.id,
|
||||||
JournalEntryLineItem.currency_code
|
JournalEntryLineItem.currency_code
|
||||||
@ -127,7 +114,7 @@ class OffsetMatcher:
|
|||||||
JournalEntryLineItem.is_debit),
|
JournalEntryLineItem.is_debit),
|
||||||
sa.and_(Account.base_code.startswith("1"),
|
sa.and_(Account.base_code.startswith("1"),
|
||||||
sa.not_(JournalEntryLineItem.is_debit))))
|
sa.not_(JournalEntryLineItem.is_debit))))
|
||||||
self.__all_line_items = JournalEntryLineItem.query \
|
self.line_items = JournalEntryLineItem.query \
|
||||||
.join(Account).join(JournalEntry) \
|
.join(Account).join(JournalEntry) \
|
||||||
.filter(sa.or_(JournalEntryLineItem.id.in_(net_balances),
|
.filter(sa.or_(JournalEntryLineItem.id.in_(net_balances),
|
||||||
unmatched_offset_condition)) \
|
unmatched_offset_condition)) \
|
||||||
@ -135,15 +122,15 @@ class OffsetMatcher:
|
|||||||
JournalEntryLineItem.is_debit, JournalEntryLineItem.no) \
|
JournalEntryLineItem.is_debit, JournalEntryLineItem.no) \
|
||||||
.options(selectinload(JournalEntryLineItem.currency),
|
.options(selectinload(JournalEntryLineItem.currency),
|
||||||
selectinload(JournalEntryLineItem.journal_entry)).all()
|
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
|
line_item.is_offset = line_item.id in net_balances
|
||||||
self.__all_unapplied = [x for x in self.__all_line_items
|
self.unapplied = [x for x in self.line_items
|
||||||
if x.is_offset]
|
if x.is_offset]
|
||||||
for line_item in self.__all_unapplied:
|
for line_item in self.unapplied:
|
||||||
line_item.net_balance = line_item.amount \
|
line_item.net_balance = line_item.amount \
|
||||||
if net_balances[line_item.id] is None \
|
if net_balances[line_item.id] is None \
|
||||||
else net_balances[line_item.id]
|
else net_balances[line_item.id]
|
||||||
self.__all_unmatched = [x for x in self.__all_line_items
|
self.unmatched = [x for x in self.line_items
|
||||||
if not x.is_offset]
|
if not x.is_offset]
|
||||||
self.__populate_accumulated_balances()
|
self.__populate_accumulated_balances()
|
||||||
|
|
||||||
@ -153,7 +140,7 @@ class OffsetMatcher:
|
|||||||
:return: None.
|
:return: None.
|
||||||
"""
|
"""
|
||||||
balance: Decimal = Decimal("0")
|
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 \
|
amount: Decimal = line_item.amount if line_item.is_offset \
|
||||||
else line_item.net_balance
|
else line_item.net_balance
|
||||||
if line_item.is_debit:
|
if line_item.is_debit:
|
||||||
@ -166,41 +153,23 @@ class OffsetMatcher:
|
|||||||
balance = balance - amount
|
balance = balance - amount
|
||||||
line_item.balance = balance
|
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
|
@property
|
||||||
def status(self) -> str | LazyString:
|
def status(self) -> str | LazyString:
|
||||||
"""Returns the match status message.
|
"""Returns the match status message.
|
||||||
|
|
||||||
:return: 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.")
|
return lazy_gettext("There is no unmatched offset.")
|
||||||
if len(self.matched_pairs) == 0:
|
if len(self.matched_pairs) == 0:
|
||||||
return lazy_gettext(
|
return lazy_gettext(
|
||||||
"%(total)s unmatched offsets without original items.",
|
"%(total)s unmatched offsets without original items.",
|
||||||
total=len(self.__all_unmatched))
|
total=len(self.unmatched))
|
||||||
return lazy_gettext(
|
return lazy_gettext(
|
||||||
"%(matches)s unmatched offsets out of %(total)s"
|
"%(matches)s unmatched offsets out of %(total)s"
|
||||||
" can match with their original items.",
|
" can match with their original items.",
|
||||||
matches=len(self.matched_pairs),
|
matches=len(self.matched_pairs),
|
||||||
total=len(self.__all_unmatched))
|
total=len(self.unmatched))
|
||||||
|
|
||||||
def match(self) -> None:
|
def match(self) -> None:
|
||||||
"""Matches the original line items with offsets.
|
"""Matches the original line items with offsets.
|
||||||
|
@ -165,13 +165,11 @@ class ReportChooser:
|
|||||||
account: Account = self.__account
|
account: Account = self.__account
|
||||||
if not account.is_need_offset:
|
if not account.is_need_offset:
|
||||||
return OptionLink(gettext("Unapplied Items"),
|
return OptionLink(gettext("Unapplied Items"),
|
||||||
unapplied_url(self.__currency, None,
|
unapplied_url(self.__currency, None),
|
||||||
self.__period),
|
|
||||||
self.__active_report == ReportType.UNAPPLIED,
|
self.__active_report == ReportType.UNAPPLIED,
|
||||||
fa_icon="fa-solid fa-link-slash")
|
fa_icon="fa-solid fa-link-slash")
|
||||||
return OptionLink(gettext("Unapplied Items"),
|
return OptionLink(gettext("Unapplied Items"),
|
||||||
unapplied_url(self.__currency, self.__account,
|
unapplied_url(self.__currency, self.__account),
|
||||||
self.__period),
|
|
||||||
self.__active_report == ReportType.UNAPPLIED,
|
self.__active_report == ReportType.UNAPPLIED,
|
||||||
fa_icon="fa-solid fa-link-slash")
|
fa_icon="fa-solid fa-link-slash")
|
||||||
|
|
||||||
@ -184,13 +182,11 @@ class ReportChooser:
|
|||||||
account: Account = self.__account
|
account: Account = self.__account
|
||||||
if not account.is_need_offset:
|
if not account.is_need_offset:
|
||||||
return OptionLink(gettext("Unmatched Offsets"),
|
return OptionLink(gettext("Unmatched Offsets"),
|
||||||
unmatched_url(self.__currency, None,
|
unmatched_url(self.__currency, None),
|
||||||
self.__period),
|
|
||||||
self.__active_report == ReportType.UNMATCHED,
|
self.__active_report == ReportType.UNMATCHED,
|
||||||
fa_icon="fa-solid fa-file-circle-question")
|
fa_icon="fa-solid fa-file-circle-question")
|
||||||
return OptionLink(gettext("Unmatched Offsets"),
|
return OptionLink(gettext("Unmatched Offsets"),
|
||||||
unmatched_url(self.__currency, self.__account,
|
unmatched_url(self.__currency, self.__account),
|
||||||
self.__period),
|
|
||||||
self.__active_report == ReportType.UNMATCHED,
|
self.__active_report == ReportType.UNMATCHED,
|
||||||
fa_icon="fa-solid fa-file-circle-question")
|
fa_icon="fa-solid fa-file-circle-question")
|
||||||
|
|
||||||
|
@ -24,17 +24,14 @@ import sqlalchemy as sa
|
|||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.models import Currency, Account, JournalEntry, \
|
from accounting.models import Currency, Account, JournalEntry, \
|
||||||
JournalEntryLineItem
|
JournalEntryLineItem
|
||||||
from accounting.report.period import Period
|
|
||||||
from accounting.utils.cast import be
|
from accounting.utils.cast import be
|
||||||
from accounting.utils.offset_alias import offset_alias
|
from accounting.utils.offset_alias import offset_alias
|
||||||
|
|
||||||
|
|
||||||
def get_accounts_with_unapplied(currency: Currency,
|
def get_accounts_with_unapplied(currency: Currency) -> list[Account]:
|
||||||
period: Period) -> list[Account]:
|
|
||||||
"""Returns the accounts with unapplied original line items.
|
"""Returns the accounts with unapplied original line items.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
:return: The accounts with unapplied original line items.
|
:return: The accounts with unapplied original line items.
|
||||||
"""
|
"""
|
||||||
offset: sa.Alias = offset_alias()
|
offset: sa.Alias = offset_alias()
|
||||||
@ -44,24 +41,18 @@ def get_accounts_with_unapplied(currency: Currency,
|
|||||||
(be(offset.c.is_debit == JournalEntryLineItem.is_debit),
|
(be(offset.c.is_debit == JournalEntryLineItem.is_debit),
|
||||||
offset.c.amount),
|
offset.c.amount),
|
||||||
else_=-offset.c.amount))).label("net_balance")
|
else_=-offset.c.amount))).label("net_balance")
|
||||||
conditions: list[sa.BinaryExpression] \
|
|
||||||
= [Account.is_need_offset,
|
|
||||||
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 \
|
select_unapplied: sa.Select \
|
||||||
= sa.select(JournalEntryLineItem.id)\
|
= sa.select(JournalEntryLineItem.id)\
|
||||||
.join(JournalEntry).join(Account)\
|
.join(JournalEntry).join(Account)\
|
||||||
.join(offset, be(JournalEntryLineItem.id
|
.join(offset, be(JournalEntryLineItem.id
|
||||||
== offset.c.original_line_item_id),
|
== offset.c.original_line_item_id),
|
||||||
isouter=True)\
|
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)\
|
.group_by(JournalEntryLineItem.id)\
|
||||||
.having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
|
.having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
|
||||||
|
|
||||||
@ -81,13 +72,12 @@ def get_accounts_with_unapplied(currency: Currency,
|
|||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
|
|
||||||
def get_net_balances(currency: Currency, account: Account,
|
def get_net_balances(currency: Currency, account: Account) \
|
||||||
period: Period | None) -> dict[int, Decimal | None]:
|
-> dict[int, Decimal | None]:
|
||||||
"""Returns the net balances of the unapplied line items of the account.
|
"""Returns the net balances of the unapplied line items of the account.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The account.
|
: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.
|
:return: The net balances of the unapplied line items of the account.
|
||||||
"""
|
"""
|
||||||
offset: sa.Alias = offset_alias()
|
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),
|
(be(offset.c.is_debit == JournalEntryLineItem.is_debit),
|
||||||
offset.c.amount),
|
offset.c.amount),
|
||||||
else_=-offset.c.amount))).label("net_balance")
|
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 \
|
select_net_balances: sa.Select \
|
||||||
= sa.select(JournalEntryLineItem.id, net_balance) \
|
= sa.select(JournalEntryLineItem.id, net_balance) \
|
||||||
.join(JournalEntry).join(Account) \
|
.join(JournalEntry).join(Account) \
|
||||||
.join(offset, be(JournalEntryLineItem.id
|
.join(offset, be(JournalEntryLineItem.id
|
||||||
== offset.c.original_line_item_id),
|
== offset.c.original_line_item_id),
|
||||||
isouter=True) \
|
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) \
|
.group_by(JournalEntryLineItem.id) \
|
||||||
.having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
|
.having(sa.or_(sa.func.count(offset.c.id) == 0, net_balance != 0))
|
||||||
return {x.id: x.net_balance
|
return {x.id: x.net_balance
|
||||||
|
@ -22,37 +22,28 @@ import sqlalchemy as sa
|
|||||||
from accounting import db
|
from accounting import db
|
||||||
from accounting.models import Currency, Account, JournalEntry, \
|
from accounting.models import Currency, Account, JournalEntry, \
|
||||||
JournalEntryLineItem
|
JournalEntryLineItem
|
||||||
from accounting.report.period import Period
|
|
||||||
from accounting.utils.cast import be
|
from accounting.utils.cast import be
|
||||||
|
|
||||||
|
|
||||||
def get_accounts_with_unmatched(currency: Currency,
|
def get_accounts_with_unmatched(currency: Currency) -> list[Account]:
|
||||||
period: Period) -> list[Account]:
|
|
||||||
"""Returns the accounts with unmatched offsets.
|
"""Returns the accounts with unmatched offsets.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
:return: The accounts with unmatched offsets, with the "count" property set
|
:return: The accounts with unmatched offsets, with the "count" property set
|
||||||
to the number of unmatched offsets.
|
to the number of unmatched offsets.
|
||||||
"""
|
"""
|
||||||
count_func: sa.Label \
|
count_func: sa.Label \
|
||||||
= sa.func.count(JournalEntryLineItem.id).label("count")
|
= sa.func.count(JournalEntryLineItem.id).label("count")
|
||||||
conditions: list[sa.BinaryExpression] \
|
select: sa.Select = sa.select(Account.id, count_func)\
|
||||||
= [Account.is_need_offset,
|
.select_from(Account)\
|
||||||
|
.join(JournalEntryLineItem, isouter=True).join(JournalEntry)\
|
||||||
|
.filter(Account.is_need_offset,
|
||||||
be(JournalEntryLineItem.currency_code == currency.code),
|
be(JournalEntryLineItem.currency_code == currency.code),
|
||||||
JournalEntryLineItem.original_line_item_id.is_(None),
|
JournalEntryLineItem.original_line_item_id.is_(None),
|
||||||
sa.or_(sa.and_(Account.base_code.startswith("2"),
|
sa.or_(sa.and_(Account.base_code.startswith("2"),
|
||||||
JournalEntryLineItem.is_debit),
|
JournalEntryLineItem.is_debit),
|
||||||
sa.and_(Account.base_code.startswith("1"),
|
sa.and_(Account.base_code.startswith("1"),
|
||||||
sa.not_(JournalEntryLineItem.is_debit)))]
|
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)\
|
|
||||||
.group_by(Account.id)\
|
.group_by(Account.id)\
|
||||||
.having(count_func > 0)
|
.having(count_func > 0)
|
||||||
counts: dict[int, int] \
|
counts: dict[int, int] \
|
||||||
|
@ -113,39 +113,35 @@ def balance_sheet_url(currency: Currency, period: Period) -> str:
|
|||||||
currency=currency, period=period)
|
currency=currency, period=period)
|
||||||
|
|
||||||
|
|
||||||
def unapplied_url(currency: Currency, account: Account | None,
|
def unapplied_url(currency: Currency, account: Account | None) -> str:
|
||||||
period: Period) -> str:
|
|
||||||
"""Returns the URL of the unapplied original line items.
|
"""Returns the URL of the unapplied original line items.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The account, or None to list the accounts with unapplied
|
:param account: The account, or None to list the accounts with unapplied
|
||||||
original line items.
|
original line items.
|
||||||
:param period: The period.
|
|
||||||
:return: The URL of the unapplied original line items.
|
:return: The URL of the unapplied original line items.
|
||||||
"""
|
"""
|
||||||
if account is None:
|
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-default")
|
||||||
return url_for("accounting-report.unapplied-accounts",
|
return url_for("accounting-report.unapplied-accounts",
|
||||||
currency=currency, period=period)
|
currency=currency)
|
||||||
return url_for("accounting-report.unapplied",
|
return url_for("accounting-report.unapplied",
|
||||||
currency=currency, account=account, period=period)
|
currency=currency, account=account)
|
||||||
|
|
||||||
|
|
||||||
def unmatched_url(currency: Currency, account: Account | None,
|
def unmatched_url(currency: Currency, account: Account | None) -> str:
|
||||||
period: Period) -> str:
|
|
||||||
"""Returns the URL of the unmatched offset line items.
|
"""Returns the URL of the unmatched offset line items.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The account, or None to list the accounts with unmatched
|
:param account: The account, or None to list the accounts with unmatched
|
||||||
offset line items.
|
offset line items.
|
||||||
:param period: The period.
|
|
||||||
:return: The URL of the unmatched offset line items.
|
:return: The URL of the unmatched offset line items.
|
||||||
"""
|
"""
|
||||||
if account is None:
|
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-default")
|
||||||
return url_for("accounting-report.unmatched-accounts",
|
return url_for("accounting-report.unmatched-accounts",
|
||||||
currency=currency, period=period)
|
currency=currency)
|
||||||
return url_for("accounting-report.unmatched",
|
return url_for("accounting-report.unmatched",
|
||||||
currency=currency, account=account, period=period)
|
currency=currency, account=account)
|
||||||
|
@ -293,52 +293,45 @@ def get_default_unapplied_accounts() -> str | Response:
|
|||||||
:return: The accounts with unapplied original line items.
|
:return: The accounts with unapplied original line items.
|
||||||
"""
|
"""
|
||||||
return __get_unapplied_accounts(
|
return __get_unapplied_accounts(
|
||||||
db.session.get(Currency, default_currency_code()), get_period())
|
db.session.get(Currency, default_currency_code()))
|
||||||
|
|
||||||
|
|
||||||
@bp.get("unapplied/<currency:currency>/<period:period>",
|
@bp.get("unapplied/<currency:currency>", endpoint="unapplied-accounts")
|
||||||
endpoint="unapplied-accounts")
|
|
||||||
@has_permission(can_view)
|
@has_permission(can_view)
|
||||||
def get_unapplied_accounts(currency: Currency,
|
def get_unapplied_accounts(currency: Currency) -> str | Response:
|
||||||
period: Period) -> str | Response:
|
|
||||||
"""Returns the accounts with unapplied original line items.
|
"""Returns the accounts with unapplied original line items.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
:return: The accounts with unapplied original line items.
|
:return: The accounts with unapplied original line items.
|
||||||
"""
|
"""
|
||||||
return __get_unapplied_accounts(currency, period)
|
return __get_unapplied_accounts(currency)
|
||||||
|
|
||||||
|
|
||||||
def __get_unapplied_accounts(currency: Currency,
|
def __get_unapplied_accounts(currency: Currency) -> str | Response:
|
||||||
period: Period) -> str | Response:
|
|
||||||
"""Returns the accounts with unapplied original line items.
|
"""Returns the accounts with unapplied original line items.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
:return: The accounts with unapplied original line items.
|
:return: The accounts with unapplied original line items.
|
||||||
"""
|
"""
|
||||||
report: AccountsWithUnappliedOriginalLineItems \
|
report: AccountsWithUnappliedOriginalLineItems \
|
||||||
= AccountsWithUnappliedOriginalLineItems(currency, period)
|
= AccountsWithUnappliedOriginalLineItems(currency)
|
||||||
if "as" in request.args and request.args["as"] == "csv":
|
if "as" in request.args and request.args["as"] == "csv":
|
||||||
return report.csv()
|
return report.csv()
|
||||||
return report.html()
|
return report.html()
|
||||||
|
|
||||||
|
|
||||||
@bp.get("unapplied/<currency:currency>/<needOffsetAccount:account>/"
|
@bp.get("unapplied/<currency:currency>/<needOffsetAccount:account>",
|
||||||
"<period:period>", endpoint="unapplied")
|
endpoint="unapplied")
|
||||||
@has_permission(can_view)
|
@has_permission(can_view)
|
||||||
def get_unapplied(currency: Currency, account: Account,
|
def get_unapplied(currency: Currency, account: Account) -> str | Response:
|
||||||
period: Period) -> str | Response:
|
|
||||||
"""Returns the unapplied original line items.
|
"""Returns the unapplied original line items.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The Account.
|
:param account: The Account.
|
||||||
:param period: The period.
|
|
||||||
:return: The unapplied original line items in the period.
|
:return: The unapplied original line items in the period.
|
||||||
"""
|
"""
|
||||||
report: UnappliedOriginalLineItems \
|
report: UnappliedOriginalLineItems \
|
||||||
= UnappliedOriginalLineItems(currency, account, period)
|
= UnappliedOriginalLineItems(currency, account)
|
||||||
if "as" in request.args and request.args["as"] == "csv":
|
if "as" in request.args and request.args["as"] == "csv":
|
||||||
return report.csv()
|
return report.csv()
|
||||||
return report.html()
|
return report.html()
|
||||||
@ -352,51 +345,44 @@ def get_default_unmatched_accounts() -> str | Response:
|
|||||||
:return: The accounts with unmatched offsets.
|
:return: The accounts with unmatched offsets.
|
||||||
"""
|
"""
|
||||||
return __get_unmatched_accounts(
|
return __get_unmatched_accounts(
|
||||||
db.session.get(Currency, default_currency_code()), get_period())
|
db.session.get(Currency, default_currency_code()))
|
||||||
|
|
||||||
|
|
||||||
@bp.get("unmatched/<currency:currency>/<period:period>",
|
@bp.get("unmatched/<currency:currency>", endpoint="unmatched-accounts")
|
||||||
endpoint="unmatched-accounts")
|
|
||||||
@has_permission(can_edit)
|
@has_permission(can_edit)
|
||||||
def get_unmatched_accounts(currency: Currency,
|
def get_unmatched_accounts(currency: Currency) -> str | Response:
|
||||||
period: Period) -> str | Response:
|
|
||||||
"""Returns the accounts with unmatched offsets.
|
"""Returns the accounts with unmatched offsets.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
:return: The accounts with unmatched offsets.
|
:return: The accounts with unmatched offsets.
|
||||||
"""
|
"""
|
||||||
return __get_unmatched_accounts(currency, period)
|
return __get_unmatched_accounts(currency)
|
||||||
|
|
||||||
|
|
||||||
def __get_unmatched_accounts(currency: Currency,
|
def __get_unmatched_accounts(currency: Currency) -> str | Response:
|
||||||
period: Period) -> str | Response:
|
|
||||||
"""Returns the accounts with unmatched offsets.
|
"""Returns the accounts with unmatched offsets.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param period: The period.
|
|
||||||
:return: The accounts with unmatched offsets.
|
:return: The accounts with unmatched offsets.
|
||||||
"""
|
"""
|
||||||
report: AccountsWithUnmatchedOffsets \
|
report: AccountsWithUnmatchedOffsets \
|
||||||
= AccountsWithUnmatchedOffsets(currency, period)
|
= AccountsWithUnmatchedOffsets(currency)
|
||||||
if "as" in request.args and request.args["as"] == "csv":
|
if "as" in request.args and request.args["as"] == "csv":
|
||||||
return report.csv()
|
return report.csv()
|
||||||
return report.html()
|
return report.html()
|
||||||
|
|
||||||
|
|
||||||
@bp.get("unmatched/<currency:currency>/<needOffsetAccount:account>/"
|
@bp.get("unmatched/<currency:currency>/<needOffsetAccount:account>",
|
||||||
"<period:period>", endpoint="unmatched")
|
endpoint="unmatched")
|
||||||
@has_permission(can_edit)
|
@has_permission(can_edit)
|
||||||
def get_unmatched(currency: Currency, account: Account,
|
def get_unmatched(currency: Currency, account: Account) -> str | Response:
|
||||||
period: Period) -> str | Response:
|
|
||||||
"""Returns the unmatched offsets.
|
"""Returns the unmatched offsets.
|
||||||
|
|
||||||
:param currency: The currency.
|
:param currency: The currency.
|
||||||
:param account: The Account.
|
:param account: The Account.
|
||||||
:param period: The period.
|
|
||||||
:return: The unmatched offsets in the period.
|
:return: The unmatched offsets in the period.
|
||||||
"""
|
"""
|
||||||
report: UnmatchedOffsets = UnmatchedOffsets(currency, account, period)
|
report: UnmatchedOffsets = UnmatchedOffsets(currency, account)
|
||||||
if "as" in request.args and request.args["as"] == "csv":
|
if "as" in request.args and request.args["as"] == "csv":
|
||||||
return report.csv()
|
return report.csv()
|
||||||
return report.html()
|
return report.html()
|
||||||
@ -410,17 +396,17 @@ def match_offsets(currency: Currency, account: Account) -> redirect:
|
|||||||
|
|
||||||
:return: Redirection to the view of the unmatched offsets.
|
:return: Redirection to the view of the unmatched offsets.
|
||||||
"""
|
"""
|
||||||
matcher: OffsetMatcher = OffsetMatcher(currency, account, None)
|
matcher: OffsetMatcher = OffsetMatcher(currency, account)
|
||||||
if len(matcher.matched_pairs) == 0:
|
if len(matcher.matched_pairs) == 0:
|
||||||
flash(s(lazy_gettext("No more offset to match automatically.")),
|
flash(s(lazy_gettext("No more offset to match automatically.")),
|
||||||
"success")
|
"success")
|
||||||
return redirect(or_next(
|
return redirect(or_next(
|
||||||
unmatched_url(currency, account, get_period())))
|
unmatched_url(currency, account)))
|
||||||
matcher.match()
|
matcher.match()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(s(lazy_gettext("Matched %(matches)s offsets.",
|
flash(s(lazy_gettext("Matched %(matches)s offsets.",
|
||||||
matches=len(matcher.matched_pairs))), "success")
|
matches=len(matcher.matched_pairs))), "success")
|
||||||
return redirect(or_next(unmatched_url(currency, account, get_period())))
|
return redirect(or_next(unmatched_url(currency, account)))
|
||||||
|
|
||||||
|
|
||||||
@bp.get("search", endpoint="search")
|
@bp.get("search", endpoint="search")
|
||||||
|
@ -26,22 +26,19 @@ First written: 2023/4/8
|
|||||||
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}{% block title %}{% if report.currency.code == accounting_default_currency_code() %}{{ A_("Accounts with Unapplied Items %(period)s", period=report.period.desc|title) }}{% else %}{{ A_("Accounts with Unapplied Items in %(currency)s %(period)s", currency=report.currency.name|title, period=report.period.desc|title) }}{% endif %}{% endblock %}{% endblock %}
|
{% block header %}{% block title %}{% if report.currency.code == accounting_default_currency_code() %}{{ A_("Accounts with Unapplied Items") }}{% else %}{{ A_("Accounts with Unapplied Items in %(currency)s", currency=report.currency.name|title) }}{% endif %}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="mb-3 accounting-toolbar">
|
<div class="mb-3 accounting-toolbar">
|
||||||
{% with use_currency_chooser = true,
|
{% with use_currency_chooser = true,
|
||||||
use_account_chooser = true,
|
use_account_chooser = true %}
|
||||||
use_period_chooser = true %}
|
|
||||||
{% include "accounting/report/include/toolbar-buttons.html" %}
|
{% include "accounting/report/include/toolbar-buttons.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include "accounting/report/include/add-journal-entry-material-fab.html" %}
|
{% include "accounting/report/include/add-journal-entry-material-fab.html" %}
|
||||||
|
|
||||||
{% include "accounting/report/include/period-chooser.html" %}
|
|
||||||
|
|
||||||
{% include "accounting/report/include/search-modal.html" %}
|
{% include "accounting/report/include/search-modal.html" %}
|
||||||
|
|
||||||
{% if report.has_data %}
|
{% if report.has_data %}
|
||||||
@ -49,9 +46,9 @@ First written: 2023/4/8
|
|||||||
<div class="d-none d-sm-flex justify-content-center mb-3">
|
<div class="d-none d-sm-flex justify-content-center mb-3">
|
||||||
<h2 class="text-center">
|
<h2 class="text-center">
|
||||||
{% if report.currency.code == accounting_default_currency_code() %}
|
{% if report.currency.code == accounting_default_currency_code() %}
|
||||||
{{ A_("Accounts with Unapplied Items %(period)s", period=report.period.desc|title) }}
|
{{ A_("Accounts with Unapplied Items") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ A_("Accounts with Unapplied Items in %(currency)s %(period)s", currency=report.currency.name|title, period=report.period.desc|title) }}
|
{{ A_("Accounts with Unapplied Items in %(currency)s", currency=report.currency.name|title) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,22 +26,19 @@ First written: 2023/4/7
|
|||||||
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}{% block title %}{% if report.currency.code == accounting_default_currency_code() %}{{ A_("Unapplied Items of %(account)s %(period)s", account=report.account.title|title, period=report.period.desc|title) }}{% else %}{{ A_("Unapplied Items of %(account)s in %(currency)s %(period)s", currency=report.currency.name|title, account=report.account.title|title, period=report.period.desc|title) }}{% endif %}{% endblock %}{% endblock %}
|
{% block header %}{% block title %}{% if report.currency.code == accounting_default_currency_code() %}{{ A_("Unapplied Items of %(account)s", account=report.account.title|title) }}{% else %}{{ A_("Unapplied Items of %(account)s in %(currency)s", currency=report.currency.name|title, account=report.account.title|title) }}{% endif %}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="mb-3 accounting-toolbar">
|
<div class="mb-3 accounting-toolbar">
|
||||||
{% with use_currency_chooser = true,
|
{% with use_currency_chooser = true,
|
||||||
use_account_chooser = true,
|
use_account_chooser = true %}
|
||||||
use_period_chooser = true %}
|
|
||||||
{% include "accounting/report/include/toolbar-buttons.html" %}
|
{% include "accounting/report/include/toolbar-buttons.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include "accounting/report/include/add-journal-entry-material-fab.html" %}
|
{% include "accounting/report/include/add-journal-entry-material-fab.html" %}
|
||||||
|
|
||||||
{% include "accounting/report/include/period-chooser.html" %}
|
|
||||||
|
|
||||||
{% include "accounting/report/include/search-modal.html" %}
|
{% include "accounting/report/include/search-modal.html" %}
|
||||||
|
|
||||||
{% if report.has_data %}
|
{% if report.has_data %}
|
||||||
|
@ -26,22 +26,19 @@ First written: 2023/4/17
|
|||||||
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}{% block title %}{% if report.currency.code == accounting_default_currency_code() %}{{ A_("Accounts with Unmatched Offsets %(period)s", period=report.period.desc|title) }}{% else %}{{ A_("Accounts with Unmatched Offsets in %(currency)s %(period)s", currency=report.currency.name|title, period=report.period.desc|title) }}{% endif %}{% endblock %}{% endblock %}
|
{% block header %}{% block title %}{% if report.currency.code == accounting_default_currency_code() %}{{ A_("Accounts with Unmatched Offsets") }}{% else %}{{ A_("Accounts with Unmatched Offsets in %(currency)s", currency=report.currency.name|title) }}{% endif %}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="mb-3 accounting-toolbar">
|
<div class="mb-3 accounting-toolbar">
|
||||||
{% with use_currency_chooser = true,
|
{% with use_currency_chooser = true,
|
||||||
use_account_chooser = true,
|
use_account_chooser = true %}
|
||||||
use_period_chooser = true %}
|
|
||||||
{% include "accounting/report/include/toolbar-buttons.html" %}
|
{% include "accounting/report/include/toolbar-buttons.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include "accounting/report/include/add-journal-entry-material-fab.html" %}
|
{% include "accounting/report/include/add-journal-entry-material-fab.html" %}
|
||||||
|
|
||||||
{% include "accounting/report/include/period-chooser.html" %}
|
|
||||||
|
|
||||||
{% include "accounting/report/include/search-modal.html" %}
|
{% include "accounting/report/include/search-modal.html" %}
|
||||||
|
|
||||||
{% if report.has_data %}
|
{% if report.has_data %}
|
||||||
@ -49,9 +46,9 @@ First written: 2023/4/17
|
|||||||
<div class="d-none d-sm-flex justify-content-center mb-3">
|
<div class="d-none d-sm-flex justify-content-center mb-3">
|
||||||
<h2 class="text-center">
|
<h2 class="text-center">
|
||||||
{% if report.currency.code == accounting_default_currency_code() %}
|
{% if report.currency.code == accounting_default_currency_code() %}
|
||||||
{{ A_("Accounts with Unmatched Offsets %(period)s", period=report.period.desc|title) }}
|
{{ A_("Accounts with Unmatched Offsets") }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ A_("Accounts with Unmatched Offsets in %(currency)s %(period)s", currency=report.currency.name|title, period=report.period.desc|title) }}
|
{{ A_("Accounts with Unmatched Offsets in %(currency)s", currency=report.currency.name|title) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,22 +26,19 @@ First written: 2023/4/17
|
|||||||
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}{% block title %}{% if report.currency.code == accounting_default_currency_code() %}{{ A_("Unmatched Offsets of %(account)s %(period)s", account=report.account.title|title, period=report.period.desc|title) }}{% else %}{{ A_("Unmatched Offsets of %(account)s in %(currency)s %(period)s", currency=report.currency.name|title, account=report.account.title|title, period=report.period.desc|title) }}{% endif %}{% endblock %}{% endblock %}
|
{% block header %}{% block title %}{% if report.currency.code == accounting_default_currency_code() %}{{ A_("Unmatched Offsets of %(account)s", account=report.account.title|title) }}{% else %}{{ A_("Unmatched Offsets of %(account)s in %(currency)s", currency=report.currency.name|title, account=report.account.title|title) }}{% endif %}{% endblock %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="mb-3 accounting-toolbar">
|
<div class="mb-3 accounting-toolbar">
|
||||||
{% with use_currency_chooser = true,
|
{% with use_currency_chooser = true,
|
||||||
use_account_chooser = true,
|
use_account_chooser = true %}
|
||||||
use_period_chooser = true %}
|
|
||||||
{% include "accounting/report/include/toolbar-buttons.html" %}
|
{% include "accounting/report/include/toolbar-buttons.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include "accounting/report/include/add-journal-entry-material-fab.html" %}
|
{% include "accounting/report/include/add-journal-entry-material-fab.html" %}
|
||||||
|
|
||||||
{% include "accounting/report/include/period-chooser.html" %}
|
|
||||||
|
|
||||||
{% include "accounting/report/include/search-modal.html" %}
|
{% include "accounting/report/include/search-modal.html" %}
|
||||||
|
|
||||||
{% if report.matched_pairs %}
|
{% if report.matched_pairs %}
|
||||||
|
@ -108,11 +108,11 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}/all-time")
|
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}")
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}/all-time?as=csv")
|
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}?as=csv")
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(f"{PREFIX}/unmatched")
|
response = client.get(f"{PREFIX}/unmatched")
|
||||||
@ -122,11 +122,11 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}/all-time")
|
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}")
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}/all-time?as=csv")
|
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}?as=csv")
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(f"{PREFIX}/search?q=Salary")
|
response = client.get(f"{PREFIX}/search?q=Salary")
|
||||||
@ -207,11 +207,11 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}/all-time")
|
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}/all-time?as=csv")
|
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}?as=csv")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
@ -222,11 +222,11 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}/all-time")
|
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}")
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(
|
response = client.get(
|
||||||
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}/all-time?as=csv")
|
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}?as=csv")
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
response = client.get(f"{PREFIX}/search?q=Salary")
|
response = client.get(f"{PREFIX}/search?q=Salary")
|
||||||
@ -308,11 +308,11 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}/all-time")
|
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}/all-time?as=csv")
|
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}?as=csv")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
@ -324,11 +324,11 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}/all-time")
|
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}/all-time?as=csv")
|
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}?as=csv")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
@ -410,11 +410,11 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}/all-time")
|
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}/all-time?as=csv")
|
f"{PREFIX}/unapplied/USD/{Accounts.PAYABLE}?as=csv")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
@ -426,11 +426,11 @@ class ReportTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}/all-time")
|
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}/all-time?as=csv")
|
f"{PREFIX}/unmatched/USD/{Accounts.PAYABLE}?as=csv")
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
self.assertEqual(response.headers["Content-Type"], CSV_MIME)
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
account = Account.find_by_code(Accounts.RECEIVABLE)
|
account = Account.find_by_code(Accounts.RECEIVABLE)
|
||||||
assert account is not None
|
assert account is not None
|
||||||
matcher = OffsetMatcher(currency, account, None)
|
matcher = OffsetMatcher(currency, account)
|
||||||
self.assertEqual({x.id for x in matcher.unapplied},
|
self.assertEqual({x.id for x in matcher.unapplied},
|
||||||
{data.l_r_or1d.id, data.l_r_or2d.id,
|
{data.l_r_or1d.id, data.l_r_or2d.id,
|
||||||
data.l_r_or3d.id, data.l_r_or4d.id})
|
data.l_r_or3d.id, data.l_r_or4d.id})
|
||||||
@ -157,7 +157,7 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
account = Account.find_by_code(Accounts.RECEIVABLE)
|
account = Account.find_by_code(Accounts.RECEIVABLE)
|
||||||
assert account is not None
|
assert account is not None
|
||||||
matcher = OffsetMatcher(currency, account, None)
|
matcher = OffsetMatcher(currency, account)
|
||||||
self.assertEqual({x.id for x in matcher.unapplied},
|
self.assertEqual({x.id for x in matcher.unapplied},
|
||||||
{data.l_r_or1d.id, data.l_r_or2d.id,
|
{data.l_r_or1d.id, data.l_r_or2d.id,
|
||||||
data.l_r_or3d.id})
|
data.l_r_or3d.id})
|
||||||
@ -179,7 +179,7 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
account = Account.find_by_code(Accounts.PAYABLE)
|
account = Account.find_by_code(Accounts.PAYABLE)
|
||||||
assert account is not None
|
assert account is not None
|
||||||
matcher = OffsetMatcher(currency, account, None)
|
matcher = OffsetMatcher(currency, account)
|
||||||
self.assertEqual({x.id for x in matcher.unapplied},
|
self.assertEqual({x.id for x in matcher.unapplied},
|
||||||
{data.l_p_or1c.id, data.l_p_or2c.id,
|
{data.l_p_or1c.id, data.l_p_or2c.id,
|
||||||
data.l_p_or3c.id, data.l_p_or4c.id})
|
data.l_p_or3c.id, data.l_p_or4c.id})
|
||||||
@ -207,7 +207,7 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
account = Account.find_by_code(Accounts.PAYABLE)
|
account = Account.find_by_code(Accounts.PAYABLE)
|
||||||
assert account is not None
|
assert account is not None
|
||||||
matcher = OffsetMatcher(currency, account, None)
|
matcher = OffsetMatcher(currency, account)
|
||||||
self.assertEqual({x.id for x in matcher.unapplied},
|
self.assertEqual({x.id for x in matcher.unapplied},
|
||||||
{data.l_p_or1c.id, data.l_p_or2c.id,
|
{data.l_p_or1c.id, data.l_p_or2c.id,
|
||||||
data.l_p_or3c.id})
|
data.l_p_or3c.id})
|
||||||
@ -250,7 +250,7 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
account = Account.find_by_code(Accounts.RECEIVABLE)
|
account = Account.find_by_code(Accounts.RECEIVABLE)
|
||||||
assert account is not None
|
assert account is not None
|
||||||
matcher = OffsetMatcher(currency, account, None)
|
matcher = OffsetMatcher(currency, account)
|
||||||
self.assertEqual({x.id for x in matcher.unapplied},
|
self.assertEqual({x.id for x in matcher.unapplied},
|
||||||
{data.l_r_or1d.id, data.l_r_or3d.id,
|
{data.l_r_or1d.id, data.l_r_or3d.id,
|
||||||
data.l_r_or4d.id, data.l_r_or5d.id,
|
data.l_r_or4d.id, data.l_r_or5d.id,
|
||||||
@ -285,7 +285,7 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
account = Account.find_by_code(Accounts.RECEIVABLE)
|
account = Account.find_by_code(Accounts.RECEIVABLE)
|
||||||
assert account is not None
|
assert account is not None
|
||||||
matcher = OffsetMatcher(currency, account, None)
|
matcher = OffsetMatcher(currency, account)
|
||||||
self.assertEqual({x.id for x in matcher.unapplied},
|
self.assertEqual({x.id for x in matcher.unapplied},
|
||||||
{data.l_r_or5d.id, data.l_r_or6d.id})
|
{data.l_r_or5d.id, data.l_r_or6d.id})
|
||||||
self.assertEqual({x.id for x in matcher.unmatched},
|
self.assertEqual({x.id for x in matcher.unmatched},
|
||||||
@ -316,7 +316,7 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
account = Account.find_by_code(Accounts.PAYABLE)
|
account = Account.find_by_code(Accounts.PAYABLE)
|
||||||
assert account is not None
|
assert account is not None
|
||||||
matcher = OffsetMatcher(currency, account, None)
|
matcher = OffsetMatcher(currency, account)
|
||||||
self.assertEqual({x.id for x in matcher.unapplied},
|
self.assertEqual({x.id for x in matcher.unapplied},
|
||||||
{data.l_p_or1c.id, data.l_p_or3c.id,
|
{data.l_p_or1c.id, data.l_p_or3c.id,
|
||||||
data.l_p_or4c.id, data.l_p_or5c.id,
|
data.l_p_or4c.id, data.l_p_or5c.id,
|
||||||
@ -351,7 +351,7 @@ class UnmatchedOffsetTestCase(unittest.TestCase):
|
|||||||
with self.app.app_context():
|
with self.app.app_context():
|
||||||
account = Account.find_by_code(Accounts.PAYABLE)
|
account = Account.find_by_code(Accounts.PAYABLE)
|
||||||
assert account is not None
|
assert account is not None
|
||||||
matcher = OffsetMatcher(currency, account, None)
|
matcher = OffsetMatcher(currency, account)
|
||||||
self.assertEqual({x.id for x in matcher.unapplied},
|
self.assertEqual({x.id for x in matcher.unapplied},
|
||||||
{data.l_p_or5c.id, data.l_p_or6c.id})
|
{data.l_p_or5c.id, data.l_p_or6c.id})
|
||||||
self.assertEqual({x.id for x in matcher.unmatched},
|
self.assertEqual({x.id for x in matcher.unmatched},
|
||||||
|
Loading…
Reference in New Issue
Block a user