Moved the utilities that are only for the report generators from the "accounting.report" module to the "accounting.report.reports.utils" module.

This commit is contained in:
2023-03-07 22:33:16 +08:00
parent edb893ecd3
commit f838e7f893
12 changed files with 54 additions and 34 deletions

View File

@ -28,12 +28,12 @@ from accounting import db
from accounting.locale import gettext
from accounting.models import Currency, BaseAccount, Account, Transaction, \
JournalEntry
from accounting.report.option_link import OptionLink
from accounting.report.page_params import PageParams
from accounting.report.period import Period
from accounting.report.period_choosers import BalanceSheetPeriodChooser
from accounting.report.report_chooser import ReportChooser
from accounting.report.report_type import ReportType
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import BalanceSheetPeriodChooser
from .utils.report_chooser import ReportChooser
from .utils.report_type import ReportType
class BalanceSheetAccount:

View File

@ -28,13 +28,13 @@ from flask import url_for, render_template, Response
from accounting import db
from accounting.locale import gettext
from accounting.models import Currency, Account, Transaction, JournalEntry
from accounting.report.option_link import OptionLink
from accounting.report.page_params import PageParams
from accounting.report.period import Period
from accounting.report.period_choosers import IncomeExpensesPeriodChooser
from accounting.report.report_chooser import ReportChooser
from accounting.report.report_type import ReportType
from accounting.utils.pagination import Pagination
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import IncomeExpensesPeriodChooser
from .utils.report_chooser import ReportChooser
from .utils.report_type import ReportType
class Entry:

View File

@ -28,12 +28,12 @@ from accounting import db
from accounting.locale import gettext
from accounting.models import Currency, BaseAccount, Account, Transaction, \
JournalEntry
from accounting.report.option_link import OptionLink
from accounting.report.page_params import PageParams
from accounting.report.period import Period
from accounting.report.period_choosers import IncomeStatementPeriodChooser
from accounting.report.report_chooser import ReportChooser
from accounting.report.report_type import ReportType
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import IncomeStatementPeriodChooser
from .utils.report_chooser import ReportChooser
from .utils.report_type import ReportType
class IncomeStatementAccount:

View File

@ -28,12 +28,12 @@ from flask import render_template, Response
from accounting import db
from accounting.locale import gettext
from accounting.models import Currency, Account, Transaction, JournalEntry
from accounting.report.page_params import PageParams
from accounting.report.period import Period
from accounting.report.period_choosers import JournalPeriodChooser
from accounting.report.report_chooser import ReportChooser
from accounting.report.report_type import ReportType
from accounting.utils.pagination import Pagination
from .utils.page_params import PageParams
from .utils.period_choosers import JournalPeriodChooser
from .utils.report_chooser import ReportChooser
from .utils.report_type import ReportType
class Entry:

View File

@ -28,13 +28,13 @@ from flask import url_for, render_template, Response
from accounting import db
from accounting.locale import gettext
from accounting.models import Currency, Account, Transaction, JournalEntry
from accounting.report.option_link import OptionLink
from accounting.report.page_params import PageParams
from accounting.report.period import Period
from accounting.report.period_choosers import LedgerPeriodChooser
from accounting.report.report_chooser import ReportChooser
from accounting.report.report_type import ReportType
from accounting.utils.pagination import Pagination
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import LedgerPeriodChooser
from .utils.report_chooser import ReportChooser
from .utils.report_type import ReportType
class Entry:

View File

@ -27,12 +27,12 @@ from flask import url_for, Response, render_template
from accounting import db
from accounting.locale import gettext
from accounting.models import Currency, Account, Transaction, JournalEntry
from accounting.report.option_link import OptionLink
from accounting.report.page_params import PageParams
from accounting.report.period import Period
from accounting.report.period_choosers import TrialBalancePeriodChooser
from accounting.report.report_chooser import ReportChooser
from accounting.report.report_type import ReportType
from .utils.option_link import OptionLink
from .utils.page_params import PageParams
from .utils.period_choosers import TrialBalancePeriodChooser
from .utils.report_chooser import ReportChooser
from .utils.report_type import ReportType
class TrialBalanceAccount:

View File

