Added the income statement.
This commit is contained in:
parent
fcefc64117
commit
84d239e4b1
@ -178,3 +178,22 @@ class TrialBalancePeriodChooser(PeriodChooser):
|
||||
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)
|
||||
|
@ -69,6 +69,7 @@ class ReportChooser:
|
||||
self.__reports.append(self.__ledger)
|
||||
self.__reports.append(self.__income_expenses)
|
||||
self.__reports.append(self.__trial_balance)
|
||||
self.__reports.append(self.__income_statement)
|
||||
for report in self.__reports:
|
||||
if report.is_active:
|
||||
self.current_report = report.title
|
||||
@ -132,6 +133,20 @@ class ReportChooser:
|
||||
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)
|
||||
|
||||
def __iter__(self) -> t.Iterator[OptionLink]:
|
||||
"""Returns the iteration of the reports.
|
||||
|
||||
|
@ -29,10 +29,10 @@ from accounting.report.option_link import OptionLink
|
||||
from accounting.report.period import Period
|
||||
from accounting.report.period_choosers import PeriodChooser, \
|
||||
JournalPeriodChooser, LedgerPeriodChooser, IncomeExpensesPeriodChooser, \
|
||||
TrialBalancePeriodChooser
|
||||
TrialBalancePeriodChooser, IncomeStatementPeriodChooser
|
||||
from accounting.report.report_chooser import ReportChooser
|
||||
from accounting.report.report_rows import JournalRow, LedgerRow, \
|
||||
IncomeExpensesRow, TrialBalanceRow
|
||||
IncomeExpensesRow, TrialBalanceRow, IncomeStatementRow
|
||||
from accounting.report.report_type import ReportType
|
||||
from accounting.utils.pagination import Pagination
|
||||
from accounting.utils.txn_types import TransactionType
|
||||
@ -339,3 +339,50 @@ class TrialBalanceParams(ReportParams[TrialBalanceRow]):
|
||||
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 IncomeStatementParams(ReportParams[IncomeStatementRow]):
|
||||
"""The parameters of an income statement page."""
|
||||
|
||||
def __init__(self,
|
||||
currency: Currency,
|
||||
period: Period,
|
||||
data_rows: list[IncomeStatementRow]):
|
||||
"""Constructs the parameters for the income statement page.
|
||||
|
||||
:param currency: The currency.
|
||||
:param period: The period.
|
||||
:param data_rows: The data rows.
|
||||
:param total: The total row, if any.
|
||||
"""
|
||||
super().__init__(
|
||||
period_chooser=IncomeStatementPeriodChooser(currency),
|
||||
report_chooser=ReportChooser(ReportType.INCOME_STATEMENT,
|
||||
currency=currency,
|
||||
period=period),
|
||||
data_rows=data_rows,
|
||||
is_paged=False)
|
||||
self.currency: Currency = currency
|
||||
"""The currency."""
|
||||
self.period: Period | None = period
|
||||
"""The period."""
|
||||
|
||||
@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.income-statement-default",
|
||||
currency=currency)
|
||||
return url_for("accounting.report.income-statement",
|
||||
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()]
|
||||
|
@ -206,3 +206,65 @@ class TrialBalanceRow(ReportRow):
|
||||
return {"Account": str(self.account).title(),
|
||||
"Debit": self.debit,
|
||||
"Credit": self.credit}
|
||||
|
||||
|
||||
class IncomeStatementRow(ReportRow):
|
||||
"""A row in the income statement."""
|
||||
|
||||
def __init__(self,
|
||||
code: str | None = None,
|
||||
title: str | None = None,
|
||||
amount: Decimal | None = None,
|
||||
is_category: bool = False,
|
||||
is_total: bool = False,
|
||||
is_subcategory: bool = False,
|
||||
is_subtotal: bool = False,
|
||||
url: str | None = None):
|
||||
"""Constructs the row in the income statement.
|
||||
|
||||
:param code: The account code.
|
||||
:param title: The account title.
|
||||
:param amount: The amount.
|
||||
:param is_category: True for a category, or False otherwise.
|
||||
:param is_total: True for a total, or False otherwise.
|
||||
:param is_subcategory: True for a subcategory, or False otherwise.
|
||||
:param is_subtotal: True for a subtotal, or False otherwise.
|
||||
:param url: The URL for the account.
|
||||
"""
|
||||
self.is_total: bool = False
|
||||
"""Whether this is the total row."""
|
||||
self.code: str | None = code
|
||||
"""The account code."""
|
||||
self.title: str | None = title
|
||||
"""The account code."""
|
||||
self.amount: Decimal | None = amount
|
||||
"""The amount."""
|
||||
self.is_category: bool = is_category
|
||||
"""True if this row is a category, or False otherwise."""
|
||||
self.is_total: bool = is_total
|
||||
"""True if this row is a total, or False otherwise."""
|
||||
self.is_subcategory: bool = is_subcategory
|
||||
"""True if this row is a subcategory, or False otherwise."""
|
||||
self.is_subtotal: bool = is_subtotal
|
||||
"""True if this row is a subtotal, or False otherwise."""
|
||||
self.url: str | None = url
|
||||
"""The URL."""
|
||||
|
||||
@property
|
||||
def is_account(self) -> bool:
|
||||
"""Returns whether the row represents an account.
|
||||
|
||||
:return: True if the row represents an account, or False otherwise.
|
||||
"""
|
||||
return not self.is_category and not self.is_total \
|
||||
and not self.is_subcategory and not self.is_subtotal
|
||||
|
||||
def as_dict(self) -> dict[str, t.Any]:
|
||||
if self.is_subtotal:
|
||||
return {"": "Total",
|
||||
"Amount": self.amount}
|
||||
if self.is_total:
|
||||
return {"": self.title,
|
||||
"Amount": self.amount}
|
||||
return {"": f"{self.code} {self.title}",
|
||||
"Amount": self.amount}
|
||||
|
@ -30,3 +30,5 @@ class ReportType(Enum):
|
||||
"""The income and expenses."""
|
||||
TRIAL_BALANCE: str = "trial-balance"
|
||||
"""The trial balance."""
|
||||
INCOME_STATEMENT: str = "income-statement"
|
||||
"""The income statement."""
|
||||
|
@ -28,12 +28,13 @@ from flask import Response, render_template, url_for
|
||||
|
||||
from accounting import db
|
||||
from accounting.locale import gettext
|
||||
from accounting.models import Currency, Account, Transaction, JournalEntry
|
||||
from accounting.models import Currency, BaseAccount, Account, Transaction, \
|
||||
JournalEntry
|
||||
from accounting.report.period import Period
|
||||
from accounting.report.report_params import JournalParams, LedgerParams, \
|
||||
IncomeExpensesParams, TrialBalanceParams
|
||||
IncomeExpensesParams, TrialBalanceParams, IncomeStatementParams
|
||||
from accounting.report.report_rows import JournalRow, LedgerRow, \
|
||||
IncomeExpensesRow, TrialBalanceRow
|
||||
IncomeExpensesRow, TrialBalanceRow, IncomeStatementRow
|
||||
|
||||
T = t.TypeVar("T")
|
||||
|
||||
@ -578,3 +579,160 @@ class TrialBalance(Report[TrialBalanceRow]):
|
||||
total=self.total)
|
||||
return render_template("accounting/report/trial-balance.html",
|
||||
report=params)
|
||||
|
||||
|
||||
class IncomeStatement(Report[IncomeStatementRow]):
|
||||
"""The income statement."""
|
||||
|
||||
def __init__(self, currency: Currency, period: Period):
|
||||
"""Constructs an income statement.
|
||||
|
||||
:param currency: The currency.
|
||||
:param period: The period.
|
||||
"""
|
||||
self.currency: Currency = currency
|
||||
"""The currency."""
|
||||
self.period: Period = period
|
||||
"""The period."""
|
||||
super().__init__()
|
||||
|
||||
def get_rows(self) -> tuple[list[T], T | None, T | None]:
|
||||
rows: list[IncomeStatementRow] = self.__query_balances()
|
||||
rows = self.__get_income_statement_rows(rows)
|
||||
return rows, None, None
|
||||
|
||||
def __query_balances(self) -> list[IncomeStatementRow]:
|
||||
"""Queries and returns the balances.
|
||||
|
||||
:return: The balances.
|
||||
"""
|
||||
sub_conditions: list[sa.BinaryExpression] \
|
||||
= [Account.base_code.startswith(str(x)) for x in range(4, 10)]
|
||||
conditions: list[sa.BinaryExpression] \
|
||||
= [JournalEntry.currency_code == self.currency.code,
|
||||
sa.or_(*sub_conditions)]
|
||||
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)
|
||||
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(JournalEntry.account_id, balance_func)\
|
||||
.join(Transaction).join(Account)\
|
||||
.filter(*conditions)\
|
||||
.group_by(JournalEntry.account_id)\
|
||||
.order_by(Account.base_code, Account.no)
|
||||
balances: list[sa.Row] = db.session.execute(select_balance).all()
|
||||
accounts: dict[int, Account] \
|
||||
= {x.id: x for x in Account.query
|
||||
.filter(Account.id.in_([x.account_id for x in balances])).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)
|
||||
|
||||
return [IncomeStatementRow(code=accounts[x.account_id].code,
|
||||
title=accounts[x.account_id].title,
|
||||
amount=x.balance,
|
||||
url=get_url(accounts[x.account_id]))
|
||||
for x in balances]
|
||||
|
||||
@staticmethod
|
||||
def __get_income_statement_rows(balances: list[IncomeStatementRow]) \
|
||||
-> list[IncomeStatementRow]:
|
||||
"""Composes the categories and totals from the balance rows.
|
||||
|
||||
:param balances: The balance rows.
|
||||
:return: None.
|
||||
"""
|
||||
categories: list[BaseAccount] \
|
||||
= BaseAccount.query\
|
||||
.filter(BaseAccount.code.in_([str(x) for x in range(4, 10)]))\
|
||||
.order_by(BaseAccount.code).all()
|
||||
subcategory_codes: set[str] = {x.code[:2] for x in balances}
|
||||
subcategory_dict: dict[str, BaseAccount] \
|
||||
= {x.code: x for x in BaseAccount.query
|
||||
.filter(BaseAccount.code.in_({x.code[:2] for x in balances}))}
|
||||
|
||||
balances_by_subcategory: dict[str, list[IncomeStatementRow]] \
|
||||
= {x: [] for x in subcategory_codes}
|
||||
for balance in balances:
|
||||
balances_by_subcategory[balance.code[:2]].append(balance)
|
||||
|
||||
subcategories_by_category: dict[str, list[BaseAccount]] \
|
||||
= {x.code: [] for x in categories}
|
||||
for subcategory in subcategory_dict.values():
|
||||
subcategories_by_category[subcategory.code[0]].append(subcategory)
|
||||
|
||||
total_titles: dict[str, str] \
|
||||
= {"4": "total revenue",
|
||||
"5": "gross income",
|
||||
"6": "operating income",
|
||||
"7": "before tax income",
|
||||
"8": "after tax income",
|
||||
"9": "net income or loss for current period"}
|
||||
|
||||
rows: list[IncomeStatementRow] = []
|
||||
total: Decimal = Decimal(0)
|
||||
for category in categories:
|
||||
rows.append(IncomeStatementRow(code=category.code,
|
||||
title=category.title,
|
||||
is_category=True))
|
||||
for subcategory in subcategories_by_category[category.code]:
|
||||
rows.append(IncomeStatementRow(code=subcategory.code,
|
||||
title=subcategory.title,
|
||||
is_subcategory=True))
|
||||
subtotal: Decimal = Decimal(0)
|
||||
for balance in balances_by_subcategory[subcategory.code]:
|
||||
rows.append(balance)
|
||||
subtotal = subtotal + balance.amount
|
||||
rows.append(IncomeStatementRow(amount=subtotal,
|
||||
is_subtotal=True))
|
||||
total = total + subtotal
|
||||
rows.append(IncomeStatementRow(title=total_titles[category.code],
|
||||
amount=total,
|
||||
is_total=True))
|
||||
return rows
|
||||
|
||||
def __get_category(self, category: BaseAccount,
|
||||
subcategory_dict: dict[str, BaseAccount],
|
||||
balances: list[IncomeStatementRow]) \
|
||||
-> list[IncomeStatementRow]:
|
||||
"""Returns the rows in the category.
|
||||
|
||||
:param category: The category.
|
||||
:param subcategory_dict: The subcategories
|
||||
:param balances: The balances.
|
||||
:return: The rows in the category.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def populate_rows(rows: list[JournalRow]) -> None:
|
||||
pass
|
||||
|
||||
@property
|
||||
def csv_field_names(self) -> list[str]:
|
||||
return ["", "Amount"]
|
||||
|
||||
@property
|
||||
def csv_filename(self) -> str:
|
||||
return f"income-statement-{self.period.spec}.csv"
|
||||
|
||||
def html(self) -> str:
|
||||
params: IncomeStatementParams = IncomeStatementParams(
|
||||
currency=self.currency,
|
||||
period=self.period,
|
||||
data_rows=self.data_rows)
|
||||
return render_template("accounting/report/income-statement.html",
|
||||
report=params)
|
||||
|
@ -22,7 +22,8 @@ from flask import Blueprint, request, Response
|
||||
from accounting.models import Currency, Account
|
||||
from accounting.utils.permission import has_permission, can_view
|
||||
from .period import Period
|
||||
from .reports import Journal, Ledger, IncomeExpenses, TrialBalance
|
||||
from .reports import Journal, Ledger, IncomeExpenses, TrialBalance, \
|
||||
IncomeStatement
|
||||
|
||||
bp: Blueprint = Blueprint("report", __name__)
|
||||
"""The view blueprint for the reports."""
|
||||
@ -188,3 +189,43 @@ def __get_trial_balance_list(currency: Currency, period: Period) \
|
||||
if "as" in request.args and request.args["as"] == "csv":
|
||||
return report.csv()
|
||||
return report.html()
|
||||
|
||||
|
||||
@bp.get("income-statement/<currency:currency>",
|
||||
endpoint="income-statement-default")
|
||||
@has_permission(can_view)
|
||||
def get_default_income_statement_list(currency: Currency) -> str | Response:
|
||||
"""Returns the income statement in the default period.
|
||||
|
||||
:param currency: The currency.
|
||||
:return: The income statement in the default period.
|
||||
"""
|
||||
return __get_income_statement_list(currency, Period.get_instance())
|
||||
|
||||
|
||||
@bp.get("income-statement/<currency:currency>/<period:period>",
|
||||
endpoint="income-statement")
|
||||
@has_permission(can_view)
|
||||
def get_income_statement_list(currency: Currency, period: Period) \
|
||||
-> str | Response:
|
||||
"""Returns the income statement.
|
||||
|
||||
:param currency: The currency.
|
||||
:param period: The period.
|
||||
:return: The income statement in the period.
|
||||
"""
|
||||
return __get_income_statement_list(currency, period)
|
||||
|
||||
|
||||
def __get_income_statement_list(currency: Currency, period: Period) \
|
||||
-> str | Response:
|
||||
"""Returns the income statement.
|
||||
|
||||
:param currency: The currency.
|
||||
:param period: The period.
|
||||
:return: The income statement in the period.
|
||||
"""
|
||||
report: IncomeStatement = IncomeStatement(currency, period)
|
||||
if "as" in request.args and request.args["as"] == "csv":
|
||||
return report.csv()
|
||||
return report.html()
|
||||
|
@ -115,11 +115,11 @@
|
||||
font-weight: bolder;
|
||||
}
|
||||
.accounting-report-table-header {
|
||||
border-bottom: thick double slategray;
|
||||
border-bottom: thin solid slategray;
|
||||
}
|
||||
.accounting-report-table-footer {
|
||||
font-style: italic;
|
||||
border-top: thick double slategray;
|
||||
border-top: thin solid slategray;
|
||||
}
|
||||
.accounting-report-table-row {
|
||||
display: grid;
|
||||
@ -158,9 +158,56 @@ a.accounting-report-table-row {
|
||||
.accounting-income-expenses-table .accounting-report-table-footer .accounting-report-table-row {
|
||||
grid-template-columns: 7fr 1fr 1fr 1fr;
|
||||
}
|
||||
.accounting-trial-balance-table .accounting-report-table-header {
|
||||
border-bottom: thick double slategray;
|
||||
}
|
||||
.accounting-trial-balance-table .accounting-report-table-footer {
|
||||
border-top: thick double slategray;
|
||||
}
|
||||
.accounting-trial-balance-table .accounting-report-table-row {
|
||||
grid-template-columns: 3fr 1fr 1fr;
|
||||
}
|
||||
.accounting-income-statement-table .accounting-report-table-body {
|
||||
border-top: thick double slategray;
|
||||
border-bottom: thick double slategray;
|
||||
}
|
||||
.accounting-income-statement-table .accounting-report-table-row {
|
||||
grid-template-columns: 3fr 1fr;
|
||||
}
|
||||
.accounting-income-statement-table .accounting-report-table-header .accounting-report-table-row, .accounting-income-statement-table .accounting-report-table-row.accounting-income-statement-category, .accounting-income-statement-table .accounting-report-table-row.accounting-income-statement-subcategory {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.accounting-income-statement-category, .accounting-income-statement-total {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bolder;
|
||||
}
|
||||
.accounting-income-statement-subcategory, .accounting-income-statement-subtotal {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.accounting-income-statement-subcategory > :first-child {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.accounting-income-statement-subtotal {
|
||||
border-top: thin solid darkslategray;
|
||||
}
|
||||
.accounting-income-statement-account > :first-child, .accounting-income-statement-subtotal > :first-child {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
.accounting-income-statement-account > .accounting-amount, .accounting-income-statement-subtotal .accounting-amount {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
.accounting-income-statement-category {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.accounting-income-statement-category:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.accounting-income-statement-total {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.accounting-income-statement-total:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* The accounting report */
|
||||
.accounting-mobile-journal-credit {
|
||||
|
174
src/accounting/templates/accounting/report/income-statement.html
Normal file
174
src/accounting/templates/accounting/report/income-statement.html
Normal file
@ -0,0 +1,174 @@
|
||||
{#
|
||||
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 %}
|
||||
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
|
||||
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}{% block title %}{{ _("Income Statement of %(currency)s %(period)s", currency=report.currency.name|title, period=report.period.desc|title) }}{% endblock %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="btn-group mb-2 d-none d-md-inline-flex">
|
||||
{% if accounting_can_edit() %}
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
{{ A_("New") }}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for("accounting.transaction.create", txn_type=report.txn_types.CASH_EXPENSE)|accounting_append_next }}">
|
||||
{{ A_("Cash Expense") }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for("accounting.transaction.create", txn_type=report.txn_types.CASH_INCOME)|accounting_append_next }}">
|
||||
{{ A_("Cash Income") }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="{{ url_for("accounting.transaction.create", txn_type=report.txn_types.TRANSFER)|accounting_append_next }}">
|
||||
{{ A_("Transfer") }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% with report_chooser = report.report_chooser %}
|
||||
{% include "accounting/report/include/report-chooser.html" %}
|
||||
{% endwith %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-money-bill-wave"></i>
|
||||
{{ report.currency.name|title }}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for currency in report.currency_options %}
|
||||
<li>
|
||||
<a class="dropdown-item {% if currency.is_active %} active {% endif %}" href="{{ currency.url }}">
|
||||
{{ currency.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-period-chooser-modal">
|
||||
<i class="fa-solid fa-calendar-day"></i>
|
||||
{{ report.period.desc|title }}
|
||||
</button>
|
||||
<a class="btn btn-primary" role="button" href="{{ report.csv_uri }}">
|
||||
<i class="fa-solid fa-download"></i>
|
||||
{{ A_("Download") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% with txn_types = report.txn_types %}
|
||||
{% include "accounting/include/add-txn-material-fab.html" %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="btn-group btn-actions mb-3 d-md-none">
|
||||
{% with report_chooser = report.report_chooser %}
|
||||
{% include "accounting/report/include/report-chooser.html" %}
|
||||
{% endwith %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-money-bill-wave"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
{% for currency in report.currency_options %}
|
||||
<li>
|
||||
<a class="dropdown-item {% if currency.is_active %} active {% endif %}" href="{{ currency.url }}">
|
||||
{{ currency.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-period-chooser-modal">
|
||||
<i class="fa-solid fa-calendar-day"></i>
|
||||
{{ A_("Period") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% with period = report.period, period_chooser = report.period_chooser %}
|
||||
{% include "accounting/report/include/period-chooser.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% if report.has_data %}
|
||||
<div class="accounting-sheet">
|
||||
<div class="d-none d-sm-flex justify-content-center mb-3">
|
||||
<h2 class="text-center">{{ _("Income Statement of %(currency)s %(period)s", currency=report.currency.name|title, period=report.period.desc|title) }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="accounting-report-table accounting-income-statement-table">
|
||||
<div class="accounting-report-table-header">
|
||||
<div class="accounting-report-table-row">
|
||||
<div class="accounting-amount">{{ A_("Amount") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accounting-report-table-body">
|
||||
{% for item in report.data_rows %}
|
||||
{% if item.is_category %}
|
||||
<div class="accounting-report-table-row accounting-income-statement-category">
|
||||
<div>
|
||||
<span class="d-none d-md-inline">{{ item.code }}</span>
|
||||
{{ item.title|title }}
|
||||
</div>
|
||||
</div>
|
||||
{% elif item.is_total %}
|
||||
<div class="accounting-report-table-row accounting-income-statement-total">
|
||||
<div>{{ item.title|title }}</div>
|
||||
<div class="accounting-amount">{{ item.amount|accounting_format_amount }}</div>
|
||||
</div>
|
||||
{% elif item.is_subcategory %}
|
||||
<div class="accounting-report-table-row accounting-income-statement-subcategory">
|
||||
<div>
|
||||
<span class="d-none d-md-inline">{{ item.code }}</span>
|
||||
{{ item.title|title }}
|
||||
</div>
|
||||
</div>
|
||||
{% elif item.is_subtotal %}
|
||||
<div class="accounting-report-table-row accounting-income-statement-subtotal">
|
||||
<div>{{ A_("Total") }}</div>
|
||||
<div class="accounting-amount">{{ item.amount|accounting_format_amount }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<a class="accounting-report-table-row accounting-income-statement-account" href="{{ item.url }}">
|
||||
<div>
|
||||
<span class="d-none d-md-inline">{{ item.code }}</span>
|
||||
{{ item.title|title }}
|
||||
</div>
|
||||
<div class="accounting-amount">{{ item.amount|accounting_format_amount }}</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>{{ A_("There is no data.") }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user