Nexus Docs Documentation for the Nexus hospital management system

Guides

Finance

21 tabs across 8 enterprise sections — receivables, payables, banking, and reporting.

The Finance Manager logs in at finance.manager@hospital.com / admin123 (or any user with role accountant) and lands on a stripped kiosk shell pinned to the /finance page. There is no sidebar — the Finance module is the entire surface — and the top bar shows live KPIs (Net MTD, AR, AP, Bank Balance, Period status). Notifications and the user pill are kept; everything clinical and HR is hidden.

The kiosk groups 21 tabs into 8 enterprise sections using a two-tier nav:

SectionTabs
OverviewDashboard
ReceivablesCashier & Billing · AR Aging · Insurance Claims
PayablesVendors & POs · AP Aging
LedgerGeneral Accounts · Chart of Accounts · Journal Entries · Period Close
BankingBank Accounts · Reconciliation · Petty Cash
Inventory & AssetsGeneral Inventory · Asset Management
Cost & BudgetBudgets · Cost Centers · Tax Codes
ReportsBalance Sheet · Cash Flow

This guide is split into two parts:

  • Part A — Business is for the Finance Manager who logs in. Day-flow, what each tab is for, common pitfalls, and how the kiosk surfaces alerts.
  • Part B — Technical is for engineers wiring or extending Finance: schema, RBAC, routes, posting rules.

Part A — Business

What you own end-to-end

You are responsible for everything financial in the hospital — money in, money out, the books that prove it, and the controls that keep regulators and auditors happy.

DomainWhat you do
ConfigureOne-time setup that drives every other tab — fiscal year, base currency + secondary currencies + FX rates, tax registration / VAT %, document numbering rules (INV-, JV-, PO-, etc.), the default-accounts GL map that decides which account is debited / credited when other modules auto-post, approval thresholds, payment terms.
Receivables (money in)Patient cashier (bills, payments, statements), insurance claims & EOBs & prior-auth, AR aging & collections triage.
Payables (money out)Vendor management, purchase orders → goods receipts → invoices → payments, AP aging & vendor payment scheduling.
General LedgerChart of accounts (5-class GAAP structure), double-entry journal entries (draft → post), period close to lock the books. Most journals are now auto-posted from the cashier flow using the GL map.
BankingOperating / payroll / escrow bank accounts, deposits + withdrawals + transfers + fees + interest, monthly reconciliation, petty-cash log.
Inventory & AssetsGeneral medical supplies inventory (with expiry & reorder alerts), fixed-asset register with depreciation, asset transfers between departments.
Cost controlDepartmental budgets vs actual with variance %, cost centers as a dimensional tag for journal lines, tax codes (VAT / withholding / excise).
ReportingReal-time P&L (via General Accounts dept-finances), Balance Sheet snapshot, Cash Flow window, vendor spend, AR/AP aging.

Screen layout

┌────────────────────────────────────────────────────────────────────┐
│ ▢ Mando · Finance     Finance      🔔  Rania (Finance) ⏻          │  ← top bar (kiosk)
├────────────────────────────────────────────────────────────────────┤
│ Net MTD $84,210 · AR $217,430 · AP $46,890 · Bank $1.2M · Period  │
│ open                                          [+ <Tab Action>]    │
├────────────────────────────────────────────────────────────────────┤
│ ⚠ $46,210 overdue AR (>30 days). Open AR Aging for the bill list. │
│ ⚠ 14 unreconciled bank transactions.                              │
├────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Overview  Receivables 12  Payables  Ledger 3  Banking 14     │ │
│ │ ──────                                                        │ │
│ │ Inventory & Assets  Cost & Budget  Reports  Configuration    │ │
│ ├──────────────────────────────────────────────────────────────┤ │
│ │ Dashboard                                                    │ │
│ └──────────────────────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────────────────────┤
│                    ─── current tab content ───                    │
└────────────────────────────────────────────────────────────────────┘

The badges on Tier 1 (Receivables 12, Banking 14) are roll-up counts of the alert items inside the section. Open the section, the Tier 2 sub-nav reveals which specific tab the badge came from.

