Aman-Mittal opened a new pull request, #803:
URL: https://github.com/apache/fineract-backoffice-ui/pull/803
Closes #754, #778, #779.
Three issues from the loan servicing backlog, all the same shape: a command
the platform accepts, with no caller anywhere in `src/app`. Each was measured
against a running `apache/fineract` before it was written, which is how four of
the assumptions below turned out to be wrong.
## What landed
**#778 — undo a re-age or a re-amortization.** Both now sit in the Actions
menu behind a destructive confirmation that says the schedule reverts. Each
appears only while its own transaction stands unreversed: the platform reverses
the most recent one and an undone re-age keeps its row with `manuallyReversed`
set, so "the loan has a reAge transaction" would keep offering an Undo that can
only answer 403. The latest is read by id rather than by position — nothing in
the payload promises an order, and the date is the value date, which a
back-dated correction can place before an earlier entry.
This also settles `reAmortize`, which `command-permissions.ts` had left
deliberately unmapped because the first probe read it as "400 at one shape, 403
at the form's". Re-probed against the endpoint the form posts to, with a role
holding `REAMORTIZE_LOAN` and nothing else, the platform answers 404
`error.msg.loan.id.invalid` — past authorisation, failing only on the absent
loan. Three commands remain unmapped.
**#779 — post and adjust buy-down fees and capitalized income.** Both
postings are in the Actions menu, shown only when the loan reports the
capability, because a product that did not enable the feature refuses the
command outright. The buy-down fee tab gets a row action for the adjustment,
addressed by the row's `transactionId`.
**#754 — pay, waive and adjust a charge on a loan, savings account or
client.** Per-row actions on all three charge lists, each gated on its own code
and on the charge's state. Before this a charge could be added and deleted and
nothing else: deletion is refused once the charge has been paid or partly paid,
and it leaves no record that a fee was forgiven.
## Four places the issues were wrong about the platform
**The deferred-income adjustments are not commands at loan level.** #779
expected `POST /loans/{id}/transactions?command=buyDownFeeAdjustment`. The
platform answers `error.msg.query.parameter.value.unsupported` on `command`
there — the #268 discriminator for a command it does not recognise at all. Both
are recognised on the per-transaction URL, and naming one switches the
permission as well as the operation: a role holding `BUYDOWNFEEADJUSTMENT_LOAN`
is refused the bare POST, while `ADJUST_LOAN` is authorised the bare POST and
refused the named command. So the adjustment belongs on the transaction detail
dialog, and the command, its code and its wording travel together as one value.
**A loan charge adjustment takes `amount` and literally nothing else.**
Sending the `locale` and `dateFormat` that every other command on that endpoint
requires makes this one a 400. The same quirk as the Working Capital charge
adjustment, so this is now the second time.
**Pay is narrower than having something outstanding.** The platform accepts
it only for a charge whose payment mode is account transfer: "This loan charge
cannot be paid as the charge payment mode is not account transfer". Regular is
the default, so gating Pay on the outstanding amount alone would have put a
button that can only fail on nearly every row. The payload carries the
platform's own answer as `chargePayable`, which is what the row asks.
**Inactivate is narrower still.** Allowed only for recurring charges —
`charge.inactivation.allowed.only.for.recurring.charges` — so an annual or
monthly fee and nothing else, and only while the charge is active. The payload
carries `isActive`, which the generated response type does not declare.
## One thing that could not be delivered
`PUT /loans/{loanId}/transactions/{transactionId}`, which the API document
titles **"Undo a Waive Charge Transaction"** and which #754 asks for, **answers
500 on every call.** Measured with an empty body, with the ids in the body,
with a transaction date and amount, and with each of `?command=undo`,
`?command=waive` and `?command=undoWaiveCharge`. The platform log shows
`java.util.NoSuchElementException: No value present` — an `Optional.get()` on
an empty optional inside the handler — so the request never reaches a business
rule.
The code for it is here and tested: the adapter method, the dialog control,
and the gate on `UNDO_WAIVECHARGE`, which was probed and is authorised by
neither `ADJUST_LOAN` nor `WAIVE_LOANCHARGE`. The button is not shipped, behind
`UNDO_WAIVE_CHARGE_AVAILABLE` in `loan-view.component.ts`, because a control
whose every click ends in an error toast is the dead-end control of #691 and
#751 reached from a third direction. `e2e/charge-servicing-backend.spec.ts`
asserts the 500, so the day the platform is fixed a test says so and that
constant is the one line to flip. **This needs an ASF Jira issue against
Fineract.**
## Where the forms live, and why
Two of the three loan charge commands need a value before they can be
issued, and they are issued from list rows that have no form on them. Rather
than six near-identical dialogs or three forms, `ConfirmDialogComponent` gained
optional fields and `DialogService` gained `prompt`.
That is where they had to go. ADR 0005 forbids a new file importing
`@ionic/angular/standalone`, there is no form primitive in `src/app/ui` yet
(#287, #684), and `eslint-suppressions.json` may only shrink — so the fields
went where the buttons already were. A dialog with fields dismisses with the
values and a cancelled one still with `false`, which is what lets `confirm`
keep reading `=== true` while `prompt` cannot mistake a cancel for an empty
answer. Required fields are checked before the request rather than after, so a
blank amount is a disabled button instead of a validation toast.
This is the second batch to run into the missing form primitives. It is
worth scheduling #684.
## RBAC
Eighteen command-to-code pairings, each probed with a role holding exactly
one permission, and the three portfolio write codes — `UPDATE_LOAN`,
`UPDATE_SAVINGSACCOUNT`, `UPDATE_CLIENT` — probed against all eighteen. Each
authorises none of its own entity's commands, which is #691 and #777 one layer
down. `e2e/loan-servicing-rbac-backend.spec.ts` re-measures the whole diagonal
on every backend run.
The Actions menu items now bind their codes from `LOAN_COMMAND_PERMISSIONS`
instead of repeating them as literals, so the button and the route guard read
the same map and cannot drift.
## Also worth noting
#506 is already implemented on `main` — refund by cash and charge refund
both landed, and the stale comment the issue asks about has been settled
(`chargeRefund` is in `NO_TEMPLATE_TYPES`). It can be closed.
`permission.guard.test.ts` used `reAmortize` as its example of a
deliberately unmapped command; it is mapped now, so the example moved to one
whose probe is still inconclusive.
## Verification
- **2320 unit tests / 301 files**, all passing.
- **11 backend e2e** against `apache/fineract:latest` across the two new
specs, in 1 minute: the full permission diagonal, a loan charge waived and read
back from the ledger, the adjustment, the Pay gating, a savings charge paid,
and the active flag that gates Inactivate.
- All repo checks, `eslint` with **no new suppressions**, `prettier
--check`, `typecheck:e2e`, production build.
Two pre-existing flakes under full-suite parallel load, both passing in
isolation (`client-form.component.test.ts`,
`loan-delinquency-actions.spec.ts`), and `loan-schedule-type-gating.spec.ts`
fails on `main` in the mocked project in this environment — its login does not
survive the navigation — so no new assertions were added there.
--
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]