Product handoff · Projects + Gateway
Multi-currency FX — what shipped
Pay in one currency, bill in another, and still read trustworthy cost & margin in a single reporting currency — with every converted number auditable back to its rate and date.
Status · Designed & prototyped
Surface · 9 screens touched, 3 new
Updated · Jun 2026
The one-paragraph version
The FX feature used to live as a separate exploration (slim mock screens in an fx/ folder). It is now woven into the real product — the actual cost dashboard, ticket, project, portfolio, reconciliation and org settings — not a parallel fork. On top of integration we hardened the trust story (locked vs stale rates, audit trail, no-guess gaps) and, most importantly, made the whole thing invisible to single-currency companies: their platform looks exactly as it did before FX existed, and the machinery only appears the moment a second currency is actually used.
Part 1
Integrated into the live product
A shared disclosure kit (the ≈ "this was converted" marker + hover popover showing native amount, rate, as-of date and source) now rides on top of the real screens. No separate FX fork to maintain.
1
Cost & margin dashboard
Revenue (billed USD/EUR), cost (paid PKR) and margin all read in reporting USD with an ≈ on every converted figure. A header Show in: USD / PKR toggle flips the view. A month with no rate on file shows as an honest gap, never a guessed bar.
pms2-cost.html
2
Ticket · Time & cost
Per-person cost carries the ≈ marker — layered on top of the existing pay-privacy mask, so a marker only ever appears when the viewer is allowed to see cost in the first place.
pms2-ticket.html → Time & cost
3
Project billing · "Billed in"
A project can be invoiced in its own currency; a live preview shows how that native revenue rolls up into reporting USD at the rate locked when each invoice books.
pms2-project.html → Settings → Billing
4
Portfolio & reconciliation roll-ups
The portfolio stat strip sums 4 currencies into reporting USD with a plain-language note; the reconciliation "margin leak" carries the same marker. Both honour the pay-privacy mask.
pms2-portfolio.html · pms2-reconciliation.html
5
New: FX rate management, legend & index
A dedicated FX rates screen (pair · rate · as-of · source, manual entry + future feed, freshness threshold) now lives in the Projects sidebar → Insights. Plus a spec/legend page and a one-page feature index.
pms2-fx-rates.html · legend · index
Part 2
Trust & correctness hardening
A converted number is only useful if people believe it. These five make the "every figure is auditable" promise real rather than decorative.
- Stale rates are shown, live. Booked actuals are rate-locked and never drift. A forecast that converts at a rate gone past the freshness threshold flags amber with a "refresh before you rely on this" warning — demonstrated on the Acme forecast, not just described.
- Rate changes are audited. Editing an FX rate writes to the org audit log (e.g. "PKR/USD · 278.40 → 281.05"), alongside automatic feed refreshes.
- One consistent toggle. The reporting ↔ native switch now behaves identically on cost, ticket, portfolio and reconciliation.
- Missing rates are actionable. A project excluded from a roll-up for lack of a rate shows as a labelled strip with an "Add the rate →" button — not buried prose.
- No silent gaps. The rate table covers every selectable currency; manual rates are shown drifting stale while the feed stays fresh, so the "why feeds matter" story is visible.
Part 3 · the important one
Single-currency companies see none of it
The biggest risk was burdening the majority of customers — who only ever deal in one currency — with machinery they don't need. The principle we shipped: FX is zero-config and self-arming, never an opt-out.
Single-currency org (default)
- No ≈ markers, no currency toggles
- No "FX rates" in the sidebar
- Org setting reads a plain "Currency"
- Project shows a quiet "Bill in another currency" link, not a picker
- Identical to the pre-FX product
Multi-currency org (armed)
- Full disclosure markers + popovers
- "FX rates" appears in Insights
- Org setting becomes "Reporting currency" + roll-up help
- Per-project transaction-currency picker
- Stale flags, audit trail, roll-up notes
Self-arming, not a setting to find. A company becomes multi-currency the moment they actually need it — choosing another currency on a project, or flipping "Turn on multi-currency" in org settings. That single action reveals the machinery. Nobody has to opt out of complexity they don't have.
How to demo both: every FX screen carries a small ORG · Multi / Single switch in the bottom-left corner (prototype-only, hidden in print). Flip it to Single and watch all FX chrome disappear; flip back to Multi to see the full feature.
Quick reference
Where to look, and for what
| Screen | What to look for |
| Cost & margin | Hero ≈ markers · "Show in" toggle · amber stale forecast · missing-rate gap on the trend |
| Ticket · Time | Per-person cost marker that respects the pay-privacy mask |
| Project · Billing | "Billed in" picker + live roll-up preview (multi) / "Bill in another currency" link (single) |
| Portfolio | 4-currency stat strip · roll-up note · excluded-project "Add the rate" strip |
| Reconciliation | Margin-leak cost marker + reporting/native toggle |
| FX rates | The rate table that powers every conversion · stale vs fresh · empty state |
| Org settings | "Reporting currency" ↔ "Currency" · audit log entries for rate edits |
| FX index | One-page tour of the whole feature |
For engineering planning
Locked decisions & scope
Decisions locked
- Reporting currency is the default view; native is one hover/toggle away
- Rate is locked at booking per transaction — actuals never drift
- Forecasts use the latest rate and can go stale (per-figure flag, org-set threshold, default 7d)
- Convert-then-sum with a ±rounding footnote
- No rate on file ⇒ show native + a "needs rate" nudge — never a guessed number
- All FX chrome gated on org being multi-currency
Not in this scope
- Live rate-feed automation (UI stub present)
- Historical rate backfill / re-statement
- Cross-pair math beyond into-reporting conversion
- Real RBAC wiring (demonstrated via role preview)
- FX on Symphora's own subscription billing
Backend shape: an org.reporting_currency setting · a per-transaction fx_rate + fx_rate_date captured at booking · a rate table with as-of history and a freshness threshold · convert-then-sum roll-up. Full detail in the dev handover.