feat: close final competitive gap — professional review workflow + multi-country (MY/SG/UK)

Closes the 4th and final gap from the competitive benchmark: the
human-in-the-loop professional tier every commercial competitor pairs
with their software. Deliberately NOT a marketplace or payment
integration — a structured review-request status on each member's
Wassiyah (not_requested -> requested -> reviewed, with reviewer name
recorded), surfaced on the Mutawalli dashboard so nothing quietly ships
as final without the legally-required review being flagged.

Paired with a new whole-app jurisdiction setting since 'necessary in
certain countries' only makes sense per-jurisdiction:

- nf_families.jurisdiction (MY/SG/UK), owner-only to change — first
  UPDATE policy ever added to nf_families, which previously had none.
- New Settings-tab jurisdiction selector (JurisdictionSetting.svelte).
- Wassiyah tab and the draft will document now carry jurisdiction-
  specific legal notes: Wills Act 1959 + state Syariah (Malaysia),
  Wills Act 1838 + AMLA (Singapore), Wills Act 1837 (UK). Singapore
  added as a full third jurisdiction option, not just a stub.
- Zakat calculator's currency label and Nisab default now follow the
  family's jurisdiction (RM/SGD/GBP) instead of a hardcoded RM guess.

Covered by e2e-jurisdiction-review.cjs (13/13). Full regression:
253/253 across all suites (2 reruns confirmed as pre-existing
parallel-load flakes, not regressions).
This commit is contained in:
2026-08-14 12:37:25 +08:00
parent 71aa4ae47c
commit 97a1d2ebe2
11 changed files with 327 additions and 31 deletions
+7
View File
@@ -4,6 +4,13 @@
// established for Asset Registry / Faraid.
const ZAKAT_RATE = 0.025;
// Rough starting points, NOT live prices — the app has no gold/silver price
// feed (manual entry only, matching the rest of the app). These exist so a
// new user isn't staring at a blank/zero Nisab field; they must still verify
// today's actual 85g-gold-equivalent value before relying on the result.
export const CURRENCY_BY_JURISDICTION = { MY: 'RM', SG: 'SGD', UK: 'GBP' };
export const DEFAULT_NISAB_BY_JURISDICTION = { MY: 24000, SG: 8000, UK: 5000 };
export function zakatableWealth(fields) {
const gross = (Number(fields.cash) || 0) + (Number(fields.gold) || 0) + (Number(fields.silver) || 0)
+ (Number(fields.businessAssets) || 0) + (Number(fields.investments) || 0) + (Number(fields.otherZakatable) || 0);