Skip to content

Generated models disagree with the payloads Fineract sends: dates typed string arrive as [y, m, d], and some fields are undeclared #653

Open

Description

@Aman-Mittal

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): string

Note 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:61

So 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

  1. shivaansh0610-LUFFY commented on Oct 2, 2026

    @shivaansh0610-LUFFY
    Contributor

    @Aman-Mittal
    Working on this issue.

  2. Aman-Mittal commented on Oct 2, 2026

    @Aman-Mittal
    MemberAuthor

    @Aman-Mittal Working on this issue.

    Hold on im working on this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    apibugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions