feat(accounting): frontend rework - separate Incoming invoices vs Expenses (stage 2)

Matches the backend split. Incoming invoices and Expenses are now distinct
surfaces with no shared rows.

Incoming invoices (AccountingInboxPage): triage sets the disposition + booking
(event or company) ON the document; "Mark paid" / "Paid" toggle records
supplier payment HERE with the outstanding total shown; re-bill via the
customer picker + markup. PDF preview still rasterised (last page = QR-bill).

Expenses (ExpensesLedgerPage): internal own-costs only. Add form has a Type
dropdown (amount / mileage(km) / per-diem); km/per-diem switch the input to a
quantity + rate (default from accounting settings, per-entry override) with a
live computed amount; optional proof upload (required when the setting says so);
localized category; booked to an event or the company. Proof viewable per row.

Service: reworked to the new endpoints/shapes; categoryLabel() localizes seed
categories (custom stay free-text). i18n: accounting.booking / incoming /
expense / expenseKind / category (EN + DE, DE native).

Verified: tsc --noEmit clean (0 errors); en/de JSON valid; npm run build green.
This commit is contained in:
Luca
2026-06-11 12:47:55 +02:00
parent 5e78fb6475
commit f305541f90
5 changed files with 475 additions and 559 deletions
+48 -1
View File
@@ -3475,10 +3475,57 @@
"card": "Karte",
"other": "Sonstiges"
},
"booking": {
"label": "Buchen auf",
"company": "Firma",
"event": "Event",
"eventId": "Event-ID"
},
"incoming": {
"triageTitle": "Eingangsrechnung kategorisieren",
"payTitle": "Lieferant als bezahlt markieren",
"outstanding": "Offen",
"markPaid": "Als bezahlt markieren",
"confirmPaid": "Als bezahlt markieren",
"paid": "Bezahlt",
"paidToast": "Als bezahlt markiert.",
"categorizedToast": "Kategorisiert."
},
"expense": {
"kind": "Art",
"km": "Kilometer",
"days": "Tage",
"rate": "Satz",
"rateDefault": "Standard {{rate}} — leer lassen, um ihn zu verwenden",
"computed": "Betrag",
"who": "Bezahlt von / Lieferant",
"whoHint": "z. B. Mitarbeitername oder Geschäft",
"proof": "Beleg",
"proofRequired": "Ein Beleg ist erforderlich.",
"viewProof": "Beleg"
},
"expenseKind": {
"amount": "Betrag",
"mileage": "Kilometer (km)",
"per_diem": "Spesenpauschale"
},
"category": {
"infrastructure": "Infrastruktur & Miete",
"equipment": "Equipment & Hardware",
"software": "Software & Lizenzen",
"material": "Material & Verbrauch",
"travel": "Reise & Spesen",
"marketing": "Werbung & Marketing",
"services": "Dienstleistungen/Fremdleistungen",
"insurance": "Versicherungen & Gebühren",
"training": "Weiterbildung",
"other": "Sonstiges"
},
"ledger": {
"allStatuses": "Alle Status",
"allDispositions": "Alle Zuordnungen",
"empty": "Noch keine Aufwände — Dokumente im Eingang kategorisieren.",
"allKinds": "Alle Arten",
"empty": "Noch keine Aufwände — oben einen hinzufügen.",
"untitled": "Aufwand",
"invoiceLink": "Rechnung",
"paid": "Bezahlt",
+48 -1
View File
@@ -3475,10 +3475,57 @@
"card": "Card",
"other": "Other"
},
"booking": {
"label": "Book to",
"company": "Company",
"event": "Event",
"eventId": "Event ID"
},
"incoming": {
"triageTitle": "Categorize incoming invoice",
"payTitle": "Mark supplier paid",
"outstanding": "Outstanding",
"markPaid": "Mark paid",
"confirmPaid": "Mark paid",
"paid": "Paid",
"paidToast": "Marked as paid.",
"categorizedToast": "Categorized."
},
"expense": {
"kind": "Type",
"km": "Kilometres",
"days": "Days",
"rate": "Rate",
"rateDefault": "Default {{rate}} — leave blank to use it",
"computed": "Amount",
"who": "Paid by / vendor",
"whoHint": "e.g. coworker name or shop",
"proof": "Proof",
"proofRequired": "A proof file is required.",
"viewProof": "Proof"
},
"expenseKind": {
"amount": "Amount",
"mileage": "Mileage (km)",
"per_diem": "Per-diem"
},
"category": {
"infrastructure": "Infrastructure & rent",
"equipment": "Equipment & hardware",
"software": "Software & licenses",
"material": "Materials & supplies",
"travel": "Travel & expenses",
"marketing": "Advertising & marketing",
"services": "Services / subcontracting",
"insurance": "Insurance & fees",
"training": "Training",
"other": "Other"
},
"ledger": {
"allStatuses": "All statuses",
"allDispositions": "All dispositions",
"empty": "No expenses yet — categorize documents in the inbox.",
"allKinds": "All types",
"empty": "No expenses yet — add one above.",
"untitled": "Expense",
"invoiceLink": "Invoice",
"paid": "Paid",