shivaansh0610-LUFFY opened a new pull request, #650:
URL: https://github.com/apache/fineract-backoffice-ui/pull/650
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<!-- Commits must be signed to merge — see CONTRIBUTING.md#commit-signing if
you haven't set this up. -->
## What and why
<!-- One or two sentences explaining what changed and why. -->
A regular loan's Delinquency tab could only be read, while a Working Capital
loan has a form for delinquency actions, so an institution could pause
delinquency on one kind of loan and not on the other. Pausing is how arrears
stop ageing during an agreed payment holiday or moratorium; without it those
accounts keep moving through arrears buckets they should not be in. This adds
**Pause Delinquency** and **Resume Delinquency** to the tab.
Closes #505
### Only pause and resume, not the Working Capital set
The issue points at the Working Capital form as the specification. That form
submits seven actions (pause, resume, reschedule, reset, undo_reset, disable,
enable), but `POST /loans/{id}/delinquency-actions` accepts exactly two,
`pause` and `resume` (`DelinquencyActionParseAndValidator` in Fineract).
Anything else is answered with
`validation.msg.loanDelinquencyAction.action.invalid.action`, so offering the
longer set would be a list of ways to get a 400. For the same reason the two
screens do not share a component: the Working Capital one is a routed page with
fields a regular loan has no use for, and the rules differ (a regular loan's
resume must be dated the business date and carry no end date).
### The permission is `CREATE_DELINQUENCY_ACTION`
The issue names `UPDATEDELINQUENCY_LOAN`, which is the permission for
assigning a loan to a delinquency bucket (`assignDelinquency`). The
delinquency-actions endpoint calls
`validateHasCreatePermission("DELINQUENCY_ACTION")`, so the buttons are gated
by `CREATE_DELINQUENCY_ACTION` (seeded by Fineract changelog 0130). Gating on
the other one would show the actions to people the platform then refuses, and
hide them from people it would accept.
### Changes
- **Pause Delinquency** opens a dialog for the period. The start defaults to
the platform's business date, not the browser's clock, which differs on a
tenant whose business date has moved. The only local rule is that the pause
ends after it starts, which is also the platform's rule ("must last at least a
day"). Everything else the platform enforces (active loan, start not before
disbursement, no overlap with another pause) depends on state the dialog does
not hold, so the platform's own message is shown.
- **Resume Delinquency** asks for confirmation, naming the date, and sends
`action: resume` dated the business date with no end date. The platform
requires exactly that and answers an `endDate` with a 400.
- Both appear only on an active loan, and only to someone holding
`CREATE_DELINQUENCY_ACTION`.
- Resume is offered only when a pause is in effect **and still has days left
after today**. A period's `active` flag includes its last day, and a resume
moves the pause's end to the resume date, so straight after resuming the period
still reads as active until the business date moves on. Offering Resume then
would be an offer the platform refuses ("There is an existing Resume
Delinquency Action on this date"). Without a business date the tab does not
offer Resume at all, rather than dating it by guesswork.
- After a successful action the tab tells the loan view, which re-reads the
loan the summary comes from.
- The business date is read in the background with the error toast skipped:
it only dates a pause and decides whether Resume can be offered, so its failure
must not put the tables into their error state, and the header already reports
its own.
- `en.json` gets the new `LOANS.*` keys, and `api-surface.json` records the
new call.
### Also on this tab
The tab read each pause period's dates from `startDate` / `endDate`, but
Fineract sends `pausePeriodStart` / `pausePeriodEnd`, so against a real
platform the pause table had blank dates and showed `true` / `false` as the
status. Seeing the result of a pause depends on that table, so it is fixed
here. Fineract also sends `LocalDate` as `[year, month, day]` (the generated
model says string), so every other date on the tab printed as `2026,10,15`; the
summary and tag dates now go through the same reader. It is the same screen and
the same cause, but it is separable if you would rather have it as its own
change.
### Not in this PR
- Reschedule, reset, undo reset, disable and enable: the platform has no
such actions for a regular loan.
- A real-Fineract e2e spec (see Verification).
## Verification
<!-- List what you ran and what you checked. Note whether the UI was
exercised with mocks, a real Fineract backend, or both. -->
Exercised against mocks. **Not run against a real Fineract**, so there is no
`backend` project spec in this PR; the rules above come from Fineract's source
and its Pause Delinquency documentation, not from a live platform. The mocks
use the shapes Fineract actually sends (date arrays, `pausePeriodStart` /
`pausePeriodEnd`) and compute a period's `active` with the platform's rule, so
a pause starting today is in effect today and a resumed pause stays in effect
until the date moves on.
- `npm run lint`, `format:check`, `i18n:check`, `check:icons`,
`check:a11y-names`, `check:ui-primitives`, `check:responsive`, `api:surface`,
`typecheck:e2e`, `build`: pass
- `npm run test:unit`: 266 files, 1753 tests pass. New coverage: the date
reader and the resume rule, the pause dialog, and the tab (permission and
loan-state gating, the request sent for pause and for resume, nothing sent when
cancelled, a refused request reporting no change, no Resume without a business
date).
- `e2e/loan-delinquency-actions.spec.ts` (new, mocked): 11 tests pass. Dates
read as dates under the real fields; pause from the business date with the
request asserted; a pause that ends on or before its start is refused in the
dialog; cancel sends nothing; a platform refusal shows its own reason and
leaves the screen usable; resume sends the business date and no end date;
Resume goes once the pause has been ended; no Resume when nothing is paused; no
actions without `CREATE_DELINQUENCY_ACTION`; none on a closed loan. Run
together with `loan-servicing-gaps.spec.ts`: 29 passed.
- axe on the pause dialog (with its error showing) and on the action
buttons: no violations once the dialog's open animation has finished.
## Screenshots
<!-- Add screenshots or a short recording for UI changes. Write "Not
applicable" for non-UI changes. -->
Same script, same mocked data, before (`main`) and after.
### Before
<img width="1440" height="1100" alt="before-01-delinquency-tab"
src="https://github.com/user-attachments/assets/0f322554-7b7f-4ec1-b6a1-5f83f64b4932"
/>
https://github.com/user-attachments/assets/0d25a24d-b61a-4622-b35e-26017565bd92
### After
<img width="1440" height="1100" alt="after-02-pause-dialog"
src="https://github.com/user-attachments/assets/843625ab-c081-4943-9587-09c84586a9cd"
/>
<img width="1440" height="1100" alt="after-03-pause-dialog-end-before-start"
src="https://github.com/user-attachments/assets/4ecc5998-f6a4-420c-b359-74b5f1d56b84"
/>
<img width="1440" height="1100" alt="after-04-pause-dialog-filled"
src="https://github.com/user-attachments/assets/9c74af79-def7-4511-ad0c-994ab7c030bc"
/>
<img width="1440" height="1100" alt="after-05-after-pause"
src="https://github.com/user-attachments/assets/5934e25a-4ce9-4910-a690-006fd303d278"
/>
<img width="1440" height="1100" alt="after-06-resume-confirmation"
src="https://github.com/user-attachments/assets/ed17c9b5-41cd-402a-8db2-bf8789d1dded"
/>
<img width="1440" height="1100" alt="after-07-after-resume"
src="https://github.com/user-attachments/assets/30bcd0d4-3061-42b3-bb62-a28d3e276764"
/>
<img width="1440" height="1100" alt="after-08-without-permission"
src="https://github.com/user-attachments/assets/7ce5e0c7-5dfc-4c0f-be0e-7a6a2488466d"
/>
<img width="1440" height="1100" alt="after-01-delinquency-tab"
src="https://github.com/user-attachments/assets/270506f0-8335-4788-be88-4b8f42632e79"
/>
https://github.com/user-attachments/assets/3a5b9d90-5dc9-476b-a5ce-49f9d6f0d00a
### Recording
https://github.com/user-attachments/assets/f4f4af12-9100-4ef8-97a2-77007557a41b
## AI assistance (optional)
<!-- If generative AI materially assisted this contribution, optionally
state the tool or model and
the harness or workflow used. The contributor remains responsible for the
submitted change. -->
- Tool / model:
- Harness / workflow:
## Checklist
<!-- Check each item, or explain why it does not apply. -->
- [x] I did not hand-edit generated files under `src/app/api/`.
- [x] New component or service code uses the adapter boundary in
`src/app/core/adapters/` instead of direct browser globals or imperative
third-party APIs.
- [x] User-facing strings use translation keys.
- [x] I added or updated tests appropriate to this change, or explained why
tests were not needed.
- [ ] UI workflow changes include suitable e2e coverage, including
real-backend testing where relevant. (Mocked e2e added; no real-backend spec,
see Verification.)
- [ ] Commits are signed — see [Commit
Signing](CONTRIBUTING.md#commit-signing) in CONTRIBUTING.md.
- [ ] I followed the [AI-assisted contributions
guidance](CONTRIBUTING.md#ai-assisted-contributions).
--
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]