Repository navigation
Generated models disagree with the payloads Fineract sends: dates typed string arrive as [y, m, d], and some fields are undeclared #653
Description
Summary
The generated OpenAPI client is derived from a spec that under-describes several endpoints, so a generated type and the payload it describes disagree. The application absorbs the difference at each call site instead of once, and the cost is now measurable: 35 as unknown as number[] casts and 35 formatArrayDate(...) display-time conversions.
Two distinct disagreements, both verified against a running apache/fineract:latest (sha256:c1d4aee9, created 2026-10-01).
1. Dates typed string arrive as [year, month, day]
GET /offices declares:
export interface GetOfficesResponse {
openingDate?: string; // ← spec says string
// …
}and returns:
{"id": 1, "name": "Head Office", "externalId": "1",
"openingDate": [2009, 1, 1], "hierarchy": "."}Nine generated models declare an openingDate?: string. The application already knows the type is wrong — core/utils/date-formatter.ts exists specifically to cope:
/** Formats a Fineract array date (`[year, month, day]`) into a `YYYY-MM-DD` string… */
export function formatArrayDate(value: unknown): stringNote the parameter is unknown, because the declared type cannot be used. Fixtures cast in the other direction:
openingDate: [2026, 6, 16] as unknown as number[], // offices-list.component.test.ts:61So the mismatch is handled, but 35 times, at display time, in a helper that returns '-' on anything unexpected — which means a shape change upstream degrades to a dash on screen rather than a failure anywhere.
2. Fields the payload sends and the type does not declare
GET /offices returns parentId and parentName. GetOfficesResponse declares neither; it has allowedParents?: Array<GetOfficesResponse> instead. GET /glclosures returns allowedOffices, also undeclared.
A component reading office.parentId therefore only compiles where the value has flowed through an untyped context. That is the same mechanism behind the isClosed defect in #651: the closures list bound closure.isClosed through an untyped appCellTemplate context, no such field exists in the payload, and every closed period rendered as "Open" with strictTemplates on and unit tests passing.
Why this is worth fixing centrally
These are not generator problems, so ADR 0001 does not cover them — the method names are stable and correct. They are spec problems, and the spec is synced automatically by ADR 0002, so the disagreement arrives without a commit here. scripts/check-api-surface.mjs does not see them either: it records which operations are called, and every operation here exists and works.
What makes the current arrangement expensive is that the generated response type is the model components bind into templates, so each of the ~470 files that imports the client carries its own share of the correction. A conversion that lives in one mapper is tested once and cannot be forgotten at the 36th call site.
Suggested fix
Follow the boundary ADR 0006 establishes in #651, one domain at a time, starting where the leverage is highest:
| Domain | Consumers | What the mapper would settle |
|---|---|---|
Offices (OfficesService) |
40 files, 17 using GetOfficesResponse |
openingDate array → ISO string; declare parentId/parentName |
| Clients | client-form, KYC |
array dates |
| Loans | loan-view, loan-form, transactions, guarantors, rescheduling |
array dates |
An Office model exposing openingDate: string | null removes the cast and the formatArrayDate call from every office screen, and makes an upstream change to that field a failing mapper test rather than a dash in a table.
Alternative considered
Fix the spec upstream so openingDate is typed as it is sent. That is the right long-term answer and worth raising in FINERACT, but it does not help today: the spec is upstream, the sync is automatic, and the application still needs to work against the current one. The mapper is also not wasted if the spec is later corrected — it becomes a one-line passthrough.
Keep using formatArrayDate at display time. It works, and 35 call sites prove it. What it does not do is give the shape a single tested home, or let a type tell you when it changes.
Scope
Not urgent — nothing is visibly broken by the dates today, because formatArrayDate handles them. The undeclared-field half is different: that is how #651's "Open" bug happened, and it is silent.
Activity
shivaansh0610-LUFFY commentedon Oct 2, 2026
@Aman-Mittal
Working on this issue.
Aman-Mittal commentedon Oct 2, 2026
Hold on im working on this one
refactor(offices): convert Fineract's array dates once, not at every …
refactor(offices): convert Fineract's array dates once, not at every …
refactor(offices): convert Fineract's array dates once, not at every …
feat(loans): undo a re-age or a re-amortization