Data model
All types live in src/types/ and are re-exported from @/types.
Entities (entities.ts)
| Type | Notes |
|---|---|
Company, Branch, Department, Region, SalesChannel, Product |
Dimensions used for filtering and breakdowns |
Account |
code, name, type (asset/liability/equity/revenue/expense), optional subtype (bank, cogs, salaries…) used by statement grouping |
Customer |
segment, region, status (active/inactive/at_risk) |
Vendor |
category, paymentTermsDays |
Transaction |
One ledger line. Lines belonging to the same business event share a reference. Exactly one of debit/credit is non-zero |
Invoice, Bill |
amount, paid, dueDate, status |
Payment |
Links to an invoice or bill |
InventoryItem |
quantity, unitCost, unitsSold12m, lastMovementDate |
Money is a number in the reporting currency. There is no minor-unit
convention — send decimals.
Filters (filters.ts)
GlobalFilters carries the date preset, resolved range, comparison mode
and up to eight dimension filters (companyId, branchId, departmentId,
customerId, vendorId, accountId, currency, categoryId).
FilterParams extends it with granularity, search, page, pageSize,
sortBy, sortDir — this is what every adapter method receives.
Comparison modes: previous_period, previous_year, budget, forecast.
Metrics (metrics.ts)
| Type | Shape |
|---|---|
ComparedValue |
{ current, previous, change, changePercent } |
KpiMetric |
A ComparedValue plus key, label, description, format, sparkline, higherIsBetter |
TimeSeriesPoint |
{ date, label, value, previous?, budget? } |
BreakdownItem |
{ id, name, value, previous?, share, meta? } — meta carries the dimension values a drill-down should apply |
ReportLine |
Recursive statement line with level, isTotal, accountId, children |
AgingRow |
One open document with daysOverdue and bucket |
Paginated<T> |
{ items, total, page, pageSize } |
Calculation semantics
Implemented in src/lib/calculations/financial.ts, all pure and null-safe.
- Percentages are whole numbers.
14.8means 14.8%. Keep this convention or formatting will be off by two orders of magnitude. - Impossible ratios return
null, neverNaNorInfinity. Growth from a zero base isnull; the UI renders it as "n/a". - Growth uses the absolute base, so improving from −100 to −50 reads as +50% rather than −50%.
- Variance is actual minus budget. Positive means above plan, which is good
for revenue and bad for expenses — hence
higherIsBetteronBudgetLine. - Ageing buckets:
current,0-30,31-60,61-90,91-120,120+. Days overdue never goes negative. - Forecasting is least-squares regression over the trailing series with a
band that widens by 15% per step out. Replace it by returning your own
ForecastPoint[]from/budget.
The demo dataset
src/mock/dataset.ts generates a deterministic 25-month double-entry ledger from
a seeded PRNG — the same numbers on every machine and every reload.
- ~8,900 ledger lines across 31 accounts (scalable to 250,000 — see docs/PERFORMANCE.md)
- 24 customers, 17 vendors, 12 products, 5 branches, 6 departments
- 1,100+ invoices and 350+ bills, with realistic partial payments and overdue tails
- Payroll, depreciation, interest, loan repayments, capex and credit-line draws
- Seasonality (Q4 uplift), a growth trend, and deliberately planted expense anomalies for the detection panel
- Budgets for the last two fiscal years
Invariants the test suite enforces: the trial balance balances, the balance sheet reconciles, P&L net profit matches the profitability engine, cash flow closes to opening plus net movement, and receivables split cleanly into current and overdue.
src/mock/engine.ts computes every report from that ledger — it is a working
reference for what your backend needs to produce.