feat(accounting): incoming-invoices inbox with camera capture + triage/re-bill

Adds the Accounting → Incoming invoices frontend on top of the existing
/api/admin/expenses backend:

- accounting.service.ts: typed client (inbound upload/list/get/update/
  categorize, expense list, re-bill, supplier-payment, categories).
- AccountingInboxPage: capture a supplier invoice via the device CAMERA
  (<input accept="image/*" capture="environment">) or a PDF/image upload;
  inbox list with status badges + parsed summary; a triage modal to confirm
  fields and pick a disposition (re-bill / pass-through / company expense /
  duplicate / declined). Re-bill uses the customer picker and mints an
  editable scheduled invoice (chains categorize -> rebill).
- AccountingLayout: "Incoming invoices" sub-nav item + AccountingIndex that
  redirects /admin/accounting to the first enabled sub-feature.
- App.tsx: /admin/accounting/inbox route (gated by incomingInvoices).
- i18n: accounting.inbox/disposition/markup + subnav.incomingInvoices +
  common.saving (EN + DE, DE authored natively).

Camera capture needs no native app — the mobile web input drives the device
camera straight into the upload endpoint. OCR/QR auto-extraction is still a
backend follow-up (extractionService is a no-op), so fields are confirmed
manually in the triage modal for now.

Verified: npm run build green; en/de JSON valid.
This commit is contained in:
Luca
2026-06-11 00:31:05 +02:00
parent 2c351bf0c9
commit 2b5efebaff
6 changed files with 591 additions and 8 deletions
+48
View File
@@ -98,6 +98,7 @@
"error": "Fehler",
"configureInSettings": "Standards in den Einstellungen anpassen ↗",
"save": "Speichern",
"saving": "Speichern…",
"cancel": "Abbrechen",
"delete": "Löschen",
"edit": "Bearbeiten",
@@ -3398,7 +3399,54 @@
"body": "Aktiviere die Steuerliste (oder eine andere Buchhaltungs-Unterfunktion) unter Einstellungen → Funktionen, um loszulegen."
},
"subnav": {
"incomingInvoices": "Eingangsrechnungen",
"taxReport": "Steuer"
},
"disposition": {
"rebill": "An Kunde weiterverrechnen",
"durchlaufend": "Durchlaufender Posten",
"eigener_aufwand": "Eigener Aufwand",
"duplikat": "Duplikat",
"abgelehnt": "Abgelehnt"
},
"markup": {
"none": "Keiner / aus Vertrag",
"percent": "Prozent",
"flat": "Pauschal"
},
"inbox": {
"captureTitle": "Lieferantenrechnung erfassen",
"captureBody": "Fotografiere eine Papierrechnung mit der Gerätekamera oder lade ein PDF / Bild hoch.",
"scanCamera": "Mit Kamera scannen",
"uploadFile": "Datei hochladen",
"capturedToast": "Dokument erfasst.",
"categorizedToast": "Dokument kategorisiert.",
"triageTitle": "Dokument kategorisieren",
"saveCategorize": "Speichern",
"categorize": "Kategorisieren",
"empty": "Noch keine Dokumente — oben eines erfassen.",
"untitled": "Unbenanntes Dokument",
"noAmount": "Betrag nicht erfasst",
"rebillHint": "Erstellt eine bearbeitbare geplante Rechnung beim Kunden. MwSt-/Steuerbehandlung ist v1 — mit Treuhänder prüfen.",
"status": {
"unsorted": "Neu",
"categorized": "Kategorisiert",
"declined": "Abgelehnt",
"duplicate": "Duplikat"
},
"field": {
"supplier": "Lieferant",
"total": "Total",
"currency": "Währung",
"invoiceDate": "Rechnungsdatum",
"disposition": "Zuordnung",
"category": "Kategorie",
"categoryNone": "— keine —",
"declineReason": "Grund",
"customer": "Kunde",
"eventId": "Event-ID (optional)",
"markup": "Zuschlag"
}
}
},
"calendar": {
+48
View File
@@ -98,6 +98,7 @@
"error": "Error",
"configureInSettings": "Configure defaults in Settings ↗",
"save": "Save",
"saving": "Saving…",
"cancel": "Cancel",
"delete": "Delete",
"edit": "Edit",
@@ -3398,7 +3399,54 @@
"body": "Enable the Tax report (or another accounting sub-feature) under Settings → Features to get started."
},
"subnav": {
"incomingInvoices": "Incoming invoices",
"taxReport": "Tax"
},
"disposition": {
"rebill": "Re-bill to client",
"durchlaufend": "Pass-through",
"eigener_aufwand": "Company expense",
"duplikat": "Duplicate",
"abgelehnt": "Declined"
},
"markup": {
"none": "None / from contract",
"percent": "Percent",
"flat": "Flat"
},
"inbox": {
"captureTitle": "Capture a supplier invoice",
"captureBody": "Photograph a paper invoice with your device camera, or upload a PDF / image.",
"scanCamera": "Scan with camera",
"uploadFile": "Upload file",
"capturedToast": "Document captured.",
"categorizedToast": "Document categorized.",
"triageTitle": "Categorize document",
"saveCategorize": "Save",
"categorize": "Categorize",
"empty": "No documents yet — capture one above.",
"untitled": "Untitled document",
"noAmount": "amount not entered",
"rebillHint": "Creates an editable scheduled invoice on the client. VAT/tax handling is v1 — verify with your Treuhänder.",
"status": {
"unsorted": "New",
"categorized": "Categorized",
"declined": "Declined",
"duplicate": "Duplicate"
},
"field": {
"supplier": "Supplier",
"total": "Total",
"currency": "Currency",
"invoiceDate": "Invoice date",
"disposition": "Disposition",
"category": "Category",
"categoryNone": "— none —",
"declineReason": "Reason",
"customer": "Client",
"eventId": "Event ID (optional)",
"markup": "Markup"
}
}
},
"calendar": {