Skip to content

Region-aware foundation: per-practice currency, tax & date formatting#9

Merged
evangauer merged 5 commits into
mainfrom
feat/region-aware-foundation
Jun 7, 2026
Merged

Region-aware foundation: per-practice currency, tax & date formatting#9
evangauer merged 5 commits into
mainfrom
feat/region-aware-foundation

Conversation

@evangauer
Copy link
Copy Markdown
Owner

Region-aware foundation

Makes OpenVPM work correctly outside the US without changing anything for US practices. Today currency, tax, and date formatting are hardcoded to USD / 8% / US order; this wires a practice's region through billing and the whole UI so a clinic in another country sees the right currency, tax, and dates end to end.

US practices are unchanged — all defaults are US/USD/8%.

What's included

Schemacountry, currency, taxRatePercent, vatNumber on practices (additive, with US defaults so existing rows keep working).

Locale utilities (lib/locale/format.ts, unit-tested) — formatCurrency, formatDate, regionDefaults(country), and regulatoryFramework(country) (the last sets up future controlled-drug/prescribing differences).

De-hardcoded billing

  • Invoice tax (both the direct and template paths) now reads the practice's configured rate instead of a fixed 8%.
  • Stripe checkout charges in the practice's currency instead of always USD.
  • Practice settings can read/update the region fields; choosing a country prefills sensible currency/tax/timezone defaults.

Region-aware display everywhere — a shared useCurrencyFormatter() hook (backed by a lightweight billing.getTaxConfig query) so amounts and dates render in the practice's region across billing, inventory, dashboard KPIs + revenue chart, reports, the client portal, invoice PDFs, and invoice emails.

Notes

  • Schema change is additive → run pnpm db:push on deploy.
  • pnpm test, pnpm type-check, pnpm build all green (186 tests).

🤖 Generated with Claude Code

evangauer and others added 5 commits June 7, 2026 13:52
Foundation for region-aware behavior (Phase 2). Adds country, currency,
taxRatePercent, and vatNumber to the practices table (US/usd/8% defaults so
existing practices are unchanged), plus a pure locale module: formatCurrency,
formatDate (UTC, locale-ordered), regulatoryFramework(country), and
regionDefaults(country) for onboarding/settings. 7 tests.

Note: additive schema — run pnpm db:push on deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Invoice tax was a fixed 8% and Stripe checkout was fixed to USD; both now
read the practice's region config:
- billing.createInvoice and templates.addItemsFromTemplate compute tax from
  practice.taxRatePercent (fallback 8%)
- new billing.getTaxConfig query feeds the invoice-form total preview so it
  matches the server's authoritative calculation
- portal checkout charges in practice.currency; createCheckoutSession takes a
  currency param (defaults usd)
- settings.updatePractice accepts country/currency/taxRatePercent/vatNumber and
  applies regionDefaults() when the country changes

US practices are unchanged (defaults usd/8%). Foundation for UK/EU pricing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Admins can now set their practice's country, currency, tax/VAT rate, and VAT
number from Settings → Practice Info. Picking a country prefills the usual
currency/tax/timezone defaults (overridable). This is the UI surface for the
region fields wired through billing + checkout, making Phase 2 usable end to
end. Adds UK/EU/CA/AU timezones to the picker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Currency was displayed as hardcoded USD/$ across the app even though billing
now charges in the practice currency. Wire the region through every money
surface:
- new useCurrencyFormatter() hook reads billing.getTaxConfig and formats with
  the practice currency/country (React Query dedupes the request)
- converted billing list/detail, new-invoice form, inventory, dashboard KPIs +
  revenue chart, reports, and the template price in settings
- client portal invoices format in the practice currency/locale (portal
  getInvoices now returns currency + country per row)
- invoice/estimate PDFs take a pre-formatted region-aware balanceDue
- formatCurrency now coerces string/null/NaN amounts safely (DB values arrive
  as strings); +3 tests

US practices are visually unchanged. Closes the region-aware display gap so a
GB practice sees £ and dd/mm dates end to end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sendInvoiceEmail hardcoded a $ on the invoice total in both the client email
and the logged communication. Read the practice currency/country and format
via the shared helper so emailed totals match the rest of the app (e.g. £ for
GB practices).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openvpm Ready Ready Preview, Comment Jun 7, 2026 6:21pm

Request Review

@evangauer evangauer merged commit ab44eb5 into main Jun 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant