From 436a4c367f90a429dd4b5c97ecb8b55e7d7c10d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Tue, 7 Mar 2023 16:56:44 +0800 Subject: [PATCH] Added the balance sheet. --- src/accounting/report/balance_sheet.py | 489 ++++++++++++++++++ src/accounting/report/period_choosers.py | 19 + src/accounting/report/report_chooser.py | 15 + src/accounting/report/report_type.py | 2 + src/accounting/report/views.py | 41 ++ src/accounting/static/css/style.css | 21 + .../accounting/report/balance-sheet.html | 243 +++++++++ 7 files changed, 830 insertions(+) create mode 100644 src/accounting/report/balance_sheet.py create mode 100644 src/accounting/templates/accounting/report/balance-sheet.html diff --git a/src/accounting/report/balance_sheet.py b/src/accounting/report/balance_sheet.py new file mode 100644 index 0000000..30c43ec --- /dev/null +++ b/src/accounting/report/balance_sheet.py @@ -0,0 +1,489 @@ +# 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 balance sheet. + +""" +import csv +import typing as t +from decimal import Decimal +from io import StringIO +from urllib.parse import urlparse, ParseResult, parse_qsl, urlencode, \ + urlunparse + +import sqlalchemy as sa +from flask import url_for, render_template, Response, request + +from accounting import db +from accounting.models import Currency, BaseAccount, Account, Transaction, \ + JournalEntry +from accounting.utils.txn_types import TransactionType +from .option_link import OptionLink +from .period import Period +from .period_choosers import BalanceSheetPeriodChooser +from .report_chooser import ReportChooser +from .report_type import ReportType + + +class BalanceSheetAccount: + """An account in the balance sheet.""" + + def __init__(self, account: Account, amount: Decimal, url: str): + """Constructs an account in the balance sheet. + + :param account: The account. + :param amount: The amount. + :param url: The URL to the ledger of the account. + """ + self.account: Account = account + """The account.""" + self.amount: Decimal = amount + """The amount of the account.""" + self.url: str = url + """The URL to the ledger of the account.""" + + +class BalanceSheetSubsection: + """A subsection in the balance sheet.""" + + def __init__(self, title: BaseAccount): + """Constructs a subsection in the balance sheet. + + :param title: The title account. + """ + self.title: BaseAccount = title + """The title account.""" + self.accounts: list[BalanceSheetAccount] = [] + """The accounts in the subsection.""" + + @property + def total(self) -> Decimal: + """Returns the total of the subsection. + + :return: The total of the subsection. + """ + return sum([x.amount for x in self.accounts]) + + +class BalanceSheetSection: + """A section in the balance sheet.""" + + def __init__(self, title: BaseAccount): + """Constructs a section in the balance sheet. + + :param title: The title account. + """ + self.title: BaseAccount = title + """The title account.""" + self.subsections: list[BalanceSheetSubsection] = [] + """The subsections in the section.""" + + @property + def total(self) -> Decimal: + """Returns the total of the section. + + :return: The total of the section. + """ + return sum([x.total for x in self.subsections]) + + +class CSVHalfRow: + """A half row in the CSV balance sheet.""" + + def __init__(self, title: str | None, amount: Decimal | None): + """The constructs a half row in the CSV balance sheet. + + :param title: The title. + :param amount: The amount. + """ + self.title: str | None = title + """The title.""" + self.amount: Decimal | None = amount + """The amount.""" + + +class CSVRow: + """A row in the CSV balance sheet.""" + + def __init__(self): + """Constructs a row in the CSV balance sheet.""" + self.asset_title: str | None = None + """The title of the asset.""" + self.asset_amount: Decimal | None = None + """The amount of the asset.""" + self.liability_title: str | None = None + """The title of the liability.""" + self.liability_amount: Decimal | None = None + """The amount of the liability.""" + + @property + def values(self) -> list[str | Decimal | None]: + """Returns the values of the row. + + :return: The values of the row. + """ + return [self.asset_title, self.asset_amount, + self.liability_title, self.liability_amount] + + +class BalanceSheetPageParams: + """The HTML parameters of the balance sheet.""" + + def __init__(self, currency: Currency, + period: Period, + has_data: bool, + assets: BalanceSheetSection, + liabilities: BalanceSheetSection, + owner_s_equity: BalanceSheetSection): + """Constructs the HTML parameters of the balance sheet. + + :param currency: The currency. + :param period: The period. + :param has_data: True if there is any data, or False otherwise. + :param assets: The assets. + :param liabilities: The liabilities. + :param owner_s_equity: The owner's equity. + """ + self.currency: Currency = currency + """The currency.""" + self.period: Period = period + """The period.""" + self.has_data: bool = has_data + """True if there is any data, or False otherwise.""" + self.assets: BalanceSheetSection = assets + """The assets.""" + self.liabilities: BalanceSheetSection = liabilities + """The liabilities.""" + self.owner_s_equity: BalanceSheetSection = owner_s_equity + """The owner's equity.""" + self.period_chooser: BalanceSheetPeriodChooser \ + = BalanceSheetPeriodChooser(currency) + """The period chooser.""" + self.report_chooser: ReportChooser \ + = ReportChooser(ReportType.BALANCE_SHEET, + currency=currency, + period=period) + """The report chooser.""" + self.txn_types: t.Type[TransactionType] = TransactionType + """The transaction types.""" + + @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) + + @property + def currency_options(self) -> list[OptionLink]: + """Returns the currency options. + + :return: The currency options. + """ + def get_url(currency: Currency): + if self.period.is_default: + return url_for("accounting.report.balance-sheet-default", + currency=currency) + return url_for("accounting.report.balance-sheet", + currency=currency, period=self.period) + + in_use: set[str] = set(db.session.scalars( + sa.select(JournalEntry.currency_code) + .group_by(JournalEntry.currency_code)).all()) + return [OptionLink(str(x), get_url(x), x.code == self.currency.code) + for x in Currency.query.filter(Currency.code.in_(in_use)) + .order_by(Currency.code).all()] + + +class BalanceSheet: + """The balance sheet.""" + + def __init__(self, currency: Currency, period: Period): + """Constructs a balance sheet. + + :param currency: The currency. + :param period: The period. + """ + self.currency: Currency = currency + """The currency.""" + self.period: Period = period + """The period.""" + self.__set_data() + + def __set_data(self) -> None: + """Queries and sets assets, the liabilities, and the owner's equity + sections in the balance sheet. + + :return: The assets, the liabilities, and the owner's equity sections. + """ + balances: list[BalanceSheetAccount] = self.__query_balances() + + titles: list[BaseAccount] = BaseAccount.query\ + .filter(BaseAccount.code.in_({"1", "2", "3"})).all() + subtitles: list[BaseAccount] = BaseAccount.query\ + .filter(BaseAccount.code.in_({x.account.base_code[:2] + for x in balances})).all() + + sections: dict[str, BalanceSheetSection] \ + = {x.code: BalanceSheetSection(x) for x in titles} + subsections: dict[str, BalanceSheetSubsection] \ + = {x.code: BalanceSheetSubsection(x) for x in subtitles} + for subsection in subsections.values(): + sections[subsection.title.code[0]].subsections.append(subsection) + for balance in balances: + subsections[balance.account.base_code[:2]].accounts.append(balance) + + self.__has_data: bool = len(balances) > 0 + self.__assets: BalanceSheetSection = sections["1"] + self.__liabilities: BalanceSheetSection = sections["2"] + self.__owner_s_equity: BalanceSheetSection = sections["3"] + + def __query_balances(self) -> list[BalanceSheetAccount]: + """Queries and returns the balances. + + :return: The balances. + """ + sub_conditions: list[sa.BinaryExpression] \ + = [Account.base_code.startswith(x) for x in {"1", "2", "3"}] + conditions: list[sa.BinaryExpression] \ + = [JournalEntry.currency_code == self.currency.code, + sa.or_(*sub_conditions)] + if self.period.end is not None: + conditions.append(Transaction.date <= self.period.end) + balance_func: sa.Function = sa.func.sum(sa.case( + (JournalEntry.is_debit, JournalEntry.amount), + else_=-JournalEntry.amount)).label("balance") + select_balance: sa.Select \ + = sa.select(Account.id, Account.base_code, Account.no, + balance_func)\ + .join(Transaction).join(Account)\ + .filter(*conditions)\ + .group_by(Account.id, Account.base_code, Account.no)\ + .order_by(Account.base_code, Account.no) + account_balances: list[sa.Row] \ + = db.session.execute(select_balance).all() + account_by_id: dict[int, Account] \ + = {x.id: x for x in Account.query + .filter(sa.or_(Account.id.in_({x.id for x in account_balances}), + Account.base_code == "3351", + Account.base_code == "3353")).all()} + + def get_url(account: Account) -> str: + """Returns the ledger URL of an account. + + :param account: The account. + :return: The ledger URL of the account. + """ + if self.period.is_default: + return url_for("accounting.report.ledger-default", + currency=self.currency, account=account) + return url_for("accounting.report.ledger", + currency=self.currency, account=account, + period=self.period) + + balances: list[BalanceSheetAccount] \ + = [BalanceSheetAccount(account=account_by_id[x.id], + amount=x.balance, + url=get_url(account_by_id[x.id])) + for x in account_balances] + self.__add_accumulated(balances, list(account_by_id.values())) + self.__add_current_period(balances, list(account_by_id.values())) + for balance in balances: + if not balance.account.base_code.startswith("1"): + balance.amount = -balance.amount + return balances + + def __add_accumulated(self, balances: list[BalanceSheetAccount], + accounts: list[Account]) -> None: + """Adds the accumulated profit or loss to the balances. + + :param balances: The accounts on the balance sheet. + :param accounts: The accounts. + :return: None. + """ + code: str = "3351-001" + amount: Decimal | None = self.__query_accumulated() + url: str = url_for("accounting.report.income-statement", + currency=self.currency, period=self.period.before) + self.__add_owner_s_equity(balances, accounts, code, amount, url) + + def __query_accumulated(self) -> Decimal | None: + """Queries and returns the accumulated profit or loss. + + :return: The accumulated profit or loss. + """ + if self.period.start is None: + return None + conditions: list[sa.BinaryExpression] \ + = [JournalEntry.currency_code == self.currency.code, + Transaction.date < self.period.start] + conditions.extend([sa.not_(Account.base_code.startswith(x)) + for x in {"1", "2"}]) + balance_func: sa.Function = sa.func.sum(sa.case( + (JournalEntry.is_debit, JournalEntry.amount), + else_=-JournalEntry.amount)).label("balance") + select_balance: sa.Select = sa.select(balance_func)\ + .join(Transaction).join(Account).filter(*conditions) + return db.session.scalar(select_balance) + + def __add_current_period(self, balances: list[BalanceSheetAccount], + accounts: list[Account]) -> None: + """Adds the accumulated profit or loss to the balances. + + :param balances: The accounts on the balance sheet. + :param accounts: The accounts. + :return: None. + """ + code: str = "3353-001" + amount: Decimal | None = self.__query_currency_period() + url: str = url_for("accounting.report.income-statement", + currency=self.currency, period=self.period) + self.__add_owner_s_equity(balances, accounts, code, amount, url) + + def __query_currency_period(self) -> Decimal | None: + """Queries and returns the net income or loss for current period. + + :return: The net income or loss for current period. + """ + conditions: list[sa.BinaryExpression] \ + = [JournalEntry.currency_code == self.currency.code] + if self.period.start is not None: + conditions.append(Transaction.date >= self.period.start) + if self.period.end is not None: + conditions.append(Transaction.date <= self.period.end) + conditions.extend([sa.not_(Account.base_code.startswith(x)) + for x in {"1", "2"}]) + balance_func: sa.Function = sa.func.sum(sa.case( + (JournalEntry.is_debit, JournalEntry.amount), + else_=-JournalEntry.amount)).label("balance") + select_balance: sa.Select = sa.select(balance_func)\ + .join(Transaction).join(Account).filter(*conditions) + return db.session.scalar(select_balance) + + @staticmethod + def __add_owner_s_equity(balances: list[BalanceSheetAccount], + accounts: list[Account], + code: str, + amount: Decimal | None, + url: str): + """Adds an owner's equity balance. + + :param balances: The accounts on the balance sheet. + :param accounts: The accounts. + :param code: The code of the account to add. + :param amount: The amount. + :return: None. + """ + # There is an existing balance. + balance_by_code: dict[str, BalanceSheetAccount] \ + = {x.account.code: x for x in balances} + if code in balance_by_code: + balance: BalanceSheetAccount = balance_by_code[code] + balance.url = url + if amount is not None: + balance.amount = balance.amount + amount + return + # Add a new balance + if amount is None: + return + account_by_code: dict[str, Account] = {x.code: x for x in accounts} + balances.append(BalanceSheetAccount( + account=account_by_code[code], + amount=amount, + url=url)) + + def csv(self) -> Response: + """Returns the report as CSV for download. + + :return: The response of the report for download. + """ + filename: str = "balance-sheet-{currency}-{period}.csv"\ + .format(currency=self.currency.code, period=self.period.spec) + rows: list[CSVRow] = self.__get_csv_rows() + with StringIO() as fp: + writer = csv.writer(fp) + writer.writerows([x.values for x in rows]) + fp.seek(0) + response: Response = Response(fp.read(), mimetype="text/csv") + response.headers["Content-Disposition"] \ + = f"attachment; filename={filename}" + return response + + def __get_csv_rows(self) -> list[CSVRow]: + """Composes and returns the CSV rows. + + :return: The CSV rows. + """ + asset_rows: list[CSVHalfRow] = self.__section_csv_rows(self.__assets) + liability_rows: list[CSVHalfRow] = [] + liability_rows.extend(self.__section_csv_rows(self.__liabilities)) + liability_rows.append(CSVHalfRow("Total", self.__liabilities.total)) + liability_rows.append(CSVHalfRow(None, None)) + liability_rows.extend(self.__section_csv_rows(self.__owner_s_equity)) + liability_rows.append(CSVHalfRow("Total", self.__owner_s_equity.total)) + rows: list[CSVRow] = [CSVRow() for _ in + range(max(len(asset_rows), len(liability_rows)))] + for i in range(len(rows)): + if i < len(asset_rows): + rows[i].asset_title = asset_rows[i].title + rows[i].asset_amount = asset_rows[i].amount + if i < len(liability_rows) and liability_rows[i].title is not None: + rows[i].liability_title = liability_rows[i].title + rows[i].liability_amount = liability_rows[i].amount + total: CSVRow = CSVRow() + total.asset_title = "Total" + total.asset_amount = self.__assets.total + total.liability_title = "Total" + total.liability_amount \ + = self.__liabilities.total + self.__owner_s_equity.total + rows.append(total) + return rows + + @staticmethod + def __section_csv_rows(section: BalanceSheetSection) -> list[CSVHalfRow]: + """Gathers the CSV rows for a section. + + :param section: The section. + :return: The CSV rows for the section. + """ + rows: list[CSVHalfRow] \ + = [CSVHalfRow(section.title.title.title(), None)] + for subsection in section.subsections: + rows.append(CSVHalfRow(f" {subsection.title.title.title()}", None)) + for account in subsection.accounts: + rows.append(CSVHalfRow(f" {str(account.account).title()}", + account.amount)) + return rows + + def html(self) -> str: + """Composes and returns the report as HTML. + + :return: The report as HTML. + """ + params: BalanceSheetPageParams = BalanceSheetPageParams( + currency=self.currency, + period=self.period, + has_data=self.__has_data, + assets=self.__assets, + liabilities=self.__liabilities, + owner_s_equity=self.__owner_s_equity) + return render_template("accounting/report/balance-sheet.html", + report=params) diff --git a/src/accounting/report/period_choosers.py b/src/accounting/report/period_choosers.py index 1d190c7..c0e49eb 100644 --- a/src/accounting/report/period_choosers.py +++ b/src/accounting/report/period_choosers.py @@ -197,3 +197,22 @@ class IncomeStatementPeriodChooser(PeriodChooser): 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) diff --git a/src/accounting/report/report_chooser.py b/src/accounting/report/report_chooser.py index f0dc75f..d0d5850 100644 --- a/src/accounting/report/report_chooser.py +++ b/src/accounting/report/report_chooser.py @@ -70,6 +70,7 @@ class ReportChooser: 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 @@ -147,6 +148,20 @@ class ReportChooser: 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. diff --git a/src/accounting/report/report_type.py b/src/accounting/report/report_type.py index 4749227..e2c8f39 100644 --- a/src/accounting/report/report_type.py +++ b/src/accounting/report/report_type.py @@ -32,3 +32,5 @@ class ReportType(Enum): """The trial balance.""" INCOME_STATEMENT: str = "income-statement" """The income statement.""" + BALANCE_SHEET: str = "balance-sheet" + """The balance sheet.""" diff --git a/src/accounting/report/views.py b/src/accounting/report/views.py index 9b82052..446dafd 100644 --- a/src/accounting/report/views.py +++ b/src/accounting/report/views.py @@ -21,6 +21,7 @@ from flask import Blueprint, request, Response from accounting.models import Currency, Account from accounting.utils.permission import has_permission, can_view +from .balance_sheet import BalanceSheet from .period import Period from .reports import Journal, Ledger, IncomeExpenses, TrialBalance, \ IncomeStatement @@ -229,3 +230,43 @@ def __get_income_statement_list(currency: Currency, period: Period) \ if "as" in request.args and request.args["as"] == "csv": return report.csv() return report.html() + + +@bp.get("balance-sheet/", + endpoint="balance-sheet-default") +@has_permission(can_view) +def get_default_balance_sheet_list(currency: Currency) -> str | Response: + """Returns the balance sheet in the default period. + + :param currency: The currency. + :return: The balance sheet in the default period. + """ + return __get_balance_sheet_list(currency, Period.get_instance()) + + +@bp.get("balance-sheet//", + endpoint="balance-sheet") +@has_permission(can_view) +def get_balance_sheet_list(currency: Currency, period: Period) \ + -> str | Response: + """Returns the balance sheet. + + :param currency: The currency. + :param period: The period. + :return: The balance sheet in the period. + """ + return __get_balance_sheet_list(currency, period) + + +def __get_balance_sheet_list(currency: Currency, period: Period) \ + -> str | Response: + """Returns the balance sheet. + + :param currency: The currency. + :param period: The period. + :return: The balance sheet in the period. + """ + report: BalanceSheet = BalanceSheet(currency, period) + if "as" in request.args and request.args["as"] == "csv": + return report.csv() + return report.html() diff --git a/src/accounting/static/css/style.css b/src/accounting/static/css/style.css index 5841995..0c43b49 100644 --- a/src/accounting/static/css/style.css +++ b/src/accounting/static/css/style.css @@ -207,6 +207,27 @@ a.accounting-report-table-row { .accounting-income-statement-total:last-child { margin-bottom: 0; } +.accounting-balance-sheet-section, .accounting-balance-sheet-total { + font-size: 1.2rem; + font-weight: bolder; +} +.accounting-balance-sheet-section { + border-bottom: thick double darkslategray; +} +.accounting-balance-sheet-total { + border-top: thick double darkslategray; +} +.accounting-balance-sheet-subtotal { + font-size: 1.1rem; + font-weight: bolder; + border-top: thick double darkslategray; +} +.accounting-balance-sheet-account { + margin-left: 0.5rem; +} +.accounting-balance-sheet-total .accounting-amount, .accounting-balance-sheet-subtotal, .accounting-amount { + font-style: italic; +} /* The accounting report */ .accounting-mobile-journal-credit { diff --git a/src/accounting/templates/accounting/report/balance-sheet.html b/src/accounting/templates/accounting/report/balance-sheet.html new file mode 100644 index 0000000..d1c9b26 --- /dev/null +++ b/src/accounting/templates/accounting/report/balance-sheet.html @@ -0,0 +1,243 @@ +{# +The Mia! Accounting Flask Project +income-statement.html: The income statement + + 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. + +Author: imacat@mail.imacat.idv.tw (imacat) +First written: 2023/3/7 +#} +{% extends "accounting/base.html" %} + +{% block accounting_scripts %} + + +{% endblock %} + +{% block header %}{% block title %}{{ _("Balance Sheet of %(currency)s %(period)s", currency=report.currency.name|title, period=report.period.desc|title) }}{% endblock %}{% endblock %} + +{% block content %} + +
+ {% if accounting_can_edit() %} + + {% endif %} + {% with report_chooser = report.report_chooser %} + {% include "accounting/report/include/report-chooser.html" %} + {% endwith %} +
+ + +
+ + + + {{ A_("Download") }} + +
+ +{% with txn_types = report.txn_types %} + {% include "accounting/include/add-txn-material-fab.html" %} +{% endwith %} + +
+ {% with report_chooser = report.report_chooser %} + {% include "accounting/report/include/report-chooser.html" %} + {% endwith %} +
+ + +
+ +
+ +{% with period = report.period, period_chooser = report.period_chooser %} + {% include "accounting/report/include/period-chooser.html" %} +{% endwith %} + +{% if report.has_data %} +
+
+