Day flow (typical)

  1. Open with the Dashboard. 12 stat cards across the top tell you whether you’re profitable this month, where your cash sits, and what’s bleeding past 30 days. Two horizontal bars show revenue and expense by category — a quick visual check that the categories you expect are dominating the month.
  2. AR triage. Open Receivables → AR Aging. The top strip shows 4 buckets (0-30 / 31-60 / 61-90 / 90+); the table below sorts by days-open so the oldest balance is on top. Anything in 90+ needs an outbound call today.
  3. AP review. Open Payables → AP Aging. Same shape. Pay 90+ first to avoid late fees and supplier holds; schedule 31-60 for the next payment run.
  4. Banking sweep. Banking → Reconciliation shows every bank transaction grouped by account. Match each one to a journal entry (or post a new one) so the books stay in sync with the bank statements.
  5. Petty cash. Banking → Petty Cash — approve any pending requests, post to GL once spent.
  6. Journal entries. Ledger → Journal Entries — the kiosk’s Post button validates debits = credits and refuses if the period is closed. Use the modal’s running totals to balance before submitting.
  7. Month-end (typically days 1-5 of the next month):
    • Check all draft journal entries are posted or deleted (Ledger → Journal Entries).
    • Reconcile every bank account (Banking → Reconciliation).
    • Review Cost & Budget → Budgets for variance > 10% — flag departments that overran.
    • Open Reports → Balance Sheet and verify Assets = Liabilities + Equity (the kiosk shows a green ✓ banner if balanced).
    • Ledger → Period Close → Close Period once everything ties out. After close, no posting into that period unless you re-open.

Each tab, in one paragraph

  • Dashboard — KPIs (Revenue/Expense MTD/YTD, Net Income, Bank Balance, AR/AP, Overdue, Unreconciled Tx, Period Status), revenue and expense bars by category. One-screen overview.
  • Cashier & Billing — patient bills with line items, partial payments, statement generation, multi-department charges. Reused existing page. Creating a bill now auto-posts a journal (DR AR / CR Revenue) using the default-accounts GL map; recording a payment auto-posts another (DR Cash / CR AR). Both go straight to the Journal Entries tab as posted_by='auto'.
  • AR Aging — every open patient bill bucketed by days outstanding, sorted by oldest. Days column is colour-coded (green → red).
  • Insurance Claims — claims, EOBs, prior authorizations, submit/adjudicate workflow. Reused existing page.
  • Vendors & POs — vendor master, vendor offers, purchase orders, goods receipts, vendor invoices, payments, contracts. Reused existing page. PO numbers are now auto-allocated from the purchase_order document sequence (e.g. PO-2026-00042) when the form is left blank.
  • AP Aging — open vendor invoices bucketed by days overdue against the due date.
  • General Accounts — revenue/expense/salary/transfer ledger entries with P&L roll-up and dept finances. Reused existing page.
  • Chart of Accounts — 5-class structure (asset / liability / equity / revenue / expense) with hierarchy via parent_account_id. Seeded with a standard hospital CoA on first boot.
  • Journal Entries — double-entry bookkeeping. Drafts are editable; posting validates debits = credits, blocks if period is closed, and updates chart_of_accounts.current_balance immediately. Entry numbers are now allocated from the journal sequence (e.g. JV-2026-000123) — you’ll see auto-posted entries from the cashier flow stamped posted_by='auto'.
  • Period Close — month / quarter / year lock. Drafts in the period must be posted or deleted before close.
  • Bank Accounts — operating, payroll, escrow accounts. Each has opening balance + running current_balance updated by transactions.
  • Reconciliation — bank transactions with reconciled toggle. Filter by account, by reconciled status. Match individual transactions to journal entries.
  • Petty Cash — small disbursements outside the AP workflow. Pending → approved → posted lifecycle.
  • General Inventory — medical supplies with stock_qty, reorder_level, expiry_date, material requests. Reused existing page.
  • Asset Management — fixed-asset register with depreciation, transfers, status. Reused existing page.
  • Budgets — period-bound (YYYY-MM) per (department, category, GL account). Actual computed live from account_entries; variance % bar colour shifts at 80% and 100% used.
  • Cost Centers — dimensional tag for journal lines and budgets. Typically dept-level.
  • Tax Codes — VAT / withholding / excise / sales / custom. Rate stored as decimal (0.05 = 5%).
  • Balance Sheet — snapshot of all chart_of_accounts.current_balance grouped into Assets / Liabilities / Equity. Banner shows ✓ when balanced.
  • Cash Flow — bank-tx-derived inflows and outflows over a window. Defaults to last 30 days.
  • Company Setup (under the Configuration section) — the one-time setup tab. Sub-sections in the left rail: Company Identity, Fiscal Year & Tax, Currencies, Exchange Rates, Document Numbering, Default Accounts, Approval Matrix, Payment Terms. See the next subsection for what each is for.