@ -0,0 +1,19 @@
# The Mia! Accounting Flask Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/7
# Copyright (c) 2023 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The utilities to generate reports.
"""

View File

@ -0,0 +1,34 @@
# The Mia! Accounting Flask Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/5
# Copyright (c) 2023 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The option link.
"""
class OptionLink:
"""An option link."""
def __init__(self, title: str, url: str, is_active: bool):
"""Constructs an option link.
:param title: The title.
:param url: The URI.
:param is_active: True if active, or False otherwise
"""
self.title: str = title
self.url: str = url
self.is_active: bool = is_active

View File

@ -0,0 +1,68 @@
# The Mia! Accounting Flask Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/6
# Copyright (c) 2023 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The page parameters of a report.
"""
import typing as t
from abc import ABC, abstractmethod
from urllib.parse import urlparse, ParseResult, parse_qsl, urlencode, \
urlunparse
from flask import request
from accounting.utils.txn_types import TransactionType
from .report_chooser import ReportChooser
class PageParams(ABC):
"""The page parameters of a report."""
@property
@abstractmethod
def has_data(self) -> bool:
"""Returns whether there is any data on the page.
:return: True if there is any data, or False otherwise.
"""
@property
@abstractmethod
def report_chooser(self) -> ReportChooser:
"""Returns the report chooser.
:return: The report chooser.
"""
@property
def txn_types(self) -> t.Type[TransactionType]:
"""Returns the transaction types.
:return: The transaction types.
"""
return TransactionType
@property
def csv_uri(self) -> str:
uri: str = request.full_path if request.query_string \
else request.path
uri_p: ParseResult = urlparse(uri)
params: list[tuple[str, str]] = parse_qsl(uri_p.query)
params = [x for x in params if x[0] != "as"]
params.append(("as", "csv"))
parts: list[str] = list(uri_p)
parts[4] = urlencode(params)
return urlunparse(parts)

View File

@ -0,0 +1,219 @@
# The Mia! Accounting Flask Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
# Copyright (c) 2023 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The period choosers.
This file is largely taken from the NanoParma ERP project, first written in
2021/9/16 by imacat (imacat@nanoparma.com).
"""
import typing as t
from abc import ABC, abstractmethod
from datetime import date
from flask import url_for
from accounting.models import Currency, Account, Transaction
from accounting.report.period import YearPeriod, Period, ThisMonth, \
LastMonth, SinceLastMonth, ThisYear, LastYear, Today, Yesterday, \
TemplatePeriod
class PeriodChooser(ABC):
"""The period chooser."""
def __init__(self, start: date | None):
"""Constructs a period chooser.
:param start: The start of the period.
"""
# Shortcut periods
self.this_month_url: str = self._url_for(ThisMonth())
"""The URL for this month."""
self.last_month_url: str = self._url_for(LastMonth())
"""The URL for last month."""
self.since_last_month_url: str = self._url_for(SinceLastMonth())
"""The URL since last mint."""
self.this_year_url: str = self._url_for(ThisYear())
"""The URL for this year."""
self.last_year_url: str = self._url_for(LastYear())
"""The URL for last year."""
self.today_url: str = self._url_for(Today())
"""The URL for today."""
self.yesterday_url: str = self._url_for(Yesterday())
"""The URL for yesterday."""
self.all_url: str = self._url_for(Period(None, None))
"""The URL for all period."""
self.url_template: str = self._url_for(TemplatePeriod())
"""The URL template."""
# Attributes
self.data_start: date | None = start
"""The start of the data."""
self.has_data: bool = start is not None
"""Whether there is any data."""
self.has_last_month: bool = False
"""Where there is data in last month."""
self.has_last_year: bool = False
"""Whether there is data in last year."""
self.has_yesterday: bool = False
"""Whether there is data in yesterday."""
self.available_years: t.Iterator[int] = []
"""The available years."""
if self.has_data is not None:
today: date = date.today()
self.has_last_month = start < date(today.year, today.month, 1)
self.has_last_year = start.year < today.year
self.has_yesterday = start < today
self.available_years: t.Iterator[int] = []
if start.year < today.year - 1:
self.available_years \
= reversed(range(start.year, today.year - 1))
@abstractmethod
def _url_for(self, period: Period) -> str:
"""Returns the URL for a period.
:param period: The period.
:return: The URL for the period.
"""
pass
def year_url(self, year: int) -> str:
"""Returns the period URL of a year.
:param year: The year
:return: The period URL of the year.
"""
return self._url_for(YearPeriod(year))
class JournalPeriodChooser(PeriodChooser):
"""The journal period chooser."""
def __init__(self):
"""Constructs the journal period chooser."""
first: Transaction | None \
= Transaction.query.order_by(Transaction.date).first()
super().__init__(None if first is None else first.date)
def _url_for(self, period: Period) -> str:
if period.is_default:
return url_for("accounting.report.journal-default")
return url_for("accounting.report.journal", period=period)
class LedgerPeriodChooser(PeriodChooser):
"""The ledger period chooser."""
def __init__(self, currency: Currency, account: Account):
"""Constructs the ledger period chooser."""
self.currency: Currency = currency
"""The currency."""
self.account: Account = account
"""The account."""
first: Transaction | None \
= Transaction.query.order_by(Transaction.date).first()
super().__init__(None if first is None else first.date)
def _url_for(self, period: Period) -> str:
if period.is_default:
return url_for("accounting.report.ledger-default",
currency=self.currency, account=self.account)
return url_for("accounting.report.ledger",
currency=self.currency, account=self.account,
period=period)
class IncomeExpensesPeriodChooser(PeriodChooser):
"""The income and expenses period chooser."""
def __init__(self, currency: Currency, account: Account):
"""Constructs the income and expenses period chooser."""
self.currency: Currency = currency
"""The currency."""
self.account: Account = account
"""The account."""
first: Transaction | None \
= Transaction.query.order_by(Transaction.date).first()
super().__init__(None if first is None else first.date)
def _url_for(self, period: Period) -> str:
if period.is_default:
return url_for("accounting.report.income-expenses-default",
currency=self.currency, account=self.account)
return url_for("accounting.report.income-expenses",
currency=self.currency, account=self.account,
period=period)
class TrialBalancePeriodChooser(PeriodChooser):
"""The trial balance period chooser."""
def __init__(self, currency: Currency):
"""Constructs the trial balance period chooser."""
self.currency: Currency = currency
"""The currency."""
first: Transaction | None \
= Transaction.query.order_by(Transaction.date).first()
super().__init__(None if first is None else first.date)
def _url_for(self, period: Period) -> str:
if period.is_default:
return url_for("accounting.report.trial-balance-default",
currency=self.currency)
return url_for("accounting.report.trial-balance",
currency=self.currency, period=period)
class IncomeStatementPeriodChooser(PeriodChooser):
"""The income statement period chooser."""
def __init__(self, currency: Currency):
"""Constructs the income statement period chooser."""
self.currency: Currency = currency
"""The currency."""
first: Transaction | None \
= Transaction.query.order_by(Transaction.date).first()
super().__init__(None if first is None else first.date)
def _url_for(self, period: Period) -> str:
if period.is_default:
return url_for("accounting.report.income-statement-default",
currency=self.currency)
return url_for("accounting.report.income-statement",
currency=self.currency, period=period)
class BalanceSheetPeriodChooser(PeriodChooser):
"""The balance sheet period chooser."""
def __init__(self, currency: Currency):
"""Constructs the balance sheet period chooser."""
self.currency: Currency = currency
"""The currency."""
first: Transaction | None \
= Transaction.query.order_by(Transaction.date).first()
super().__init__(None if first is None else first.date)
def _url_for(self, period: Period) -> str:
if period.is_default:
return url_for("accounting.report.balance-sheet-default",
currency=self.currency)
return url_for("accounting.report.balance-sheet",
currency=self.currency, period=period)

