08-portals-and-i18nwave: W5filled15 citations

Accountant Portal — Financial Reporting

Audiences: clinical-buyer, developer

Accountant Portal — Financial Reporting

The clinic finance lens — invoices, payments, revenue, expenses, and reports — separated from the clinic-admin's operations view by RBAC, but sharing the same role-portal factory and BFF gateway.

Business Purpose

Healthcare invoicing is a separate craft from clinical care: payer mix reconciliation, statutory chart of accounts (Germany: SKR03/SKR04), VAT on non-medical line items, deferred revenue, payout reports for the practice's external bookkeeper. The accountant portal is the surface where the clinic's finance staff (or an outsourced Steuerberater) sees the financial state of the practice without seeing the patient record proper. It is the only portal where a non-clinician can routinely log in to a clinical product — the RBAC contract isolates them to ACCOUNTANT_ONLY routes.

For an investor narrative, this matters because it converts the HMS from a clinician tool into a back-office system of record — clinics that already have HMS-as-EMR can adopt HMS-as-finance-system instead of running parallel SaaS for billing. For the clinic buyer, it is the difference between "we charge €X" and "we collected €Y, here's the GAAP-aligned reconciliation".

Audiences

  • Investor: the existence of a portal that only sees money proves the HMS' tenancy model — a non-clinician can have full read on cents without any cleartext patient context. Same shell, hard boundary.
  • Clinical buyer (clinic admin): this is the seat to give a part-time accountant or an outsourced Steuerberater without granting access to charts. Auth scope is ACCOUNTANT_ONLY — no ADMIN_PLUS slip-through.
  • Developer/partner: the simplest non-clinical config — no needsBackendStatus, no extraElements, no taglineFormatter. Useful reference for new role portals that just need a sidebar + RBAC gate.
  • Internal (ops/support): dashboard data is stubbed (a TODO inside dashboard/page.tsx:20-21 notes billing endpoints are pending) — the shell ships ahead of the data wiring; track it through CR-31 / accounting rollout.

Architecture

Role config — the minimal form

ddx-web/src/lib/portal/role-configs.ts:29-39 is the canonical "simple portal" config:

typescript
export const ACCOUNTANT_CONFIG: RolePortalConfig = {
  roleKey: 'accountant',
  authRequirement: 'ACCOUNTANT_ONLY',
  roleBadge: { role: 'ACCOUNTANT', labelKey: 'roles.accountant',
               icon: CalculatorIcon, variant: 'default' },
  brandNameKey: 'portals.accountant',
};

No backend-status banner, no extra elements, no tagline formatter, no fetchOrg: false override — the factory at ddx-web/src/components/portal/createRoleLayout.tsx runs every default.

Layout — one-liner

ddx-web/src/app/[locale]/portal/accountant/layout.tsx:17 is two lines of substance:

typescript
function AccountantNav() { return <PortalNavigation roleKey="accountant" />; }
export default createRoleLayout(ACCOUNTANT_CONFIG, AccountantNav);

The sidebar is fully driven by the nav config; nothing about it is hand-coded per page.

ddx-web/src/lib/portal/nav-configs/accountant.ts:22-99 exports five sections: Overview (dashboard), Billing (invoices/payments/billing), Financial (revenue/expenses), Insights (reports), Other (messenger, settings). All icons are Phosphor-SSR (@phosphor-icons/react/ssraccountant.ts:8-18).

Routes

diagram
portal/accountant/
├── layout.tsx                  → ACCOUNTANT_ONLY shell
├── page.tsx                    → redirects to dashboard
├── dashboard/page.tsx          → AccountantDashboardClient + RBAC gate
├── accounting/
│   ├── page.tsx
│   ├── journal-entries/        → daybook entries
│   └── reports/                → GAAP-aligned reports
├── billing/
│   ├── page.tsx                → top-level billing summary
│   └── invoices/               → invoice list/detail
├── messages/ + messenger/      → comms (shared infra; see messenger topic)
├── loading.tsx, error.tsx, not-found.tsx

The portal does not expose expenses/, payments/, or revenue/ as standalone routes yet — the nav config references them, but the pages are deferred. Adding them is a createRoleLayout-compatible drop-in (one page.tsx + a translation key).

Tech Stack & Choices

