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

   Four defects in the family member form, three of them in what the operator 
is told and one in what gets saved. The last one is the reason to read this.
   
   ## 1. An unchanged edit replaced the birth date with 2 December 1901
   
   `GET /clients/{id}/familymembers/{memberId}` answers:
   
   ```json
   
{"id":1,"firstName":"…","relationshipId":27,"dateOfBirth":"2026-10-31","age":0,"isDependent":false}
   ```
   
   A string. The form read that value as `[year, month, day]`:
   
   ```ts
   const dob = data.dateOfBirth as unknown as number[];
   this.dateOfBirth = new Date(dob[0], dob[1] - 1, dob[2]);
   ```
   
   Indexing a string takes its characters, so that is `new Date('2', '0' - 1, 
'2')` — year 2, month −1, day 2 — which resolves to **2 December 1901**. 
Opening a member and pressing Save without touching anything sent `"02 December 
1901"` and the platform stored it.
   
   Nothing on screen showed it. The picker has bound the right day since #841, 
because that path narrows the value through `toIsoFineractDate` first; only the 
`Date` behind the payload was wrong, and the picker is the only place the date 
is visible. The `Date` is now derived from the same narrowed ISO string the 
picker gets, so the day shown and the day saved cannot drift apart again.
   
   **Why no test caught it.** The mocked specs fixture this field as `[1980, 4, 
30]` — the shape much of the API does send, and the shape the form was written 
for. Against that fixture the old code is correct. This only exists where the 
platform is real, which is what the new backend spec is for: with the fix 
reverted it fails with
   
   ```
   Expected: "2026-10-31"
   Received: "1901-12-02"
   ```
   
   and the unit case, now parametrised over both payload shapes, fails with 
`'08 September 1901'`. #653 is the general case of this disagreement.
   
   ## 2. Save was disabled with nothing to explain it
   
   First name, last name and relationship carry `required`, and Save is 
disabled until the form is valid — but nothing said which of eleven fields was 
holding it, so the button read as broken. Marked with the same 
`required-marker` asterisk, per-field `role="alert"` note and 
`COMPLETE_REQUIRED_FIELDS` hint the client form already uses.
   
   The markers sit flush against the label text rather than on their own line, 
because `ionSelect()` matches `^<label>\*?$` and a space before the asterisk 
puts that shared helper out of reach for the form.
   
   ## 3. The picker offered birth dates the platform refuses
   
   Unbounded, it opened on the current month with every day selectable — 
today's and the rest of the year included. It is bounded at the platform's 
today now, resolved through `PlatformDateService` rather than the browser's 
clock: for a tenant east of the operator those are different days for part of 
every day, which is the defect behind #758.
   
   ## 4. Nothing confirmed the save
   
   Both commands navigated away and logged any refusal to a console the 
operator cannot see. They go through `MutationOutcomeService` now (#798), which 
toasts the outcome and handles a maker-checker queue, and a refusal leaves the 
form usable instead of stuck behind a disabled Save — `errorInterceptor` 
already toasts what the platform said. The client id is passed explicitly, 
because the `resourceId` these commands return is the *member's*, and routing 
on it would open an unrelated client.
   
   ## What the platform actually enforces — worth knowing before trusting any 
of it to the server
   
   `ClientFamilyMemberCommandFromApiJsonDeserializer` declares three rules — a 
mandatory first name, a mandatory relationship, a birth date no later than the 
business date — collects every breach into a `DataValidatorBuilder`, and then 
**never calls `throwExceptionIfValidationWarningsExist`**. The collected errors 
are discarded. Measured against a real Fineract, in the backend spec:
   
   | request | answer |
   | --- | --- |
   | no `firstName` at all | **200**, stored with an empty one |
   | birth date one day after the platform's own | **200** |
   | no `relationshipId` | **403** `null value in column "relationship_cv_id" … 
violates not-null constraint` |
   
   So the only rule anything enforces is a NOT NULL constraint, and what 
reaches the operator is the database's sentence about a column rather than a 
message naming a field. That is both halves of the case for §2 and §3: the 
form's rules are the only usable guard, not a duplicate of the platform's. 
**This looks like a platform bug worth a FINERACT ticket** — the validator's 
results being dropped is almost certainly not deliberate — and it is not 
fixable from here.
   
   Also measured: a stock Fineract ships the `RELATIONSHIP` code with **no 
values**, so the one field the form requires cannot be filled at all until a 
tenant adds one. The backend spec seeds one rather than asserting on it, since 
an empty reference list is the tenant's state and not this form's bug.
   
   Deliberately not changed: the form sends `middleName`, `qualification` and 
`mobileNumber` as empty strings when untouched, which looks worth trimming 
until you read `JsonCommand.stringValueOfParameterNamed`, which defaults a 
missing parameter to `""` anyway. Omitting them would change the payload and 
nothing else.
   
   ## Tests
   
   - `client-family-member-backend.spec.ts`, new, in `BACKEND_SPECS` — four 
cases against a real Fineract: a member added through the form is stored and 
listed, an unchanged edit leaves its birth date alone, and the three 
measurements in the table above. No date is hardcoded and no clock is read: the 
picker's bound is taken off the element and the day after it is posted, so the 
two are compared rather than both assumed — a spec that assumed either would 
fail in the evening window for the wrong reason.
   - `client-family-member-create.spec.ts`, new, mocked — the required-field 
guidance, and the picker refusing the day after the platform's own. The 
business date is mocked to `2026-10-15`, which no runner's clock will be on, so 
a bound read from the browser fails at every hour rather than only in the 
window where the two disagree.
   - 7 unit cases added; the unchanged-edit case is parametrised over both 
payload shapes.
   
   ## Checks
   
   - unit suite 320 files / 2741 tests green; the form's own file green at 
`UTC`, `America/New_York` and UTC+14
   - backend spec 4/4 against the local stack; mocked family specs 6/6 
(together and separately)
   - `lint` 0 errors with `eslint-suppressions.json` unchanged, `format:check` 
clean on tracked files, `tsc` app + e2e clean
   - `i18n:check` consistent, `fr` and `hi` carry the two new keys; 
`api:surface` 145 services / 644 operations; `ga:check` 10/11, 0 blocking
   - one intermittent NG0100 appeared once on the new `attr.aria-invalid` 
bindings and did not recur across four later runs. It is the interval-sampling 
shape `e2e/fixtures.ts` analyses for #572 — `touched` flipping inside an event 
handler just before the render task registers — on the same binding pattern the 
client form already uses. Reported, not enforced; mentioned because it was seen.
   


-- 
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