SourceModule enum extension (Phase 0 Task 0.1)

Status: ✅ Complete

Plan reference: Task 0.1 in 2026-04-30-FINANCE_MODULE_IMPLEMENTATION_PLAN.md (operations workspace root)

What it is

SourceModule is a string-literal union used on every JournalEntry. It tells consumers (reports, reconciliation, posting handlers) which kind of business event produced the entry. Phase 0 extended the enum so every posting flow that Phases 1–3 will introduce has a dedicated tag.

Where it lives

  • API (authoritative)ethica-api/src/models/journal-entry.model.ts:18-35
  • ERP (mirror)ethica-erp/types/finance.ts:10-27

Both must list the same values in the same order. The mirror is a manual copy because the ERP is built from a different package boundary; there is no shared types package today.

The values

ValuePosted by
manualDirect journal entry created via Finance → Accounting transactions
tradeLegacy generic trade postings (predates equity/sukuk split)
subscriptionInvestment subscription approval (investments.service.ts)
redemptionRedemption pricing journal (Phase 1.D)
nav_adjustmentNAV publish (Phase 1.C)
fx_revaluationDaily FX revaluation cron (Phase 2.C)
purificationHaram-income allocation to purification reserve (Phase 2.A)
management_feeDaily management/trustee/custody fee accrual (Phase 2.D)
payrollSalary run integration (Phase 3.A)
dividendEquity dividend receipt with halal/haram split (Phase 2.D)
sukuk_incomeSukuk profit accrual + receipt (Phase 2.D)
mudaraba_profitMudaraba/Wakala profit distribution (Phase 3.A)
equity_tradeEquity buy/sell posting (Phase 1.D)
zakatZakat expense + payable + payment (Phase 2.B)
depreciationMonthly fixed-asset depreciation (Phase 3.A)
redemption_paymentPayout leg of a redemption (Phase 1.D)
month_end_closeMonth-end orchestrator postings (Phase 2.E)

Design rationale

  • Why a string-literal union, not a DB enum? Sequelize-typescript treats this as DataType.STRING(32). Adding a value requires no migration. The trade-off is no DB-level constraint, but our posting engine validates against the union at write time.
  • Why include all future values now? So Phases 1–3 add posting handlers without touching the central model file repeatedly. Each value is harmless until a handler emits it.
  • Why mirror manually in ERP? Avoids a shared package dependency. A drift test on first invocation of a handler will catch missed mirror updates.

Edge cases

  • Switch statements over SourceModule (search: grep -rn "SourceModule" ethica-api/src/ ethica-erp/) currently rely on string equality — no exhaustive switch exists yet, so adding a value silently passes. Phase 1.B will introduce an exhaustive SourceModuleHandler registry that compile-fails on missing handlers.

Tests

  • Compile-time: TypeScript narrows correctly across both packages.
  • Runtime: existing journal-entry unit tests already exercise manual, subscription, trade. New values are exercised by the handlers that emit them.