Configuration — the one-time setup tab

Configuration is where you tell the system how this company keeps its books: when the fiscal year starts, what currency the books are in, how invoice / receipt / journal numbers are formatted, and — crucially — which GL account is used by default when the cashier or purchasing modules auto-post a journal. You set these once at go-live, revise occasionally, and the rest of the kiosk reads from them. Two of these (Company Identity, Base Currency / Fiscal anchors) are shared with HR.

Sub-sectionWhat it controlsWhen you touch it
Company IdentityLegal name, trade name, commercial registration #, employer #, logo, address, phone/email/website. Shared with HR.At go-live. Edit if the company rebrands or changes registration.
Fiscal Year & TaxFiscal-year start month, period type (monthly/quarterly/yearly), period auto-lock days (after period end), base currency, tax ID, VAT number, default VAT %.At go-live. Edit if the fiscal year shifts or VAT registration changes.
CurrenciesCurrency master: code, name, symbol, decimals, active flag. Seeded with USD, EUR, GBP, SAR, AED, EGP, JOD.Add when you start trading in a new currency.
Exchange RatesFX rates per (from, to, effective_date) with source (manual / central bank / xe / vendor). Multiple rates over time form a history.Update at the cadence your auditor requires (daily for trading, monthly for reporting).
Document NumberingSequences per doc type (invoice, receipt, payment, journal, purchase_order, goods_receipt, vendor_bill, credit_note, debit_note). Prefix, current number, padding (digits), reset rule (never / yearly / monthly). Seeded with sensible defaults.At go-live. Edit if the auditor wants a different prefix or padding.
Default AccountsThe GL map — single source of truth for which account is debited / credited when other modules auto-post. Slots: Cash, Bank, AR, AP, Revenue, COGS, Inventory, Fixed Assets, Accumulated Depreciation, VAT Payable, VAT Receivable, Payroll Expense, Salaries Payable, Retained Earnings.At go-live (auto-mapped to the seeded chart of accounts on first boot). Re-map whenever you reorganise the chart.
Approval MatrixPer-doc-type rules: amount range × required role. E.g. “PO between $5,000 and $50,000 needs manager; above $50,000 needs cfo.”At go-live. Adjust when delegation policy changes.
Payment TermsCatalog of Net-X terms: code, name, days, optional discount % within X days. Seeded with COD / Net 15 / Net 30 / Net 45 / Net 60 / Net 90.Edit when contract terms change with a major customer / vendor.

The Default Accounts sub-section is the most operationally important one — every auto-post the system performs (cashier bill creation, cashier payment, future payroll-export and asset-depreciation jobs) reads from this map. If a slot is left blank, the auto-post for that flow is skipped silently (so the operational flow is never blocked) but the books stop staying in sync. On a fresh DB the seeded chart of accounts is auto-mapped to every slot so it works out of the box; you only need to remap if you renumber the chart.

Notification triggers

  • Overdue AR > $0 → red banner above the tab list (link to AR Aging).
  • Overdue AP > $0 → amber banner.
  • Unreconciled Tx > 5 → amber banner.
  • Open journal entries → counter on the Journal Entries tab.
  • Pending petty cash → counter on the Petty Cash tab.
  • Period closed → CLOSED indicator in top bar.

Common pitfalls

  • Posting an unbalanced journal. The kiosk blocks this server-side, but the modal’s running-totals indicator is the easier way to spot it. If “Difference” isn’t $0.00, the Save & Post button is disabled.
  • Posting into a closed period. Server returns 403 with period XXXX-XX is closed. Either re-open the period (audit trail!) or backdate the entry to a still-open period.
  • Forgetting reconciliation before period close. Closing while bank accounts have unreconciled transactions is allowed but bad practice — your Balance Sheet’s bank totals won’t match the bank statements. The Banking section’s tab badge shows unreconciled count for this reason.
  • Editing the chart of accounts in the middle of a month. Renaming an account is fine; deleting one with posted journal lines breaks history. Archive instead of delete.
  • Treating the embedded pages as separate apps. General Accounts, Cashier, Inventory, Assets, Purchasing, Claims are the same components admins use — they just render inside the kiosk shell. Any change you make there is visible to admins too.

