fix: IPSAS double-entry compliance matrix for M0-M4 journals
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m36s
CI/CD Pipeline / Security Scanning (push) Successful in 2m26s
CI/CD Pipeline / Lint and Format (push) Failing after 49s
CI/CD Pipeline / Terraform Validation (push) Failing after 26s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 24s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 30s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 27s
Validation / validate-genesis (push) Successful in 30s
Validation / validate-terraform (push) Failing after 28s
Validation / validate-kubernetes (push) Failing after 11s
Validation / validate-smart-contracts (push) Failing after 10s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 19s
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m36s
CI/CD Pipeline / Security Scanning (push) Successful in 2m26s
CI/CD Pipeline / Lint and Format (push) Failing after 49s
CI/CD Pipeline / Terraform Validation (push) Failing after 26s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 24s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 30s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 27s
Validation / validate-genesis (push) Successful in 30s
Validation / validate-terraform (push) Failing after 28s
Validation / validate-kubernetes (push) Failing after 11s
Validation / validate-smart-contracts (push) Failing after 10s
Validation / validate-security (push) Failing after 1m23s
Validation / validate-documentation (push) Failing after 19s
Replace invalid same-GL pairs (2000/2000) with proper Dr/Cr legs, add M2-M4 and inter-office entries (1410/2410), extend IPSAS registry accounts, and reject same-account pairs at validation load time. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
"headOfficeDebitGlCode": "2100",
|
||||
"headOfficeCreditGlCode": "2410",
|
||||
"materialOpeningUsd": 1000000000,
|
||||
"journalMatrixDoc": "docs/04-configuration/mifos-omnl-central-bank/omnl-journal-matrix-hospitallers24-opening-1b.json",
|
||||
"journalMatrixDoc": "config/omnl-journal-matrix.json",
|
||||
"runbookDoc": "docs/04-configuration/mifos-omnl-central-bank/HOSPITALLERS24_OFFICE_OPENING_1B_M1.md",
|
||||
"currency": "USD",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"description": "IPSAS-aligned GL codes for OMNL Hybx (Fineract). Includes core M0/M1, FX/XAU (IAS 21), fees, and IAS 37 provisions.",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"currencyCode": "USD",
|
||||
"accounts": [
|
||||
{
|
||||
@@ -22,6 +22,42 @@
|
||||
"ifrsRefs": ["IAS 32", "IFRS 9"],
|
||||
"roles": ["m0_reserve", "treasury_reserve"]
|
||||
},
|
||||
{
|
||||
"glCode": "1410",
|
||||
"name": "Due from Head Office (inter-office receivable)",
|
||||
"fineractType": "ASSET",
|
||||
"usage": "Branch receivable from HO for M1 allocation; not M4 near-money aggregate",
|
||||
"ipsasStandards": ["IPSAS 28", "IPSAS 29"],
|
||||
"ifrsRefs": ["IFRS 7"],
|
||||
"roles": ["interoffice_receivable", "due_from_ho"]
|
||||
},
|
||||
{
|
||||
"glCode": "2200",
|
||||
"name": "M2 — Broad money / meta-fiat rail",
|
||||
"fineractType": "LIABILITY",
|
||||
"usage": "Broad money layer backing token loads and external rails",
|
||||
"ipsasStandards": ["IPSAS 28", "IPSAS 29"],
|
||||
"ifrsRefs": ["IAS 32"],
|
||||
"roles": ["m2_broad", "meta_fiat"]
|
||||
},
|
||||
{
|
||||
"glCode": "2300",
|
||||
"name": "M3 — On-chain token liabilities",
|
||||
"fineractType": "LIABILITY",
|
||||
"usage": "Token liabilities minted from M2 backing on Chain 138",
|
||||
"ipsasStandards": ["IPSAS 28", "IPSAS 29"],
|
||||
"ifrsRefs": ["IAS 32", "IFRS 9"],
|
||||
"roles": ["m3_token_liability", "crypto_liability"]
|
||||
},
|
||||
{
|
||||
"glCode": "2410",
|
||||
"name": "Due to branch offices (inter-office payable)",
|
||||
"fineractType": "LIABILITY",
|
||||
"usage": "HO payable to branch for allocated M1 / opening journals",
|
||||
"ipsasStandards": ["IPSAS 28", "IPSAS 29"],
|
||||
"ifrsRefs": ["IFRS 7"],
|
||||
"roles": ["interoffice_payable", "due_to_branch"]
|
||||
},
|
||||
{
|
||||
"glCode": "12010",
|
||||
"name": "FX reserves — USD",
|
||||
@@ -184,8 +220,19 @@
|
||||
"allowedJournalPairs": [
|
||||
{ "debitGlCode": "1000", "creditGlCode": "2000", "ipsasRef": "IPSAS 3, 28", "memo": "T-001", "ifrsRef": "IAS 32 issuance" },
|
||||
{ "debitGlCode": "1050", "creditGlCode": "2000", "ipsasRef": "IPSAS 28, 29", "memo": "T-001B", "ifrsRef": "IAS 32 M0 reserve" },
|
||||
{ "debitGlCode": "2000", "creditGlCode": "2000", "ipsasRef": "IPSAS 28", "memo": "T-002A" },
|
||||
{ "debitGlCode": "2000", "creditGlCode": "2410", "ipsasRef": "IPSAS 28", "memo": "T-002A", "ifrsRef": "IAS 32 inter-office allocation" },
|
||||
{ "debitGlCode": "2000", "creditGlCode": "2100", "ipsasRef": "IPSAS 28", "memo": "T-002B" },
|
||||
{ "debitGlCode": "2100", "creditGlCode": "2410", "ipsasRef": "IPSAS 28", "memo": "T-H24-HO", "ifrsRef": "IAS 32 HO opening leg" },
|
||||
{ "debitGlCode": "1410", "creditGlCode": "2100", "ipsasRef": "IPSAS 28", "memo": "T-H24-BR", "ifrsRef": "IAS 32 branch opening leg" },
|
||||
{ "debitGlCode": "2100", "creditGlCode": "2200", "ipsasRef": "IPSAS 28, 29", "memo": "T-M1M2", "ifrsRef": "IAS 32 M1 to M2" },
|
||||
{ "debitGlCode": "2200", "creditGlCode": "2300", "ipsasRef": "IPSAS 28, 29", "memo": "T-M2M3", "ifrsRef": "IAS 32 token liability" },
|
||||
{ "debitGlCode": "2200", "creditGlCode": "1000", "ipsasRef": "IPSAS 2, 28", "memo": "T-M2M4", "ifrsRef": "IFRS 7 SWIFT suspense" },
|
||||
{ "debitGlCode": "2100", "creditGlCode": "1000", "ipsasRef": "IPSAS 2, 28", "memo": "T-M1M4", "ifrsRef": "IFRS 7 SWIFT suspense" },
|
||||
{ "debitGlCode": "1000", "creditGlCode": "2200", "ipsasRef": "IPSAS 2, 28", "memo": "T-M4-IN", "ifrsRef": "IFRS 7 inbound SWIFT" },
|
||||
{ "debitGlCode": "1410", "creditGlCode": "2100", "ipsasRef": "IPSAS 28, 29", "memo": "T-SBLC", "ifrsRef": "IFRS 9 trade finance" },
|
||||
{ "debitGlCode": "1410", "creditGlCode": "1000", "ipsasRef": "IPSAS 2, 28", "memo": "T-DLC", "ifrsRef": "IFRS 9 documentary LC" },
|
||||
{ "debitGlCode": "1410", "creditGlCode": "2100", "ipsasRef": "IPSAS 28, 29", "memo": "T-BG", "ifrsRef": "IFRS 9 bank guarantee" },
|
||||
{ "debitGlCode": "1000", "creditGlCode": "2200", "ipsasRef": "IPSAS 2, 28", "memo": "T-LS", "ifrsRef": "IFRS 7 letter of settlement" },
|
||||
{ "debitGlCode": "12020", "creditGlCode": "42100", "ipsasRef": "IPSAS 29", "memo": "FX-REVAL-GAIN", "ifrsRef": "IAS 21 revaluation" },
|
||||
{ "debitGlCode": "52100", "creditGlCode": "12020", "ipsasRef": "IPSAS 29", "memo": "FX-REVAL-LOSS", "ifrsRef": "IAS 21 revaluation" },
|
||||
{ "debitGlCode": "52100", "creditGlCode": "23010", "ipsasRef": "IPSAS 19", "memo": "IAS37-PROVISION", "ifrsRef": "IAS 37" },
|
||||
@@ -216,6 +263,11 @@
|
||||
"monetaryLayerHints": {
|
||||
"m0_reserve": { "primaryGlCodes": ["1050"], "ipsasNarrative": "Treasury / M0 reserve assets (IPSAS 28, 29)" },
|
||||
"m1_liability": { "primaryGlCodes": ["2000", "2100"], "ipsasNarrative": "Financial liabilities — deposits (IPSAS 28, 29)" },
|
||||
"m2_broad": { "primaryGlCodes": ["2200"], "ipsasNarrative": "Broad money / meta-fiat rail (IPSAS 28, 29)" },
|
||||
"m3_token": { "primaryGlCodes": ["2300"], "ipsasNarrative": "On-chain token liabilities (IPSAS 28, 29)" },
|
||||
"m4_near_money": { "primaryGlCodes": ["1000"], "ipsasNarrative": "Settlement suspense + in-flight SWIFT — not GL 1410 (IPSAS 2, 28)" },
|
||||
"interoffice_receivable": { "primaryGlCodes": ["1410"], "ipsasNarrative": "Due from Head Office — branch asset (IPSAS 28)" },
|
||||
"interoffice_payable": { "primaryGlCodes": ["2410"], "ipsasNarrative": "Due to branch — HO liability (IPSAS 28)" },
|
||||
"m00_gru": { "primaryGlCodes": ["21010"], "ipsasNarrative": "GRU M00 base reserve (gold-referenced)" },
|
||||
"settlement": { "primaryGlCodes": ["1000", "13010"], "ipsasNarrative": "Cash and cash equivalents (IPSAS 2)" },
|
||||
"equity": { "primaryGlCodes": ["3000"], "ipsasNarrative": "Equity / control (IPSAS 1)" },
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"description": "OMNL journal entries for API posting to OMNL Hybx (Fineract). Head Office and entities 2–8 per Migration Memorandum. officeId=1 for all; narrative identifies entity. IPSAS-aligned.",
|
||||
"source": "OMNL_JOURNAL_LEDGER_MATRIX.md",
|
||||
"description": "IPSAS double-entry journal matrix for OMNL Hybx (Fineract). Every entry debits one GL and credits a distinct GL with equal amount — no same-account pairs. Head Office (officeId=1) and branch legs identified by memo / officeId.",
|
||||
"source": "OMNL_IPSAS_COMPLIANCE_MATRIX",
|
||||
"currencyCode": "USD",
|
||||
"dateFormat": "yyyy-MM-dd",
|
||||
"locale": "en",
|
||||
"doubleEntryRule": "debitGlCode !== creditGlCode; sum(debits) === sum(credits) per journal",
|
||||
"entries": [
|
||||
{
|
||||
"memo": "T-001",
|
||||
@@ -11,8 +12,9 @@
|
||||
"debitGlCode": "1000",
|
||||
"creditGlCode": "2000",
|
||||
"amount": 900000000000,
|
||||
"narrative": "Opening Balance Migration (Head Office)",
|
||||
"ipsasRef": "IPSAS 3, 28"
|
||||
"narrative": "Opening Balance Migration (Head Office) — Dr settlement asset / Cr M1 central deposits",
|
||||
"ipsasRef": "IPSAS 3, 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-001B",
|
||||
@@ -20,17 +22,19 @@
|
||||
"debitGlCode": "1050",
|
||||
"creditGlCode": "2000",
|
||||
"amount": 250000000000,
|
||||
"narrative": "Treasury Conversion — Transfer to Reserve (M0); Head Office",
|
||||
"ipsasRef": "IPSAS 28, 29"
|
||||
"narrative": "Treasury Conversion — Dr M0 reserve / Cr M1 central deposits (Head Office)",
|
||||
"ipsasRef": "IPSAS 28, 29",
|
||||
"moneyLayer": "M0"
|
||||
},
|
||||
{
|
||||
"memo": "T-002A",
|
||||
"officeId": 1,
|
||||
"debitGlCode": "2000",
|
||||
"creditGlCode": "2000",
|
||||
"creditGlCode": "2410",
|
||||
"amount": 2900000000,
|
||||
"narrative": "Shamrayan Available (M1) — Office 2",
|
||||
"ipsasRef": "IPSAS 28"
|
||||
"narrative": "HO M1 allocation to Office 2 — Dr central deposits / Cr due to branch",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-002B",
|
||||
@@ -38,8 +42,9 @@
|
||||
"debitGlCode": "2000",
|
||||
"creditGlCode": "2100",
|
||||
"amount": 2100000000,
|
||||
"narrative": "Shamrayan Restricted — Office 2",
|
||||
"ipsasRef": "IPSAS 28"
|
||||
"narrative": "Shamrayan Restricted — Dr M1 central / Cr M1 restricted (Office 2)",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-003",
|
||||
@@ -47,53 +52,179 @@
|
||||
"debitGlCode": "2000",
|
||||
"creditGlCode": "2100",
|
||||
"amount": 350000000000,
|
||||
"narrative": "HYBX Capitalization Escrow — Office 3",
|
||||
"ipsasRef": "IPSAS 28"
|
||||
"narrative": "HYBX Capitalization Escrow — Dr M1 central / Cr M1 restricted (Office 3)",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-004",
|
||||
"officeId": 1,
|
||||
"debitGlCode": "2000",
|
||||
"creditGlCode": "2000",
|
||||
"creditGlCode": "2410",
|
||||
"amount": 5000000000,
|
||||
"narrative": "TAJ Allocation (M1) — Office 4",
|
||||
"ipsasRef": "IPSAS 28"
|
||||
"narrative": "TAJ Allocation (M1) — HO Dr central deposits / Cr due to branch (Office 4)",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-005",
|
||||
"officeId": 1,
|
||||
"debitGlCode": "2000",
|
||||
"creditGlCode": "2000",
|
||||
"creditGlCode": "2410",
|
||||
"amount": 5000000000,
|
||||
"narrative": "Aseret Allocation (M1) — Office 5",
|
||||
"ipsasRef": "IPSAS 28"
|
||||
"narrative": "Aseret Allocation (M1) — HO Dr central deposits / Cr due to branch (Office 5)",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-006",
|
||||
"officeId": 1,
|
||||
"debitGlCode": "2000",
|
||||
"creditGlCode": "2000",
|
||||
"creditGlCode": "2410",
|
||||
"amount": 5000000000,
|
||||
"narrative": "Mann Li Allocation (M1) — Office 6",
|
||||
"ipsasRef": "IPSAS 28"
|
||||
"narrative": "Mann Li Allocation (M1) — HO Dr central deposits / Cr due to branch (Office 6)",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-007",
|
||||
"officeId": 1,
|
||||
"debitGlCode": "2000",
|
||||
"creditGlCode": "2000",
|
||||
"creditGlCode": "2410",
|
||||
"amount": 50000000000,
|
||||
"narrative": "OSJ Allocation (M1) — Office 7",
|
||||
"ipsasRef": "IPSAS 28"
|
||||
"narrative": "OSJ Allocation (M1) — HO Dr central deposits / Cr due to branch (Office 7)",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-008",
|
||||
"officeId": 1,
|
||||
"debitGlCode": "2000",
|
||||
"creditGlCode": "2000",
|
||||
"creditGlCode": "2410",
|
||||
"amount": 50000000000,
|
||||
"narrative": "Alltra Allocation (M1) — Office 8",
|
||||
"ipsasRef": "IPSAS 28"
|
||||
"narrative": "Alltra Allocation (M1) — HO Dr central deposits / Cr due to branch (Office 8)",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-H24-HO",
|
||||
"officeId": 1,
|
||||
"debitGlCode": "2100",
|
||||
"creditGlCode": "2410",
|
||||
"amount": 1000000000,
|
||||
"narrative": "HOSPITALLERS-24 opening HO leg — Dr M1 circulating / Cr due to Office 24",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-H24-BR",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "1410",
|
||||
"creditGlCode": "2100",
|
||||
"amount": 1000000000,
|
||||
"narrative": "HOSPITALLERS-24 opening branch leg — Dr due from HO / Cr M1 circulating",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
},
|
||||
{
|
||||
"memo": "T-M1M2",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "2100",
|
||||
"creditGlCode": "2200",
|
||||
"amount": 0,
|
||||
"narrative": "M1→M2 broad-money conversion — Dr M1 circulating / Cr M2 broad money",
|
||||
"ipsasRef": "IPSAS 28, 29",
|
||||
"moneyLayer": "M2"
|
||||
},
|
||||
{
|
||||
"memo": "T-M2M3",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "2200",
|
||||
"creditGlCode": "2300",
|
||||
"amount": 0,
|
||||
"narrative": "M2→M3 token load — Dr M2 broad money / Cr M3 on-chain token liability",
|
||||
"ipsasRef": "IPSAS 28, 29",
|
||||
"moneyLayer": "M3"
|
||||
},
|
||||
{
|
||||
"memo": "T-M2M4",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "2200",
|
||||
"creditGlCode": "1000",
|
||||
"amount": 0,
|
||||
"narrative": "M2→M4 SWIFT/RTGS outbound — Dr M2 broad money / Cr settlement suspense (GL 1000)",
|
||||
"ipsasRef": "IPSAS 2, 28",
|
||||
"moneyLayer": "M4"
|
||||
},
|
||||
{
|
||||
"memo": "T-M1M4",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "2100",
|
||||
"creditGlCode": "1000",
|
||||
"amount": 0,
|
||||
"narrative": "M1→M4 SWIFT/RTGS outbound — Dr M1 circulating / Cr settlement suspense (GL 1000)",
|
||||
"ipsasRef": "IPSAS 2, 28",
|
||||
"moneyLayer": "M4"
|
||||
},
|
||||
{
|
||||
"memo": "T-M4-IN",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "1000",
|
||||
"creditGlCode": "2200",
|
||||
"amount": 0,
|
||||
"narrative": "M4 inbound SWIFT settled — Dr settlement suspense / Cr M2 broad money",
|
||||
"ipsasRef": "IPSAS 2, 28",
|
||||
"moneyLayer": "M4"
|
||||
},
|
||||
{
|
||||
"memo": "T-SBLC",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "1410",
|
||||
"creditGlCode": "2100",
|
||||
"amount": 0,
|
||||
"narrative": "SBLC trade finance — Dr inter-office / contingent receivable / Cr M1",
|
||||
"ipsasRef": "IPSAS 28, 29",
|
||||
"moneyLayer": "M4"
|
||||
},
|
||||
{
|
||||
"memo": "T-DLC",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "1410",
|
||||
"creditGlCode": "1000",
|
||||
"amount": 0,
|
||||
"narrative": "Documentary LC — Dr contingent receivable / Cr M4 settlement suspense",
|
||||
"ipsasRef": "IPSAS 2, 28",
|
||||
"moneyLayer": "M4"
|
||||
},
|
||||
{
|
||||
"memo": "T-BG",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "1410",
|
||||
"creditGlCode": "2100",
|
||||
"amount": 0,
|
||||
"narrative": "Bank guarantee — Dr contingent receivable / Cr M1",
|
||||
"ipsasRef": "IPSAS 28, 29",
|
||||
"moneyLayer": "M4"
|
||||
},
|
||||
{
|
||||
"memo": "T-LS",
|
||||
"officeId": 24,
|
||||
"debitGlCode": "1000",
|
||||
"creditGlCode": "2200",
|
||||
"amount": 0,
|
||||
"narrative": "Letter of settlement — Dr M4 suspense / Cr M2",
|
||||
"ipsasRef": "IPSAS 2, 28",
|
||||
"moneyLayer": "M4"
|
||||
},
|
||||
{
|
||||
"memo": "SETTLE-NOSTRO",
|
||||
"officeId": 1,
|
||||
"debitGlCode": "13010",
|
||||
"creditGlCode": "2000",
|
||||
"amount": 0,
|
||||
"narrative": "Nostro settlement — Dr FX nostro asset / Cr M1 central deposits",
|
||||
"ipsasRef": "IPSAS 28",
|
||||
"moneyLayer": "M1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ router.get('/omnl/ipsas/registry', omnlSensitiveRouteGuard, (_req: Request, res:
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /omnl/ipsas/matrix — journal matrix (T-001…T-008) for Fineract posting alignment.
|
||||
* GET /omnl/ipsas/matrix — IPSAS double-entry journal matrix (T-001…T-H24, M0–M4 rails).
|
||||
*/
|
||||
router.get('/omnl/ipsas/matrix', omnlSensitiveRouteGuard, (_req: Request, res: Response) => {
|
||||
try {
|
||||
@@ -213,7 +213,7 @@ router.get('/omnl/ipsas/compliance-context/:lineId', omnlSensitiveRouteGuard, as
|
||||
ipsas: {
|
||||
monetaryLayerHints: registry.monetaryLayerHints,
|
||||
postingGuidance:
|
||||
'Reserve movements (M0) map to GL 1050; M1 liabilities to 2000/2100; settlement to 1000. Validate every journal with GET /api/v1/omnl/ipsas/validate-pair before POST /journalentries.',
|
||||
'IPSAS double-entry: every journal must debit one GL and credit a distinct GL (debitGlCode !== creditGlCode). M0→1050, M1→2000/2100, M2→2200, M3→2300, M4 suspense→1000, inter-office→1410/2410. Validate pairs via GET /api/v1/omnl/ipsas/validate-pair or POST /api/v1/omnl/ipsas/validate-pairs before POST /journalentries.',
|
||||
fineractDocs: 'https://omnl.hybxfinance.io/fineract-provider/swagger-ui/index.html',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -52,6 +52,9 @@ export function validateJournalPair(
|
||||
): { valid: boolean; ipsasRef?: string; memo?: string } {
|
||||
const d = debitGlCode.trim();
|
||||
const c = creditGlCode.trim();
|
||||
if (d === c) {
|
||||
return { valid: false };
|
||||
}
|
||||
const hit = registry.allowedJournalPairs.find(
|
||||
(p) => p.debitGlCode === d && p.creditGlCode === c
|
||||
);
|
||||
@@ -74,6 +77,9 @@ export function validateJournalPairWithMatrix(
|
||||
}
|
||||
const d = debitGlCode.trim();
|
||||
const c = creditGlCode.trim();
|
||||
if (d === c) {
|
||||
return { valid: false, source: 'none' };
|
||||
}
|
||||
if (matrix) {
|
||||
const hit = matrix.entries.find((e) => e.debitGlCode === d && e.creditGlCode === c);
|
||||
if (hit) {
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface JournalMatrixEntry {
|
||||
amount: number;
|
||||
narrative: string;
|
||||
ipsasRef: string;
|
||||
moneyLayer?: string;
|
||||
}
|
||||
|
||||
export interface JournalMatrixFile {
|
||||
@@ -16,6 +17,7 @@ export interface JournalMatrixFile {
|
||||
description?: string;
|
||||
source?: string;
|
||||
currencyCode?: string;
|
||||
doubleEntryRule?: string;
|
||||
}
|
||||
|
||||
export function loadJournalMatrixPath(): string {
|
||||
@@ -30,7 +32,23 @@ export function loadJournalMatrix(): JournalMatrixFile {
|
||||
if (!existsSync(p)) {
|
||||
throw new Error(`Journal matrix not found: ${p}. Set OMNL_JOURNAL_MATRIX_PATH.`);
|
||||
}
|
||||
return JSON.parse(readFileSync(p, 'utf8')) as JournalMatrixFile;
|
||||
const matrix = JSON.parse(readFileSync(p, 'utf8')) as JournalMatrixFile;
|
||||
const violations = validateMatrixDoubleEntry(matrix);
|
||||
if (violations.length > 0) {
|
||||
throw new Error(`IPSAS journal matrix invalid: ${violations.join('; ')}`);
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/** IPSAS requires distinct debit/credit GL per line (true double entry). */
|
||||
export function validateMatrixDoubleEntry(matrix: JournalMatrixFile): string[] {
|
||||
const errors: string[] = [];
|
||||
for (const e of matrix.entries) {
|
||||
if (e.debitGlCode.trim() === e.creditGlCode.trim()) {
|
||||
errors.push(`${e.memo}: debit and credit GL must differ (${e.debitGlCode})`);
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
/** Union of IPSAS registry pairs and matrix-derived pairs for validation. */
|
||||
|
||||
Reference in New Issue
Block a user