{{ _("Balance Sheet of %(currency)s %(period)s", currency=report.currency.name|title, period=report.period.desc|title) }}

+
+ +
+
+ {% if report.assets.subsections %} +
+
{{ report.assets.title.title|title }}
+
+
+ {% for subsection in report.assets.subsections %} +
+
+ {{ subsection.title.code }} + {{ subsection.title.title|title }} +
+
+ {% for account in subsection.accounts %} + + {% endfor %} + {% endfor %} +
+
+
{{ A_("Total") }}
+
{{ report.assets.total|accounting_format_amount }}
+
+ {% endif %} +
+ +
+ {% if report.liabilities.subsections %} +
+
{{ report.liabilities.title.title|title }}
+
+
+ {% for subsection in report.liabilities.subsections %} +
+
+ {{ subsection.title.code }} + {{ subsection.title.title|title }} +
+
+ {% for account in subsection.accounts %} + + {% endfor %} + {% endfor %} +
+
+
{{ A_("Total") }}
+
{{ report.liabilities.total|accounting_format_amount }}
+
+ {% endif %} + + {% if report.owner_s_equity.subsections %} +
+
{{ report.owner_s_equity.title.title|title }}
+
+
+ {% for subsection in report.owner_s_equity.subsections %} +
+
+ {{ subsection.title.code }} + {{ subsection.title.title|title }} +
+
+ {% for account in subsection.accounts %} + + {% endfor %} + {% endfor %} +
+
+
{{ A_("Total") }}
+
{{ report.owner_s_equity.total|accounting_format_amount }}
+
+ {% endif %} + +
+
{{ A_("Total") }}
+
{{ (report.liabilities.total + report.owner_s_equity.total)|accounting_format_amount }}
+
+
+
+ +
+
+
+
{{ A_("Total") }}
+
{{ report.assets.total|accounting_format_amount }}
+
+
+ +
+
+
{{ A_("Total") }}
+
{{ (report.liabilities.total + report.owner_s_equity.total)|accounting_format_amount }}
+
+
+
+
+{% else %} +

{{ A_("There is no data.") }}

+{% endif %} + +{% endblock %}