Part B — Technical

Identity & RBAC

Backend role string: accountant (middleware.RoleAccountant).

Path-based RBAC in backend/internal/middleware/roles.go extends the existing accountant block to add the new finance resource and tightens the legacy general-inventory from read-only to full access for the kiosk:

RoleAccountant: {
    "patients":         {"GET"},
    "appointments":     {"GET"},
    "cashier":          {"GET", "POST", "PUT", "DELETE"},
    "claims":           {"GET", "POST", "PUT", "DELETE"},
    "accounts":         {"GET", "POST", "PUT", "DELETE"},
    "bills":            {"GET", "POST", "PUT", "DELETE"},
    "finance":          {"GET", "POST", "PUT", "DELETE"},   // NEW: deep finance
    "inventory":        {"GET", "POST", "PUT", "DELETE"},
    "general-inventory":{"GET", "POST", "PUT", "DELETE"},
    "assets":           {"GET", "POST", "PUT", "DELETE"},
    "purchasing":       {"GET", "POST", "PUT", "DELETE"},
    "vendors":          {"GET", "POST", "PUT", "DELETE"},
    "vendor-offers":    {"GET", "POST", "PUT", "DELETE"},
    "purchase-orders":  {"GET", "POST", "PUT", "DELETE"},
    "purchase-invoices":{"GET", "POST", "PUT", "DELETE"},
    "goods-receipts":   {"GET", "POST", "PUT", "DELETE"},
    "vendor-contracts": {"GET", "POST", "PUT", "DELETE"},
    "eob":              {"GET", "POST", "PUT", "DELETE"},
    "prior-auth":       {"GET", "POST", "PUT", "DELETE"},
    // …plus the read-only patient context routes
}

Kiosk shell wiring

Three lines in frontend/src/components/app-root.js:

// 1. WARD_CONFIG entry — informational depts list helps admins typed
//    as "Finance" or "Accounting" still resolve correctly.
finance: {
  route: 'finance',
  brand: 'Mando · Finance',
  tabLabel: 'Finance',
  depts: ['finance', 'accounting', 'accounts'],
}

// 2. wardForUser short-circuits on role==='accountant'.
if (user.role === 'accountant') return 'finance';

// 3. renderWardPage routes to the new <finance-page>.
case 'finance': return html`<finance-page></finance-page>`;

Same role-based pattern as HR — users.department spelling doesn’t affect the kiosk.

Seed user & seeded data

ensureFinanceManager() in database.go creates finance.manager@hospital.com (role accountant, dept Finance) on every boot — ON CONFLICT (email) DO NOTHING.

ensureChartOfAccountsSeed() populates a 33-row standard hospital CoA on first boot only (idempotent — checks COUNT(*) FROM chart_of_accounts). Codes follow GAAP convention:

  • 1xxx Assets (Cash, Bank, AR, Inventory, Fixed Assets)
  • 2xxx Liabilities (AP, Salaries Payable, VAT Payable, Loans)
  • 3xxx Equity (Owner’s Equity, Retained Earnings)
  • 4xxx Revenue (Patient Service Revenue and sub-categories)
  • 5xxx Expenses (Salaries, Medical Supplies, Pharmacy COGS, Utilities, Rent, Maintenance, Depreciation, Insurance, Professional Fees)

Database (deep-finance additions)

TablePurposeKey columns
chart_of_accountsGL spineaccount_code (UNIQUE), account_name, account_type (asset/liability/equity/revenue/expense), parent_account_id, currency, opening_balance, current_balance, status
journal_entriesGL headerentry_no (UNIQUE), entry_date, period (YYYY-MM), description, reference, status (draft/posted/void), total_debit, total_credit, posted_at, posted_by
journal_linesGL linesentry_id, account_id, description, debit, credit, cost_center_code
bank_accountsOperating/payroll/escrowname, bank_name, account_no, iban, swift, currency, opening_balance, current_balance, gl_account_id, status
bank_transactionsDeposits/withdrawals/feesbank_account_id, tx_date, tx_type (deposit/withdrawal/transfer/fee/interest), amount, description, reference, counterparty, reconciled, matched_entry_id
budgetsPeriod budgetsperiod (YYYY-MM), department, category, account_id, budget_amount
cost_centersDimensional tagcode (UNIQUE), name, department, manager, parent_code, status
tax_codesVAT/withholding/excisecode (UNIQUE), name, tax_type, rate (decimal), account_id, status
period_closeMonth-end lockperiod (UNIQUE), period_type, status (open/closed/reopened), closed_at, closed_by, reopened_at, reopened_by
petty_cashSmall disbursementstx_date, payee, category, amount, purpose, receipt_no, approved_by, status (pending/approved/posted/rejected), department

