feat(accounting): split Incoming invoices vs Expenses - flags, schema, settings (stage 1)

Foundation for separating external supplier invoices from internal expenses,
per design review. This stage is additive + buildable; the service/route/UI
data rework follows in stage 2.

- Migration 126: incoming invoices own their payable on inbound_documents
  (supplier_paid/at/method/ref + disposition + tax_treatment + booking event_id
  + category_id + re-bill markup/linkage); expenses gain kind (amount/mileage/
  per_diem) + quantity + snapshotted rate_minor. Additive, hasColumn-guarded.
- Migration 127: seed `expenses` feature flag (default off) + accounting
  app_settings (accounting_km_rate_minor=70, accounting_per_diem_rate_minor=0,
  accounting_require_proof=false).
- Backend: `expenses` added to feature-flag known/defaults/dependency (forced
  off when the accounting master is off); new PUT /admin/settings/accounting
  (read via the generic GET /:type).
- Frontend: `expenses` flag (type + context + dependency); Features tab gets an
  Expenses sub-card; the Expenses sub-nav + route now gate on `expenses` (not
  incomingInvoices); AccountingIndex prefers inbox -> expenses -> tax.
- i18n: settings.features.expenses.* (EN + DE).

Verified: node -c; migration 124->126->127 harness (new columns, flag, settings
+ idempotency); en/de JSON valid; npm run build green.
This commit is contained in:
Luca
2026-06-11 12:24:53 +02:00
parent 413a200592
commit c59df52d40
11 changed files with 214 additions and 9 deletions
@@ -18,6 +18,7 @@ import {
Calculator,
Landmark,
ScanLine,
Wallet,
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Button, Card } from '../../../components/common';
@@ -331,6 +332,25 @@ export const FeaturesTab: React.FC = () => {
'Enable Accounting first — Incoming invoices live in the Accounting section.',
) : undefined}
/>
<FeatureCard
icon={Wallet}
title={t('settings.features.expenses.title', 'Expenses')}
description={t(
'settings.features.expenses.description',
'Internal expenses (mileage, per-diem, cash) booked to an event or the company, with optional proof. Separate from incoming supplier invoices. Configure km / per-diem rates and the proof requirement in the Accounting settings tab.',
)}
status="new"
statusLabel={statusLabel('new')}
sidebarLabel={t('settings.features.expenses.sidebar', 'Expenses')}
enabled={staged.expenses}
onToggle={(next) => setFlag('expenses', next)}
disabled={!staged.accounting}
lockedReason={!staged.accounting ? t(
'settings.features.expenses.requiresAccounting',
'Enable Accounting first — Expenses live in the Accounting section.',
) : undefined}
/>
</Section>
{/* Insights & Access */}