LayerChoiceWhy
Auth gaterequireRoleAccess('ACCOUNTANT_ONLY', locale) (dashboard/page.tsx:18)Strict single role — never ACCOUNTANT_PLUS. Doctors must NOT see this portal accidentally.
ShellDefault createRoleLayout settingsThe simplest possible role layout — proves the factory works without overrides.
IconCalculatorIcon (Phosphor-SSR)One visual cue throughout the chrome.
Brand labeli18n key portals.accountantResolved via the default brand namespace (common).
NavSectioned (5 sections via NavSection[] )Section grouping matches German accounting workflow (Buchhaltung separate from Abrechnung).
DataStubbed dashboard pending billing endpointsdashboard/page.tsx:20-21 carries a TODO; the underlying billing API is the gate.

Rejected: making accountant inherit from ADMIN_PLUS (would over-grant — violates least privilege; an accountant must never see patient charts); embedding accounting inside clinic-admin (would scatter the same KPIs behind a role-detect flag, hurting the audit story).

Data Flow

  1. Accountant logs in → JWT has role: ACCOUNTANT + organizationId (+ typically NO fhirPractitionerId).
  2. Browser hits acm.dudoxx.com/en/portal/accountant.
  3. proxy.ts strips locale, resolves trigram → X-Clinic-ID: acme-clinic, confirms session.
  4. portal/accountant/page.tsx redirects to dashboard.
  5. accountant/layout.tsx runs createRoleLayout(ACCOUNTANT_CONFIG, AccountantNav) — RBAC = ACCOUNTANT_ONLY, org is fetched, shell mounts.
  6. dashboard/page.tsx re-asserts RBAC, currently stubs financial data, and renders <AccountantDashboardClient locale userName>.
  7. Future: backendGet<BillingStatsResponse>('billing/stats') will populate pending invoices, payments, revenue — once those endpoints land (see billing).

Implicated Code

  • ddx-web/src/lib/portal/role-configs.ts:29-39ACCOUNTANT_CONFIG (minimal form).
  • ddx-web/src/lib/portal/role-configs.ts:188 — registry entry.
  • ddx-web/src/app/[locale]/portal/accountant/layout.tsx:13-17 — layout + one-line nav delegate.
  • ddx-web/src/app/[locale]/portal/accountant/page.tsx:7-12 — root redirect to dashboard.
  • ddx-web/src/app/[locale]/portal/accountant/dashboard/page.tsx:14-29 — RBAC-gated server page; renders AccountantDashboardClient; lines 20-21 carry the open TODO for billing endpoints.
  • ddx-web/src/lib/portal/nav-configs/accountant.ts:22-99 — five-section sidebar config.
  • ddx-web/src/components/portal/accountant/AccountantDashboardClient.tsx — client-side dashboard scaffold (placeholder until billing API lands).

Operational Notes

  • ACCOUNTANT_ONLY is strict — a SUPER_ADMIN cannot view this portal as the accountant; impersonation must use the super-admin tools, not URL walking. RBAC is asserted twice: in the layout (factory) and in dashboard/page.tsx:18 — both required so a child page error boundary cannot leak.
  • Dashboard is intentionally stubbed — the TODO at dashboard/page.tsx:20-21 is tracked: do not "fix" by hand-rolling ad-hoc fetches; the billing page documents the proper endpoint rollout.
  • Nav paths reference deferred routespayments, revenue, expenses exist as nav entries but not as pages. Clicking renders the 404 boundary. Either add the page or remove the entry; do not leave half-wired nav items.
  • Brand label namespaceportals.accountant resolves under the default namespace; if you rename to a feature-specific namespace (e.g. accountant.brand.label) update both the config and all three locale JSON files (src/locales/{en,fr,de}/*.json) and run pnpm i18n:ci-check.
  • No needsBackendStatus — the accountant doesn't see a degraded-API banner. Financial reads on a degraded backend will simply fail; consider enabling needsBackendStatus: true, scope: 'children' when billing endpoints ship (mirroring receptionist's config).
  • Multi-Portal Architecture — the shared factory and registry this portal participates in.
  • Admin Portal — the operations counterpart; finance-adjacent clinic configuration lives there.
  • Billing — backend endpoints that will feed this dashboard.
  • Accounting — chart-of-accounts and journal entries surfaced through accounting/ routes.
  • i18n — Multi-Lingual Interface — translation namespaces consumed by the accountant nav config.