Aman-Mittal opened a new pull request, #656:
URL: https://github.com/apache/fineract-backoffice-ui/pull/656

   First migration under ADR 0006 since #651 established the boundary. 
Follow-up to #653.
   
   ## Why notes
   
   Unlike the two domains in #651, this one corrects **no disagreement** 
between the generated type and the payload. Notes agree: `createdOn` is 
declared `string` and really is an ISO-8601 timestamp with offset 
(`2026-10-02T14:13:53.322357+05:30`), verified against a running instance. I 
checked before writing a mapper, and there is nothing to convert.
   
   It earns its place on two other grounds.
   
   **Reach.** Notes are read from client, group, loan and savings screens — 
more consumers than most domains — and Fineract serves all four from one 
templated path, `/{resourceType}/{resourceId}/notes`.
   
   **One type that was wrong in the same way twice.** Both the generated client 
and `EntityNotesComponent.resourceType` typed that path segment as a bare 
`string`:
   
   ```ts
   readonly resourceType = input.required<string>();
   ```
   
   Only four values are ever passed. A typo in a fifth caller was a runtime 404 
on a tab that renders empty — no error, no failing test. `NoteResourceType` is 
a closed union of the four, so the same mistake is now a compile error.
   
   ## What fell out of it
   
   Two incidental findings, both the kind of thing a contract quietly removes:
   
   **A workaround whose cause is the generated overloads.** `group-note-form` 
carried this:
   
   ```ts
   // Typed as `Observable<unknown>`: create and update resolve to different 
response models,
   // and the union of the two overload sets has no callable `subscribe`.
   const save$: Observable<unknown> = …
   ```
   
   Both operations are `Observable<void>` on the contract, so the comment and 
the widening are gone.
   
   **A fixture that matched nothing.** `client-notes-list`'s spec had 
`createdOn: 1_757_000_000_000` — epoch millis, which is neither what the 
generated type declares (`string`) nor what Fineract sends (an ISO timestamp). 
It reached the component through an `as unknown as Observable<never>` cast, so 
nothing checked it. A typed fixture has to be a real value, and now is one.
   
   `client-note-form` also held the generated request object in a signal and 
let the template write into it (`[(ngModel)]="note().note"`). It holds the text 
now; the body shape is the adapter's business.
   
   ## Scope
   
   `EntityNotesApi` has all five of the generated service's operations, because 
all five have callers — the tabs list and delete, and both note forms load one 
note by id to edit it. Seven files lose their generated-client import:
   
   - `shared/components/entity-notes/entity-notes.component.ts`
   - `features/clients/tabs/client-notes-list.component.ts` + spec
   - `features/clients/kyc/client-note-form.component.ts` + spec
   - `features/groups/tabs/group-notes-list.component.ts`
   - `features/groups/group-note-form.component.ts`
   
   `client-view.component.ts` still calls `NotesService` directly. It injects a 
dozen other generated services, so migrating its notes usage alone would not 
clear its suppression; left for whenever that screen is taken on as a whole.
   
   **Ratchet: 469 → 462.** Other boundaries untouched at their current counts, 
which is what the separate rule id is for:
   
   ```
   no-restricted-imports          235   unchanged
   local/no-generated-api-import  469 → 462
   local/no-vendor-ui-import      227   unchanged
   no-restricted-globals           15   unchanged
   unicorn/no-array-sort           14   unchanged
   ```
   
   ## The arithmetic, stated plainly
   
   ADR 0006 now records this, because it sets the expectation for everything 
after it:
   
   > Three domains, seven files cleared. 462 files still import the client, and 
361 of them depend on exactly **one** generated service — so the work is 
tractable, but the distribution is flat. The largest single domain left is 
**nine files**.
   
   This is a long campaign of small PRs, not something one change finishes. The 
ratchet exists so it can proceed at that pace without the number going back up. 
I also looked at `DefaultService`, which has the most single-domain files (9) — 
it is a catch-all spanning email campaigns, SMS, office transactions, external 
events and 2FA, so a contract over it would be incoherent. Not worth doing, and 
worth saying so rather than chasing the count.
   
   ## Checks run
   
   | Check | Result |
   | --- | --- |
   | `npm run lint` | pass |
   | `npm run test:unit` | pass |
   | `npm run i18n:check` | pass |
   | `npm run check:template-text` | pass |
   | `npm run format:check` | pass |
   | `bash scripts/check-license.sh` | pass |
   | `npx tsc --noEmit -p tsconfig.app.json` | pass |
   
   New adapter spec: 14 tests, built from the payload captured off a live 
instance rather than from the generated type — the distinction that let #651's 
"Open" bug survive its own unit test.
   
   No E2E run locally; CI's sharded jobs are the authority, and running three 
projects at once on one machine produced timeouts that CI does not.
   
   ## Risk
   
   - **No behavioural change intended.** Same endpoints, same order, same 
payloads. The one visible difference would be if a screen passed a 
`resourceType` outside the four-value union — that is now a compile error 
rather than an empty tab, and nothing in the tree does.
   - `mapEntityNote` throws on a note with no `id`, because delete and update 
both post it back and defaulting would act on the wrong row. Fineract always 
sends one.
   - An empty `note` maps to `''` rather than throwing: Fineract permits it, 
and one odd row should not take down the tab.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to