feat: add Zakat calculator (per-member, Nisab + 2.5% rate)

Closes the Zakat gap identified in the competitive benchmark against
Rafiq. Per-member module (nf_zakat_records, same author-owns/family-reads
RLS pattern as Insurance/Wassiyah/Waqf) computing 2.5% due on zakatable
wealth (cash, gold, silver, business assets, investments) once above a
user-entered Nisab threshold, minus deductible short-term liabilities.
Manual entry only, matching the app's existing philosophy — no live gold
price feed. Covered by e2e-zakat.cjs (7/7).
This commit is contained in:
2026-08-14 11:55:59 +08:00
parent 2b022cbcc3
commit 972ad7ff68
5 changed files with 234 additions and 12 deletions
+24
View File
@@ -0,0 +1,24 @@
// Shared Zakat calculation core — Nisab check + 2.5% (1/40) on qualifying
// wealth held above threshold. Nisab is entered as a value, not a live gold
// price feed — matches the app's "manual entry only" philosophy already
// established for Asset Registry / Faraid.
const ZAKAT_RATE = 0.025;
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);
const deductible = Number(fields.deductibleLiabilities) || 0;
return Math.max(0, gross - deductible);
}
export function zakatDue(fields) {
const wealth = zakatableWealth(fields);
const nisab = Number(fields.nisabThreshold) || 0;
if (nisab <= 0 || wealth < nisab) return 0;
return wealth * ZAKAT_RATE;
}
export function meetsNisab(fields) {
const nisab = Number(fields.nisabThreshold) || 0;
return nisab > 0 && zakatableWealth(fields) >= nisab;
}