Plus 11 covering indexes on the most common kiosk reads (period+status, account, reconciled flag, etc.).

Company-configuration tables (shared with HR)

TablePurposeKey columns
company_settingsSingleton (id=1). Identity + fiscal anchors + workforce defaults. Same row HR reads.legal_name, trade_name, commercial_reg_no, tax_id, vat_number, default_vat_rate, fiscal_year_start_month, period_type, base_currency, period_lock_days
currenciesCurrency mastercode (UNIQUE), name, symbol, decimals, active
exchange_ratesFX historyfrom_currency, to_currency, rate, effective_date, source
document_sequencesNumbering rulesdoc_type (UNIQUE), prefix, current_number, padding, reset_rule (never/yearly/monthly), last_reset, active
default_accountsSingleton (id=1). The GL map used by every auto-post.cash_account_id, bank_account_id, ar_account_id, ap_account_id, revenue_account_id, cogs_account_id, inventory_account_id, fixed_asset_account_id, accumulated_dep_account_id, vat_payable_account_id, vat_receivable_account_id, payroll_expense_account_id, payroll_payable_account_id, retained_earnings_account_id
approval_thresholdsPer-doc-type role gatesdoc_type, min_amount, max_amount, role_required, active
payment_termsNet-X catalogcode (UNIQUE), name, days, discount_percent, discount_days, active

Seed functions in database.go:

  • ensureCompanySettings() — inserts the singleton (id=1) on first boot.
  • ensureCurrenciesSeed() — seeds USD / EUR / GBP / SAR / AED / EGP / JOD.
  • ensureDocumentSequencesSeed() — seeds 9 doc types (invoice / receipt / payment / journal / purchase_order / goods_receipt / vendor_bill / credit_note / debit_note), all yearly-reset.
  • ensurePaymentTermsSeed() — COD / Net 15/30/45/60/90.
  • ensureDefaultAccountsSeed() — auto-maps every slot to the seeded chart-of-accounts code (1000 → cash, 1100 → AR, 4000 → revenue, etc.) so cashier auto-posts work out of the box.

Auto-numbering & auto-posting (server-side)

Two helpers expose the configuration to other modules:

  • AllocateDocNumber(docType) in handlers/config_finance.go — bumps document_sequences.current_number atomically, applies yearly reset if configured, and returns the formatted string (e.g. JV-2026-000123). Called by CreateJournalEntry and CreatePurchaseOrder when the caller leaves the number blank.
  • PostBillAutoJournal(billID, total, date, description) and PostPaymentAutoJournal(billID, amount, date) in handlers/gl_autopost.go — read the singleton default_accounts GL map and call postBalancedJournal() to insert a posted journal_entries row + two journal_lines (one DR, one CR). The helper:
    1. Bails out silently if the GL map is unconfigured (d == nil or the relevant slot is nil).
    2. Refuses to post into a closed period (logs and skips — never blocks the operational flow).
    3. Allocates the JE number via AllocateDocNumber("journal").
    4. Stamps posted_by='auto' so a human can tell auto-posts from manual ones.
    5. Calls the existing applyJournalToBalances(entryID) so chart_of_accounts.current_balance updates immediately.

CreateBill and PayBill in handlers/billing.go call these helpers after their existing work — the legacy account_entries insert is kept so the General Accounts tab keeps showing the same rows it always has.

Posting rules (server-enforced)

