financial page with bank data
All checks were successful
Deploy / build-deploy (push) Successful in 1m48s
All checks were successful
Deploy / build-deploy (push) Successful in 1m48s
This commit is contained in:
parent
e68d2c245c
commit
33c68c2f44
11 changed files with 394 additions and 87 deletions
|
|
@ -1473,32 +1473,22 @@ export RenderedPage RenderFinancials(std::int64_t salesCount,
|
|||
std::vector<SafeHtml> incomeRows;
|
||||
if (fin.Loaded()) {
|
||||
incomeRows.push_back(MoneyRow(
|
||||
fin.donationCount == 1 ? std::string("Donations (1)")
|
||||
: std::format("Donations ({})", fin.donationCount),
|
||||
std::format("Donations ({})", fin.donationCount),
|
||||
fin.donationsMinor));
|
||||
}
|
||||
incomeRows.push_back(MoneyRow(
|
||||
salesCount == 1 ? std::string("Sales (1 order)")
|
||||
: std::format("Sales ({} orders)", salesCount),
|
||||
std::format("Sales ({})", salesCount),
|
||||
salesTotalMinor));
|
||||
if (fin.Loaded()) {
|
||||
incomeRows.push_back(totalRow("Income", fin.donationsMinor + salesTotalMinor));
|
||||
}
|
||||
|
||||
// Expenses: one table, group-header rows for the recurring/one-off split,
|
||||
// so the amounts stay in a single aligned column.
|
||||
// Expenses: one flat table. No recurring/one-off grouping — see the note
|
||||
// on Financials::expenses for why a lifetime total cannot carry a rate.
|
||||
SafeHtml expenses;
|
||||
if (fin.Loaded()) {
|
||||
std::vector<SafeHtml> rows;
|
||||
auto group = [&](std::string_view heading, std::span<const FinCategory> cats) {
|
||||
if (cats.empty()) return;
|
||||
rows.push_back(Format(
|
||||
R"(<tr class="fin-group"><th colspan="2">{}</th></tr>)",
|
||||
Escape(heading)));
|
||||
for (const FinCategory& c : cats) rows.push_back(MoneyRow(c.label, c.totalMinor));
|
||||
};
|
||||
group("Recurring", fin.recurring);
|
||||
group("One-off", fin.single);
|
||||
for (const FinCategory& c : fin.expenses) rows.push_back(MoneyRow(c.label, c.totalMinor));
|
||||
rows.push_back(totalRow("Expenses", fin.ExpensesMinor()));
|
||||
expenses = Format(R"(<table class="spec-table"><tbody>{}</tbody></table>)",
|
||||
Join(rows));
|
||||
|
|
@ -1509,6 +1499,33 @@ export RenderedPage RenderFinancials(std::int64_t salesCount,
|
|||
R"(figures above are already live.</p>)");
|
||||
}
|
||||
|
||||
// Net: what is actually left. Shown only when the bank figures exist —
|
||||
// income minus an unknown expense side is not a net of anything, and a
|
||||
// figure equal to sales while expenses are unpublished would read as a
|
||||
// company with no costs.
|
||||
//
|
||||
// Deliberately NOT called profit. On a cash basis this ignores stock
|
||||
// still on the shelf, anything owed in either direction, and tax not yet
|
||||
// paid, so calling it profit would be a claim the arithmetic cannot
|
||||
// support. FormatEuro renders a negative as "€-12.34", which is the
|
||||
// honest thing to show in a month that bought inventory.
|
||||
const std::int64_t netMinor =
|
||||
fin.donationsMinor + salesTotalMinor - fin.ExpensesMinor();
|
||||
SafeHtml netBlock;
|
||||
if (fin.Loaded()) {
|
||||
netBlock = Format(
|
||||
R"(<section class="section"><h2 class="section__title">Net</h2>)"
|
||||
R"(<table class="spec-table"><tbody>{}</tbody></table>)"
|
||||
R"(<p class="section__lede">On the same cash basis as everything above. )"
|
||||
R"(This is not profit: it counts no stock still on the shelf, nothing )"
|
||||
R"(owed in either direction, and no tax.</p>)"
|
||||
R"(</section>)",
|
||||
// The row says the arithmetic rather than repeating the heading —
|
||||
// a section titled "Net" whose only row is also "Net" reads as a
|
||||
// rendering fault, and the sum is worth spelling out anyway.
|
||||
totalRow("Income − expenses", netMinor));
|
||||
}
|
||||
|
||||
// The freshness line keeps the page honest about its two cadences.
|
||||
const SafeHtml freshness = fin.Loaded()
|
||||
? Format(R"(<p class="legal__updated">Sales are live from the order ledger · )"
|
||||
|
|
@ -1539,10 +1556,11 @@ export RenderedPage RenderFinancials(std::int64_t salesCount,
|
|||
R"(<p class="page-header__lede">{}</p>)"
|
||||
R"({})"
|
||||
R"(</header>)"
|
||||
R"(<div class="fin"{}{}{}{}{}>)"
|
||||
R"(<div class="fin"{}{}{}{}{}{}>)"
|
||||
R"(<section class="section"><h2 class="section__title">Income</h2>)"
|
||||
R"(<table class="spec-table"><tbody>{}</tbody></table></section>)"
|
||||
R"(<section class="section"><h2 class="section__title">Expenses</h2>{}</section>)"
|
||||
R"({})"
|
||||
R"(</div>)"
|
||||
R"(<div class="legal">{}</div>)",
|
||||
Escape(notes.title), Escape(notes.lede), freshness,
|
||||
|
|
@ -1554,7 +1572,8 @@ export RenderedPage RenderFinancials(std::int64_t salesCount,
|
|||
: SafeHtml{},
|
||||
fin.Loaded() ? Attr("data-fin-expenses-minor", std::to_string(fin.ExpensesMinor()))
|
||||
: SafeHtml{},
|
||||
Join(incomeRows), expenses, Join(sections));
|
||||
fin.Loaded() ? Attr("data-fin-net-minor", std::to_string(netMinor)) : SafeHtml{},
|
||||
Join(incomeRows), expenses, netBlock, Join(sections));
|
||||
return page;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue