feat(products): correct a deposit or savings transaction after it is recorded - #341
Merged
Aman-Mittal merged 1 commit intoAug 14, 2026
Conversation
…recorded
The transactions tables on the savings and term deposit screens could be read
but never acted on. A teller who mis-keyed an amount had no reversal, and a
hold placed against a claim could never be released from this application —
money could be earmarked and then only freed outside the product.
Verified against a running Fineract rather than assumed. Probing the command
discriminator on each endpoint gives:
savings POST /savingsaccounts/{id}/transactions/{txId}
undo, modify, releaseAmount, reverse
RD POST /recurringdepositaccounts/{id}/transactions deposit, withdrawal
POST /recurringdepositaccounts/{id}/transactions/{txId} undo, modify
FD POST /fixeddepositaccounts/{id}/transactions deposit, withdrawal
POST /fixeddepositaccounts/{id}/transactions/{txId} undo, modify
What the discriminator does not say, and the platform does:
* A fixed deposit **refuses** a withdrawal. The command is enumerated but
answers 503 error.msg.fixeddepositaccount.account.trasaction.withdraw.notallowed.
The screen offered Withdraw on every term deposit and navigated to a route
that does not exist; it is now offered for recurring deposits only, where it
works. Money leaves a fixed deposit through maturity or premature closure.
* undo is lenient — it answers 200 against an already-reversed transaction and
against a hold or a release, doing nothing useful. releaseAmount is strict,
refusing a second release with validation.msg.amount.is.not.on.hold. So the
screens gate both themselves rather than offering a button that reports
success and changes nothing.
* A hold records releaseTransactionId as 0 while it stands, not as absent, so
the release gate tests for a truthy id rather than for the key.
The term deposit transactions tab was empty by construction. It read
account['transactions'], and neither GET /fixeddepositaccounts/{id} nor its
recurring twin returns that key unless asked. Fixed deposits have a list
endpoint and a generated method for it. Recurring deposits have neither — the
list path answers 405, and the upstream document describes no associations
parameter for either deposit type although it does for /savingsaccounts/{id} —
so that one read goes through the HttpClient escape hatch group-view already
uses, with the condition for deleting it recorded.
Reversed rows stay on screen, struck through, on both screens. Removing them
would take the audit trail with them.
Also adds the fixed-deposit transaction form, which did not exist. Its
generated operation is (accountId, command, body) where the recurring twin is
(accountId, request, command), and the body is typed string; a call copied from
the twin binds the request to command and still compiles, so the spec asserts
the positions.
Closes apache#298.
Covered by unit specs for the gating and the argument order, and by two
real-backend suites: a fixed deposit recorded and reversed, and a savings
deposit reversed with a hold released.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The transactions tables on the savings and term deposit screens could be read but never acted on. A teller who mis-keyed an amount had no reversal, and a hold placed against a claim could never be released from this application — money could be earmarked and then only freed outside the product.
Closes #298. Also closes the last two rows of the table in #197 (
holdAmount/releaseAmount, which was half-built: funds could be held but not freed).Verified against a running Fineract
Probing the command discriminator on each endpoint:
POST /savingsaccounts/{id}/transactions/{txId}undo,modify,releaseAmount,reversePOST /recurringdepositaccounts/{id}/transactionsdeposit,withdrawalPOST /recurringdepositaccounts/{id}/transactions/{txId}undo,modifyPOST /fixeddepositaccounts/{id}/transactionsdeposit,withdrawalPOST /fixeddepositaccounts/{id}/transactions/{txId}undo,modifyWhat the discriminator does not say, and the platform does:
503 error.msg.fixeddepositaccount.account.trasaction.withdraw.notallowed. The screen offered Withdraw on every term deposit and navigated to a route that does not exist. It is now offered for recurring deposits only, where it works — money leaves a fixed deposit through maturity or premature closure, both already on the menu.undois lenient. It answers200against an already-reversed transaction, and against a hold or a release, doing nothing useful.releaseAmountis strict, refusing a second release withvalidation.msg.amount.is.not.on.hold. So the screens gate both themselves rather than offering a button that reports success and changes nothing.releaseTransactionIdas0while it stands, not as absent, so the release gate tests for a truthy id rather than for the key.The term deposit transactions tab was empty by construction
It read
account['transactions'], and neitherGET /fixeddepositaccounts/{id}nor its recurring twin returns that key unless asked. So the tab showed nothing on every account regardless of activity.GET /recurringdepositaccounts/{id}/transactionsanswers405, and the upstream OpenAPI document describes noassociationsparameter for either deposit type — although it does for/savingsaccounts/{accountId}. That one read therefore goes through the sameHttpClientescape hatchgroup-viewalready uses, with the condition for deleting it recorded in the comment.Worth raising upstream:
associationsis honoured by the platform on both deposit account GETs but missing from the spec.Reversed rows stay visible
Struck through, marked, never removed — on both screens. Fineract flags
reversedrather than deleting, and dropping the row would take the audit trail with it. Same failure class as #280.The fixed-deposit transaction form
Did not exist. One trap worth flagging for reviewers: its generated operation is
(accountId, command, body)where the recurring twin is(accountId, request, command), and the body is typedstring. A call copied from the twin binds the request object tocommandand still compiles. The spec asserts the argument positions explicitly.Two more spec-versus-platform gaps handled locally, both commented:
paymentTypeOptionsis typedArray<number>but arrives as objects (binding the raw element would render[object Object]), andpaymentTypeIdis absent from the request model although the platform accepts it and echoes it back underchanges.Not touched
#279/#278overlap with open PR #335, which fixes the recurring-deposit route and command. Nothing here changes that file or the recurring-deposit navigation.Verification
npm run lint,format:check,check:icons,i18n:check,buildall clean.Checklist
src/app/api/.| appTranslate), per ADR-0003.en.jsonand verified by hand —scripts/check-translations.mjsdoes not match| appTranslate.