POST /api/finance/journal-entries and POST /api/finance/journal-entries/{id}/post:

  1. Balancedtotal_debit == total_credit (within 0.005 tolerance). Server recomputes from lines defensively before checking.
  2. Period openperiod_close.status for the entry’s period must not be 'closed'. 403 if it is.
  3. Idempotent post — already-posted entries return 400.
  4. Balance application — once posted, applyJournalToBalances(entryID) walks each line and updates chart_of_accounts.current_balance:
    • Assets / Expenses: += debit - credit
    • Liabilities / Equity / Revenue: += credit - debit
  5. Delete protection — only drafts can be deleted. Posted entries return 403 (void via a reversing entry instead).

POST /api/finance/period-close/close refuses if any draft journal entries remain in the period; tells the user how many.

Bank-balance tracking

CreateBankTransaction updates bank_accounts.current_balance atomically:

  • deposit / interest+= amount
  • withdrawal / fee / transfer-= amount

DeleteBankTransaction reverses the same delta before deleting the row, so the running balance stays accurate even when entries are corrected.

API surface

All under /api/finance/*, mounted in cmd/server/main.go after the HR block:

GET  /api/finance/dashboard                   ← aggregated KPIs
GET  /api/finance/ar-aging                    ← AR buckets + bills
GET  /api/finance/ap-aging                    ← AP buckets + invoices
GET  /api/finance/balance-sheet?as_of=         ← snapshot
GET  /api/finance/cash-flow?from=&to=          ← bank-tx derived

GET/POST/PUT/DELETE /api/finance/chart-of-accounts
GET/POST/DELETE     /api/finance/journal-entries
GET                 /api/finance/journal-entries/{id}    ← header + lines
POST                /api/finance/journal-entries/{id}/post ← validate + post

GET/POST/PUT/DELETE /api/finance/bank-accounts
GET/POST/DELETE     /api/finance/bank-transactions
POST                /api/finance/bank-transactions/{id}/reconcile

GET/POST/PUT/DELETE /api/finance/budgets
GET/POST/PUT/DELETE /api/finance/cost-centers
GET/POST/PUT/DELETE /api/finance/tax-codes

GET                 /api/finance/period-close
POST                /api/finance/period-close/close
POST                /api/finance/period-close/reopen

GET/POST/PUT/DELETE /api/finance/petty-cash

Plus the shared /api/config/* block (Finance owns these slots; HR owns the HR slots from the same prefix):

GET/PUT             /api/config/company             ← singleton, shared with HR
GET/POST/PUT/DELETE /api/config/currencies
GET/POST/DELETE     /api/config/exchange-rates
GET/POST/PUT/DELETE /api/config/document-sequences
POST                /api/config/document-sequences/{docType}/allocate  ← bump + format
GET/PUT             /api/config/default-accounts    ← singleton GL map
GET/POST/PUT/DELETE /api/config/approval-thresholds
GET/POST/PUT/DELETE /api/config/payment-terms

Existing finance routes (/api/accounts/*, /api/bills/*, /api/inventory/*, /api/assets/*, /api/vendors/*, /api/purchase-orders/*, /api/claims/*, etc.) are unchanged and reachable through the embedded existing pages.

Handlers in backend/internal/handlers/finance_extended.go. Patterns:

  • List handlers do LEFT JOIN-friendly queries so the frontend can render parent / account / vendor names without round trips.
  • Days-open / days-overdue is computed in Go off NOW() — no SQL date arithmetic that varies between Postgres and SQLite during tests.
  • AR/AP aging buckets are produced by a single Go pass over the rowset; bucket totals + bill list returned together.

Configuration panel (shared component)

Configuration is rendered by configuration-panel.js, the same Lit component the HR kiosk embeds. The Finance kiosk passes mode="finance":

case 'config': return html`<configuration-panel mode="finance"></configuration-panel>`;

The component reads mode and shows only the Finance sub-sections (Company Identity, Fiscal & Tax, Currencies, Exchange Rates, Document Numbering, Default Accounts, Approval Matrix, Payment Terms). Per-mode accent colour (--accent, --accent-tint) is green for Finance.

Default-accounts editing is a special case: each of the 14 slots is a <select> populated from /api/finance/chart-of-accounts, optionally filtered by account type (e.g. Default Cash lists only assets, AP lists only liabilities). Saving writes the singleton row.

Frontend page structure

frontend/src/pages/finance-page.js is one page with 22 tabs grouped into 9 sections (Overview / Receivables / Payables / Ledger / Banking / Inventory & Assets / Cost & Budget / Reports / Configuration).

For the 6 reused tabs (Cashier / Claims / Purchasing / Accounts / Inventory / Assets) we render the existing custom-element directly inside an .embed wrapper:

case 'cashier':    return html`<div class="embed"><cashier-page></cashier-page></div>`;
case 'accounts':   return html`<div class="embed"><accounts-page></accounts-page></div>`;
// …etc.

These pages have their own purple banners which look fine stacked under the kiosk’s green Finance banner.

For the 15 new tabs, the page renders inline (Chart of Accounts, Journal Entries, Period Close, Bank Accounts, Reconciliation, Petty Cash, Budgets, Cost Centers, Tax Codes, AR Aging, AP Aging, Balance Sheet, Cash Flow, Dashboard).

The two-tier nav uses the same enterprise pattern as HR: Tier 1 sections with underlined active state, Tier 2 sub-nav with hairline dividers and chip-style active. Active section is derived from the active tab — no separate state.

The Journal Entry modal builds an editable line-by-line grid with running debit/credit totals; the Save & Post button is disabled until the difference is zero. Each line picker is filtered to active accounts only.

Build verification

cd backend && go build ./...
cd frontend && npx vite build

Both should complete without errors. Frontend now ~1.78 MB / 296 kB gzip — the Configuration panel and config wiring added ~80 kB raw / ~14 kB gzip on top of the deep-finance baseline.

Where to add a new tab

  1. Add table(s) in database.go (in the migrate() statements list, plus indexes).
  2. Add the model struct in backend/internal/models/models.go.
  3. Add CRUD handlers in backend/internal/handlers/finance_extended.go.
  4. Wire routes in backend/cmd/server/main.go under the finance block.
  5. Add API methods in frontend/src/services/api.js.
  6. Add a tab id to one of the TAB_GROUPS arrays in finance-page.js.
  7. Add a case to switchTab() to load the slice.
  8. Add a case to renderTab() to render it.
  9. Add a render method.
  10. Add a modal block in renderModal() if it has create/edit forms.

Follow-ups (NOT done)

  • Multi-currency revaluation. currencies and exchange_rates are now configurable, and bank / chart accounts carry a currency column, but FX revaluation at period end is not yet computed.
  • Approval workflow on journal entries. Today a Finance Manager can post directly. Larger orgs may need a maker/checker chain (drafter ≠ poster).
  • Approval matrix enforcement. approval_thresholds is configurable but not yet read at submit time — POs / payments still post without checking the role × amount rules. Server-side gate to be added in CreatePurchaseOrder / PayBill and friends.
  • Payment terms on vendors / customers. payment_terms is configurable but vendor and customer records don’t yet carry a payment_term_id. Adding the column lets AP / AR aging compute due dates from the term.
  • Auto-post on PO receipt and vendor-bill payment. Bills now auto-post (DR AR / CR Revenue) and bill payment auto-posts (DR Cash / CR AR). The next obvious wires are Goods Receipt → DR Inventory / CR GR-IR-Suspense and Vendor Bill Payment → DR AP / CR Cash.
  • FX revaluation jobs. Once a currency that isn’t the base currency has a non-zero balance, period-end revaluation against the latest exchange_rates should produce an unrealised gain/loss journal automatically.
  • Vendor payment batches. AP aging is read-only; there’s no “select these 12 invoices and pay them” surface yet.
  • Tax application on bills / invoices. tax_codes exists but bill items don’t reference one yet — adding tax_code_id to bill_items and purchase_order_items would let the kiosk auto-compute output and input VAT.
  • Audit log. Posted journal entries, period closures, account-balance overrides are high-trust operations. A finance_audit_log table mirroring the existing audit-log infrastructure would close the compliance gap.
  • Recurring journals. Rent, depreciation, payroll accrual repeat monthly. A “recurring_template” with a cron-like schedule that auto-creates draft journals would save hours each close.
  • Bank statement import. Today bank transactions are entered manually. A CSV / OFX / MT940 importer per bank would dramatically cut reconciliation time.
  • Budget alerts. When actual crosses 80% / 100% of budget, an inter-departmental notification to the cost center manager would let them course-correct mid-month.
  • Drill-through on Balance Sheet / P&L. Click an account to see the journal entries that produced its balance.
  • Aged-trial-balance export. AR aging and AP aging exist as views; CSV / PDF export for collections agencies and auditors is the obvious next step.