Tenant-admin UI för fakturering
En bref
The Tenant-admin UI for billing surfaces the entire billing layer in six admin views — billing profile, invoice list, invoice detail, payments, unmatched transfers and jobs admin — anchored by a central typed API client, status-coloured pill badges, multi-status fan-out filters and a platform-admin RBAC HOC that hides operator-only sections.
Comment ça fonctionne
All six views share admin/src/lib/billing-api.ts as the single typed client into /tenants/{id}/billing-profile, /invoices, /payments, /admin/bank/* and craft-easy-/jobs/*. It exposes typed entities (BillingProfile, Invoice, Payment, UnmatchedTransfer, JobRun), helper functions (formatMoney, invoiceStatusLabel, INVOICE_STATUSES), a multi-status fan-out helper that issues one request per selected status and de-duplicates per id, and ApiError pass-through for consistent error rendering. BillingProfileView edits the tenant singleton through GET and PUT /tenants/{id}/billing-profile: dunning stages render as an editable table with add, remove and reorder; languages as a chip multi-select with the default locked into the supported list; conditional late-fee fields per policy; a Preview button posts to /dunning-stages/preview for a dry run.
Client-side validators mirror the backend pydantic rules so users never round-trip a guaranteed-failure save. InvoiceList consumes /invoices/?status=... with multi-status chip filter (parallel fan-out plus de-dup when multiple statuses are picked), Load 50 more pagination, sortable columns (Number, Debtor, Issued, Due, Total, Status) and status-coloured pill badges. InvoiceDetail renders the full invoice: header with status badge, info cards (issuer, debtor, ECB reference rate, OCR), line items, a payments list (parallel GET /payments/?invoice_id=), the status_history timeline and conditional actions (PATCH /send, POST /send-reminder, PATCH /cancel) — the PDF link opens through Linking.openURL.
PaymentList consumes /payments/?status= with a radio filter (all, completed, pending, failed, refunded, cancelled), pagination, and row-click navigation to the parent invoice. UnmatchedTransfersView (platform-admin only) consumes /admin/bank/unmatched, supports an include-resolved toggle, and offers row actions through a modal: Match by invoice id (POST .../match?invoice_id=) or Ignore with mandatory reason (POST .../ignore), with badges UNRESOLVED, MATCHED, IGNORED. JobsAdmin (platform-admin only) renders a card grid of every job in /jobs/registry with History filter and Run now buttons, plus a body table of the latest 100 /jobs/runs entries (manual-trigger marker, runtime in seconds, status-coloured badge and 2-line error message).
Navigation adds a Fakturering sidebar section between Ekonomi and Innehall with five links — three tenant-scoped, two gated by a new requiresPlatformAdmin flag on NavItem — applied in both visibleSectionsFor() and the Cmd+K command palette. The requirePlatformAdmin HOC at admin/src/auth/platform-admin.tsx fetches GET /session/me and allows scope === platform_admin, role === platform_admin or is_system_user, otherwise rendering a 403 panel; usePlatformAdmin is the hook variant for non-blocking conditional UI such as the hidden sidebar links and command-palette indexing. Backend authority is independent — the HOC is purely cosmetic.
Capacités clés
- Central typed billing-api.ts client consolidating every billing endpoint and helpers like formatMoney
- BillingProfileView with editable dunning table, chip language multi-select and dry-run preview
- InvoiceList with multi-status chip filter (parallel fan-out plus id de-dup) and InvoiceDetail with timeline and conditional actions
- PaymentList with radio status filter and row-click navigation to the parent invoice
- UnmatchedTransfersView (platform-admin) with modal-based match-by-id and ignore-with-reason actions
- JobsAdmin (platform-admin) showing job registry with Run now and a runs-history table
- requirePlatformAdmin HOC and usePlatformAdmin hook for cosmetic gating, with backend always authoritative
En pratique
A federation treasurer logs into admin and clicks Fakturering > Fakturor in the new sidebar section. She filters by sent and overdue (the chip filter fans out two parallel calls and merges results), sorts by Due ascending and finds the oldest overdue invoice. Clicking opens InvoiceDetail; she sees the status-history timeline showing draft -> sent -> overdue, the ECB reference rate frozen at issue time, the empty payments list and a Send reminder action.
She hits it; POST /invoices/{id}/send-reminder fires, the timeline gains a new entry and the action becomes available again after the configured cooldown. Later a platform admin opens UnmatchedTransfersView, matches three Bankgirot lines to invoice ids using the modal and ignores one with reason Test transfer from bank ops; the queue updates and structured audit rows are written.
Fonctionnalités de ce sous-système
10| ID | Status | Fonctionnalités |
|---|---|---|
| F08.16.01 | Livré | Central API-klient — admin/src/lib/billing-api.ts centraliserar alla anrop mot /tenants/{id}/billing-profile, /invoices, /payments, /admin/bank/* och craft-easy-/jobs/*; exponerar typer (BillingProfile, Invoice, Payment, UnmatchedTransfer, JobRun), hjälpfunktioner (formatMoney, invoiceStatusLabel, INVOICE_STATUSES), multi-status fan-out för listfiltret, och ApiError-genomsläpp. ✅ PL-T096 |
| F08.16.02 | Livré | BillingProfileView — editerar tenantens singleton-profil via GET/PUT /tenants/{id}/billing-profile. Dunning-stegar som editerbar tabell (add/remove/reorder, auto-sortering på save), språk som chip multi-select (default låst i supported-listan), villkorliga dröjsmålsfält per policy, POST .../dunning-stages/preview för dry-run. Client-side validering speglar backend-pydantic. ✅ PL-T096 |
| F08.16.03 | Livré | InvoiceList — GET /invoices/?status=... med multi-status-chip-filter (parallel fan-out + de-dup per id när flera valts), "Ladda fler 50"-paginering, sorterbara kolumner (Nummer, Debitor, Utfärdad, Förfaller, Summa, Status), statusfärgade pill-badges. ✅ PL-T096 |
| F08.16.04 | Livré | InvoiceDetail — full faktura-detalj: header med status-badge, info-kort (issuer, debitor, ECB-referenskurs, OCR), rader, betalningslista (parallell GET /payments/?invoice_id=), status_history-tidslinje, villkorliga actions (PATCH /send, POST /send-reminder, PATCH /cancel), PDF-länk via Linking.openURL. ✅ PL-T096 |
| F08.16.05 | Livré | PaymentList — GET /payments/?status= med radiofilter (alla/avslutade/pågående/misslyckade/återbetalda/avbrutna), paginering, row-click öppnar faktura. Kolumner: Betald, Belopp, Metod, Referens, Faktura (last-8), Status. ✅ PL-T096 |
| F08.16.06 | Livré | UnmatchedTransfersView (platform-admin) — GET /admin/bank/unmatched, include-resolved-toggle, row-actions via modal (Matcha via faktura-ID + POST .../match?invoice_id=...; Ignorera med motivering + POST .../ignore). Status-badges OLÖST/MATCHAD/IGNORERAD. ✅ PL-T096 |
| F08.16.07 | Livré | JobsAdmin (platform-admin) — kort-grid med alla /jobs/registry-jobb (Historik-filter + Kör nu), body-table med senaste 100 /jobs/runs-poster (manuellt-triggad-markör, tid i sekunder, status-färgad badge, felmeddelande 2 rader). POST /jobs/run/{name} med tom parameter-body. ✅ PL-T096 |
| F08.16.08 | Livré | Navigation — ny sidomenysektion "Fakturering" mellan Ekonomi och Innehåll med 5 länkar (3 tenant-scopade, 2 platform-admin-gatade via nytt requiresPlatformAdmin-flag i NavItem). Filtret appliceras både i visibleSectionsFor() och i ⌘K command palette. ✅ PL-T096 |
| F08.16.09 | Livré | requirePlatformAdmin-HOC — admin/src/auth/platform-admin.tsx: hämtar GET /session/me, tillåter scope === "platform_admin", role === "platform_admin" eller is_system_user, renderar annars 403-panel. Används av /billing/unmatched-transfers och /platform/jobs. Bara kosmetisk — backend enforceras oberoende. ✅ PL-T096 |
| F08.16.10 | Livré | usePlatformAdmin-hook — hook-variant för icke-blockerande villkorlig UI (sidomenyns dolda länkar, command palette-index). Läser samma /session/me-resultat. ✅ PL-T096 |