View File

@ -0,0 +1,170 @@
# The Mia! Accounting Flask Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
# Copyright (c) 2023 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The report chooser.
This file is largely taken from the NanoParma ERP project, first written in
2021/9/16 by imacat (imacat@nanoparma.com).
"""
import re
import typing as t
from flask import url_for
from flask_babel import LazyString
from accounting import db
from accounting.locale import gettext
from accounting.models import Currency, Account
from accounting.report.period import Period
from accounting.template_globals import default_currency_code
from .option_link import OptionLink
from .report_type import ReportType
class ReportChooser:
"""The report chooser."""
def __init__(self, active_report: ReportType,
period: Period | None = None,
currency: Currency | None = None,
account: Account | None = None):
"""Constructs the report chooser.
:param active_report: The active report.
:param period: The period.
:param currency: The currency.
:param account: The account.
"""
self.__active_report: ReportType = active_report
"""The currently active report."""
self.__period: Period = Period.get_instance() if period is None \
else period
"""The period."""
self.__currency: Currency = db.session.get(
Currency, default_currency_code()) \
if currency is None else currency
"""The currency."""
self.__account: Account = Account.find_by_code("1111-001") \
if account is None else account
"""The currency."""
self.__reports: list[OptionLink] = []
"""The links to the reports."""
self.current_report: str | LazyString = ""
"""The title of the current report."""
self.__reports.append(self.__journal)
self.__reports.append(self.__ledger)
self.__reports.append(self.__income_expenses)
self.__reports.append(self.__trial_balance)
self.__reports.append(self.__income_statement)
self.__reports.append(self.__balance_sheet)
for report in self.__reports:
if report.is_active:
self.current_report = report.title
@property
def __journal(self) -> OptionLink:
"""Returns the journal.
:return: The journal.
"""
url: str = url_for("accounting.report.journal-default") \
if self.__period.is_default \
else url_for("accounting.report.journal", period=self.__period)
return OptionLink(gettext("Journal"), url,
self.__active_report == ReportType.JOURNAL)
@property
def __ledger(self) -> OptionLink:
"""Returns the ledger.
:return: The ledger.
"""
url: str = url_for("accounting.report.ledger-default",
currency=self.__currency, account=self.__account) \
if self.__period.is_default \
else url_for("accounting.report.ledger",
currency=self.__currency, account=self.__account,
period=self.__period)
return OptionLink(gettext("Ledger"), url,
self.__active_report == ReportType.LEDGER)
@property
def __income_expenses(self) -> OptionLink:
"""Returns the income and expenses.
:return: The income and expenses.
"""
account: Account = self.__account
if not re.match(r"[12][12]", account.base_code):
account: Account = Account.find_by_code("1111-001")
url: str = url_for("accounting.report.income-expenses-default",
currency=self.__currency, account=account) \
if self.__period.is_default \
else url_for("accounting.report.income-expenses",
currency=self.__currency, account=account,
period=self.__period)
return OptionLink(gettext("Income and Expenses"), url,
self.__active_report == ReportType.INCOME_EXPENSES)
@property
def __trial_balance(self) -> OptionLink:
"""Returns the trial balance.
:return: The trial balance.
"""
url: str = url_for("accounting.report.trial-balance-default",
currency=self.__currency) \
if self.__period.is_default \
else url_for("accounting.report.trial-balance",
currency=self.__currency, period=self.__period)
return OptionLink(gettext("Trial Balance"), url,
self.__active_report == ReportType.TRIAL_BALANCE)
@property
def __income_statement(self) -> OptionLink:
"""Returns the income statement.
:return: The income statement.
"""
url: str = url_for("accounting.report.income-statement-default",
currency=self.__currency) \
if self.__period.is_default \
else url_for("accounting.report.income-statement",
currency=self.__currency, period=self.__period)
return OptionLink(gettext("Income Statement"), url,
self.__active_report == ReportType.INCOME_STATEMENT)
@property
def __balance_sheet(self) -> OptionLink:
"""Returns the balance sheet.
:return: The balance sheet.
"""
url: str = url_for("accounting.report.balance-sheet-default",
currency=self.__currency) \
if self.__period.is_default \
else url_for("accounting.report.balance-sheet",
currency=self.__currency, period=self.__period)
return OptionLink(gettext("Balance Sheet"), url,
self.__active_report == ReportType.BALANCE_SHEET)
def __iter__(self) -> t.Iterator[OptionLink]:
"""Returns the iteration of the reports.
:return: The iteration of the reports.
"""
return iter(self.__reports)

View File

@ -0,0 +1,36 @@
# The Mia! Accounting Flask Project.
# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4
# Copyright (c) 2023 imacat.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The report types.
"""
from enum import Enum
class ReportType(Enum):
"""The report types."""
JOURNAL: str = "journal"
"""The journal."""
LEDGER: str = "ledger"
"""The ledger."""
INCOME_EXPENSES: str = "income-expenses"
"""The income and expenses."""
TRIAL_BALANCE: str = "trial-balance"
"""The trial balance."""
INCOME_STATEMENT: str = "income-statement"
"""The income statement."""
BALANCE_SHEET: str = "balance-sheet"
"""The balance sheet."""