This commit is contained in:
parent
70668af8f5
commit
e68d2c245c
17 changed files with 1801 additions and 15 deletions
|
|
@ -281,6 +281,67 @@ export Rates LoadRates(std::string_view json) {
|
|||
return out;
|
||||
}
|
||||
|
||||
// The bank-derived aggregates for the public /financials page, read from a
|
||||
// state file the owner's tooling writes (<orders>.financials.json on the
|
||||
// server). Aggregates by construction: a category is a label and a running
|
||||
// total, donations are a count and a running total, and nothing finer ever
|
||||
// exists in this structure — that is the page's privacy design, not an
|
||||
// implementation shortcut. Sales are not in here: they fold live out of the
|
||||
// order ledger on the server and arrive at the renderer as two integers.
|
||||
export struct FinCategory {
|
||||
std::string label; // "Hosting" — shown verbatim
|
||||
std::int64_t totalMinor = 0; // running total, EUR cents
|
||||
};
|
||||
|
||||
export struct Financials {
|
||||
std::string asOf; // ISO date the figures are current to;
|
||||
// empty = nothing published yet
|
||||
std::int64_t donationCount = 0;
|
||||
std::int64_t donationsMinor = 0;
|
||||
std::vector<FinCategory> recurring; // insurance, hosting, …
|
||||
std::vector<FinCategory> single; // inventory, fees, tax, …
|
||||
|
||||
bool Loaded() const { return !asOf.empty(); }
|
||||
std::int64_t ExpensesMinor() const {
|
||||
std::int64_t sum = 0;
|
||||
for (const FinCategory& c : recurring) sum += c.totalMinor;
|
||||
for (const FinCategory& c : single) sum += c.totalMinor;
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
export Financials LoadFinancials(std::string_view json) {
|
||||
Financials out;
|
||||
auto doc = Json::Parse(json);
|
||||
if (!doc || !doc->IsObject()) return out;
|
||||
out.asOf = std::string(doc->Str("as_of"));
|
||||
// Undated figures stay unpublished: the page promises an honest
|
||||
// freshness line, and numbers that cannot carry one are not shown.
|
||||
if (out.asOf.empty()) return out;
|
||||
if (const Json::Value* d = doc->Find("donations"); d && d->IsObject()) {
|
||||
out.donationCount = d->Int("count");
|
||||
out.donationsMinor = d->Int("total_minor");
|
||||
}
|
||||
auto categories = [](const Json::Value* arr) {
|
||||
std::vector<FinCategory> cats;
|
||||
if (!arr || !arr->IsArray()) return cats;
|
||||
for (const Json::Value& v : arr->array) {
|
||||
if (!v.IsObject()) continue;
|
||||
FinCategory c;
|
||||
c.label = std::string(v.Str("label"));
|
||||
c.totalMinor = v.Int("total_minor");
|
||||
// A category with no label has nothing to render as; dropping it
|
||||
// beats an anonymous row that looks like a redaction.
|
||||
if (c.label.empty()) continue;
|
||||
cats.push_back(std::move(c));
|
||||
}
|
||||
return cats;
|
||||
};
|
||||
out.recurring = categories(doc->Find("recurring"));
|
||||
out.single = categories(doc->Find("single"));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Everything the order status page needs to render — a projection of the
|
||||
// server's order record, not the record itself. The renderer stays a pure
|
||||
// function in Shared; the server owns storage and fills this in.
|
||||
|
|
|
|||
Loading…
Reference in a new issue