- Account Management
- Transaction Processing
- Loan Lifecycle Operations
- Reporting Queries
$ pnpm installCreate a new .env.development file copying the contents from .env.example file
cp .env.example .env.development# development
$ pnpm run start
# watch mode
$ pnpm run start:dev
# production mode
$ pnpm run start:prod# unit tests
$ pnpm run test
# e2e tests
$ pnpm run test:e2e
# test coverage
$ pnpm run test:cov#User Flow
curl -X POST http://localhost:3000/api/accounting/setup/seed-accountscurl -X POST http://localhost:3000/api/lenders/setup/seed-lenderscurl -X POST http://localhost:3000/api/lenders/setup/seed-contributionscurl -X GET http://localhost:3000/api/accounting/accounts | jq .Filter accounts with currency=KES, we will use account ids from the seeded accounts list to perform next operations
curl 'http://localhost:3000/api/accounting/accounts?currency=KES' | jq .Request:
POST http://localhost:3000/api/loans
Content-Type: application/json
Payload:
{
"borrowerId": "BORROWER-001",
"amount": 50000,
"currency": "KES",
"termDays": 30
}Response:
{
"id": "3bfcdd4a-c372-4f3b-9043-1b290476d71b",
"borrowerId": "BORROWER-001",
"amount": 50000,
"currency": "KES",
"status": "PENDING",
"disbursedAt": "2026-01-11T13:52:07.637Z",
"dueDate": "2026-02-10T13:52:07.637Z",
"createdAt": "2026-01-11T13:52:07.656Z",
"updatedAt": "2026-01-11T13:52:07.656Z"
}** store the loan-id (3bfcdd4a-c372-4f3b-9043-1b290476d71b) for next steps
Request:
POST http://localhost:3000/api/loans/loan-uuid-12345/disburse
Content-Type: application/jsonPayload:
{
"cashAccountId": "uuid-cash-kes",
"loansReceivableAccountId": "uuid-loans-kes",
"feeReceivableAccountId": "uuid-fee-recv-kes",
"feeIncomeAccountId": "uuid-fee-inc-kes",
"feeAmount": 2500
}Note: Replace uuid-* with actual IDs from Step 2.2
Response:
{
"id": "3bfcdd4a-c372-4f3b-9043-1b290476d71b",
"borrowerId": "BORROWER-001",
"amount": "50000.00",
"currency": "KES",
"status": "DISBURSED",
"disbursedAt": "2026-01-11T16:39:48.688Z",
"dueDate": "2026-02-10T13:52:07.637Z",
"createdAt": "2026-01-11T13:52:07.656Z",
"updatedAt": "2026-01-11T16:39:48.690Z"
}Request:
POST http://localhost:3000/api/loans/loan-uuid-12345/repay
Content-Type: application/jsonPayload:
{
"principalAmount": 50000,
"interestAmount": 3500,
"cashAccountId": "uuid-cash-kes",
"loansReceivableAccountId": "uuid-loans-kes",
"interestIncomeAccountId": "uuid-int-inc-kes"
}Response:
{
"loanId": "loan-uuid-12345",
"borrowerId": "BORROWER-001",
"principalPaid": 50000,
"interestPaid": 3500,
"totalRepayment": 53500,
"status": "CLOSED"
}Request:
POST http://localhost:3000/api/loans
Content-Type: application/jsonPayload:
{
"borrowerId": "BORROWER-002",
"amount": 30000,
"currency": "KES",
"termDays": 30
}Response:
{
"id": "4bfcdd4a-c372-4f3b-9043-1b290476d71e",
"borrowerId": "BORROWER-002",
"amount": 30000,
"currency": "KES",
"status": "PENDING",
"disbursedAt": "2026-01-11T13:52:07.637Z",
"dueDate": "2026-02-10T13:52:07.637Z",
"createdAt": "2026-01-11T13:52:07.656Z",
"updatedAt": "2026-01-11T13:52:07.656Z"
}save the loan id to be used to disburse loan, the next step
Request:
POST http://localhost:3000/api/loans/loan-uuid-67890/disburse
Content-Type: application/jsonPayload:
{
"cashAccountId": "uuid-cash-kes",
"loansReceivableAccountId": "uuid-loans-kes"
}Response:
{
{
"id": "12fcdd4a-c372-4f3b-9043-1b290476d71b",
"borrowerId": "BORROWER-002",
"amount": "30000.00",
"currency": "KES",
"status": "DISBURSED",
"disbursedAt": "2026-01-11T16:39:48.688Z",
"dueDate": "2026-02-10T13:52:07.637Z",
"createdAt": "2026-01-11T13:52:07.656Z",
"updatedAt": "2026-01-11T16:39:48.690Z"
}
}Request:
POST http://localhost:3000/api/loans/loan-uuid-67890/writeoff
Content-Type: application/jsonPayload:
{
"loansReceivableAccountId": "uuid-loans-kes",
"badDebtExpenseAccountId": "uuid-bad-debt-exp-kes"
}Response:
{
"message": "Loan written off successfully",
"loanId": "loan-uuid-67890",
"writeOffAmount": "30000.00",
"updatedStatus": "DEFAULTED"
}Request:
GET http://localhost:3000/api/accounting/reports/trial-balanceRequest:
GET http://localhost:3000/api/accounting/reports/balance-sheetRequest:
GET http://localhost:3000/api/accounting/reports/transaction-history/uuid-cash-kes?page=1&limit=10Request:
GET http://localhost:3000/api/loans/reports/agingRequest:
POST http://localhost:3000/api/accounting/transactions
Content-Type: application/jsonPayload:
{
"idempotencyKey": "manual-adjustment-001",
"description": "Manual adjustment - test reversal",
"type": "ADJUSTMENT",
"entries": [
{
"accountId": "uuid-cash-kes",
"debit": 5000,
"credit": 0
},
{
"accountId": "uuid-retained-kes",
"debit": 0,
"credit": 5000
}
]
}Response:
{
"id": "journal-uuid-abc123",
"idempotencyKey": "manual-adjustment-001",
"description": "Manual adjustment - test reversal",
"status": "POSTED",
"type": "ADJUSTMENT",
"currency": "KES",
"entries": [...],
"version": 1,
"postedAt": "2026-01-11T13:00:00.000Z",
"createdAt": "2026-01-11T13:00:00.000Z"
}** Store the journal-uuid-abc123 for reversal
Request:
POST http://localhost:3000/api/accounting/transactions/journal-uuid-abc123/reverse
Content-Type: application/jsonPayload:
{
"reason": "Administrative correction - wrong amount",
"expectedVersion": 1
}Response:
{
"originalTransaction": {
"id": "journal-uuid-abc123",
"status": "REVERSED",
"version": 2,
"description": "Manual adjustment - test reversal"
},
"reversalTransaction": {
"id": "journal-uuid-xyz789",
"idempotencyKey": "reversal-journal-uuid-abc123",
"description": "REVERSAL: Manual adjustment- wrong amount)",
"status": "POSTED",
"type": "ADJUSTMENT",
"entries": [
{
"accountId": "uuid-cash-kes",
"debit": 0,
"credit": 5000
},
{
"accountId": "uuid-retained-kes",
"debit": 5000,
"credit": 0
}
],
"reversalOf": {
"id": "journal-uuid-abc123"
},
"postedAt": "2026-01-11T13:05:00.000Z"
}
}-
Account ↔ Account: may have or lack parent id (to support this hierarchy: Assets → Current Assets → Cash) - Self-referential parentid
-
Journal → LedgerEntry: One-to-Many (each journal entry contains ledger lines)
-
LedgerEntry → Account: Many-to-One (each ledger line posts to one account)
-
Journal ↔ Journal: Self-referential for transaction reversals (reversalOf points to original journal)
-
Lender: Lender capital contributions are tracked through
Journalentries with typeCAPITAL_CONTRIBUTION -
Loan: Loan operations (disbursement, repayment, write-off) are tracked through
Journalentries
| Entity | Purpose | Key Features |
|---|---|---|
| Account | Chart of Accounts | Supports 5 types (ASSET, LIABILITY, EQUITY, INCOME, EXPENSE), multi-currency, hierarchical structure |
| Journal | Transaction header | Idempotency, versioning, reversal tracking, multi-type support |
| LedgerEntry | Individual debit/credit lines | Ensures double-entry bookkeeping, linked to accounts and journals |
| Lender | Capital providers | Tracks lender identity, contributions via journal entries |
| Loan | Borrower loans | Tracks loan lifecycle (PENDING → DISBURSED → CLOSED/DEFAULTED) |