feat(accounting): unify tax report into one signed, typed, sortable ledger

Replaces the separate revenue + costs tables with a single ledger across the
screen, CSV and PDF. Every row is typed (outgoing invoice / incoming invoice /
expense) and signed — outgoing positive, incoming + expenses negative — so
sorting by value runs income → costs and the column nets toward the Result.

- getTaxReport now returns a `ledger` array (signed, typed, date-sorted);
  legacy rows/costs/summary kept for back-compat.
- Frontend: one sortable table (click Type/Date/Party/Net/VAT/Gross), coloured
  type badges, cancelled rows greyed with lineage badges; Income/Costs/Result
  summary box unchanged.
- CSV + PDF reworked to the same unified, signed layout; PDF totals show
  Income / Costs (negative) / Result.
- i18n: en/de (frontend) + pdf-i18n (en/de real; fr/nl/pt/ru English-fallback,
  flagged for native review).

Build + node --check + JSON parse green.
This commit is contained in:
Luca
2026-06-15 17:30:55 +02:00
parent ab65a470a0
commit fd1dd81e8d
6 changed files with 340 additions and 258 deletions
@@ -87,6 +87,30 @@ export interface TaxReportSummary {
vatPayableMinor: number;
}
/** A single row of the unified ledger (#5). Outgoing invoices carry
* POSITIVE amounts; incoming invoices + expenses are NEGATIVE so the
* amount columns net toward the Result and a value-sort runs
* income → costs. `type` is the discriminator. */
export interface TaxLedgerRow {
key: string;
type: 'outgoing' | 'incoming' | 'expense';
date: string;
reference: string;
party: string;
eventName: string;
vatRate: number | null;
taxTreatment: string | null;
status: string;
isCancelled: boolean;
isReissue: boolean;
kind: string | null;
skontoApplied: boolean;
skontoAmountMinor: number;
netMinor: number;
vatMinor: number;
totalMinor: number;
}
export interface TaxReport {
rows: TaxReportRow[];
totalsByVatRate: TaxReportBucket[];
@@ -101,6 +125,9 @@ export interface TaxReport {
costsError?: string | null;
/** Income/cost/result summary (#4). */
summary: TaxReportSummary;
/** Unified, signed, typed ledger (#5) — the canonical surface for the
* on-screen table + exports. Sorted by date ascending server-side. */
ledger: TaxLedgerRow[];
currency: string;
period: { from: string; to: string };
}