Overview
Introduce an immutable double-entry accounting ledger as the source of truth for ChainMove wallet, investment, repayment, payout, fee, and adjustment balances.
Problem
The application currently updates denormalized fields such as User.availableBalance and writes a general transaction record. A partial failure, manual update, or future code path can cause the cached balance and transaction history to disagree. A finance platform needs balanced, auditable entries for every movement of value.
Scope
- Define ledger accounts for investor wallets, driver balances, pool escrow, platform clearing, revenue/fees, repayments receivable, payouts payable, and adjustment accounts.
- Add immutable journal and journal-entry models.
- Require every journal to contain balanced debit and credit totals.
- Create domain posting helpers for wallet funding, wallet debit, pool investment, down payment, repayment, refund, payout, fee, and administrative adjustment.
- Make journal creation idempotent using a domain event/reference key.
- Treat existing balance fields as cached projections, not the source of truth.
- Add a projection/rebuild service that can recompute balances from the ledger.
- Backfill legacy
Transaction records into journals through a dry-run migration.
Security and integrity requirements
- Posted journals cannot be edited or deleted; corrections use reversing entries.
- Administrative adjustments require an actor, reason, and audit event.
- Prevent a journal from posting if total debits and credits differ.
- Keep currency explicit on accounts and entries.
- Enforce transaction/session boundaries for journal and projection updates.
Suggested structure
models/LedgerAccount.ts
models/LedgerJournal.ts
models/LedgerEntry.ts
lib/ledger/posting.service.ts
lib/ledger/projection.service.ts
scripts/migrations/backfill-ledger.ts
Test requirements
- balanced and unbalanced journal validation
- idempotent reposting of the same payment reference
- reversal entries
- concurrent wallet credits/debits
- projection rebuild from journal history
- failure rollback inside MongoDB transactions
- backfill dry run and resume behavior
- invariant test proving system-wide debits equal credits per currency
Acceptance criteria
Evidence required
- Sample balanced journals for at least five domain flows
- Rebuild/reconciliation command output
- Tests demonstrating rollback, reversal, and idempotency
Difficulty
Hard
Overview
Introduce an immutable double-entry accounting ledger as the source of truth for ChainMove wallet, investment, repayment, payout, fee, and adjustment balances.
Problem
The application currently updates denormalized fields such as
User.availableBalanceand writes a general transaction record. A partial failure, manual update, or future code path can cause the cached balance and transaction history to disagree. A finance platform needs balanced, auditable entries for every movement of value.Scope
Transactionrecords into journals through a dry-run migration.Security and integrity requirements
Suggested structure
models/LedgerAccount.tsmodels/LedgerJournal.tsmodels/LedgerEntry.tslib/ledger/posting.service.tslib/ledger/projection.service.tsscripts/migrations/backfill-ledger.tsTest requirements
Acceptance criteria
npm run lint,npm run typecheck,npm run test, andnpm run buildpass.Evidence required
Difficulty
Hard