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]