Commit Graph
2 Commits
Author SHA1 Message Date
Luca 315d15afd4 test(accounting): incoming-invoice integration test + fix vat_code reload & SQLite logActivity deadlock
- Add backend/__tests__/integration/incomingInvoiceRebill.test.js (8 tests):
  disposition state machine, per-event PENDING pool, passthrough-no-markup,
  unwindBilledLine recompute, INVOICE_LOCKED on an issued invoice, and
  re-categorisation transitions. The invoice-MINTING paths can't run inside an
  outer transaction on SQLite (createInvoice's sequence claim deadlocks on the
  held write lock) — covered by buildInboundLineItem unit tests + discountLineItems
  instead; documented in the test.

- Move logActivity out of the categorize/rebill/bundle transactions. It writes
  via the global db; inside a transaction a second write connection deadlocks on
  a SQLite-backed install (also affected SQLite-prod, not just tests).

- Fix bill-editor vat_code reload: transformInvoice (adminInvoices.js) dropped
  vatCode, so the editor fell back to rate-matching and lost a custom-rate code
  on edit. Now returns vatCode: i.vat_code.

- Rewrite docs/accounting-inbound-invoices.md to the current implementation
  (IR-vs-Expenses split, re-categorise + unwind, cadence-aware re-bill / pending
  pool, passthrough-at-cost, migrations 122-132, rasterised preview, tax/ledger/VAT).
2026-06-18 14:11:08 +02:00
Luca c305492845 feat(accounting): inbound supplier-invoice capture + expense re-bill (backend)
New top-level Accounting area (gated by an `accounting` feature flag, default
OFF, + accounting.view/manage permissions), separate from CRM. Lets an admin
capture a received supplier invoice (upload OR phone/tablet camera), give it a
disposition, and re-bill the cost to a client onto the relevant event's
invoice with a contract-driven markup. Mirrors the billable-hours model.

Backend foundation only — frontend pages (inbox / expenses UI + camera widget)
and the heavy extractors (Tesseract OCR / Swiss-QR decode / isolated rasterise
worker) are follow-ups; extractionService is scaffolded so the upload path is
already wired.

Migrations 122-125 (numbered above the in-flight feat/crm 117-121):
- 122 seed `accounting` flag (default OFF, idempotent)
- 123 seed accounting.view/manage permissions + grant super_admin/admin
- 124 inbound_documents + expenses + expense_categories (+ seed categories)
- 125 contracts Spesen-Zuschlag clause (expense_markup_type/_percent/_flat_minor)

API: /api/admin/expenses — inbound capture/list/confirm/categorize, expense
CRUD, /:id/rebill (event-scoped; markup = expense override -> contract clause
-> 0%; mints an editable scheduled invoice), /:id/supplier-payment, categories.
adminFeatureFlags KNOWN_FLAGS/DEFAULT_FLAGS gain `accounting`.

Conventions: idempotent hasTable/hasColumn-guarded migrations; money in integer
*_minor; QR amount stored separately + untrusted; requirePermission guards;
camelCase API <-> snake_case columns; multer + 15MB cap for PDF/JPEG/PNG.
VAT/tax handling is v1 capture-only — verify with a Treuhaender before relying.

Verified: node -c all files, require-graph smoke test, and a SQLite migration
harness (schema + seeds + idempotency + defaults assert green).
2026-06-11 00:04:16 +02:00