FINERACT-2455: Add final discount fee amortization on charge-off and reversal - #6281
Conversation
d704146 to
17a622d
Compare
17a622d to
8e91fd4
Compare
galovics
left a comment
There was a problem hiding this comment.
Went back over this one more carefully given the size and the financial logic involved, and I think there are real gaps in the charge-off/reversal symmetry.
1. Charge-off lifted via reprocessing never reverses the final amortization
WorkingCapitalLoanTransactionReprocessingServiceImpl#replayChargeOff:
if (!MathUtil.isGreaterThanZero(chargeOffAmount)) {
chargeOffTransaction.setReversed(true);
...
loan.liftChargeOff();
...
return false; // <-- returns before restateFinalDiscountFeeAmortization runs
}
...
restateFinalDiscountFeeAmortization(loan, balance, chargeOffTransaction, accountingEnabled);When a backdated repayment/goodwill/payout-refund zeroes the outstanding, the charge-off is auto-lifted, but the linked final discount-fee amortization survives un-reversed - its journal entries keep crediting the charge-off expense account on a loan that's no longer charged off. undoChargeOff (the explicit path) does reverse it via undoDiscountFeeAmortizationOnChargeOff; this path doesn't. Every lift scenario in the feature file uses discount | 0, so nothing currently exercises a lift with a non-zero discount pool - worth adding one, and it should catch this once fixed.
2. Apply-adjustment restates, undo-adjustment doesn't
makeDiscountFeeAdjustment reprocesses transactions when the loan is charged off; undoDiscountFeeAdjustment doesn't have the same call. So on a charged-off loan, applying a backdated adjustment shrinks the final amortization, but undoing that adjustment restores the discount pool while leaving the amortization shrunk - and since DiscountFeeAmortizationBusinessStep now skips charged-off loans, there's no COB path to reconcile the difference either. New scenario UC51 actually asserts this stale state as "correct" (discount back to 1000, amortization still 750) - it only looks fine because the scenario fully repays and closes the loan afterward, which happens to post the missing amount on close. A charged-off loan that's never repaid would strand that amount permanently.
3. One-way door once reversed
The relation lookup used by the restate path filters on reversed = false, so once a final amortization has been reversed by a pool drop to zero, a later undo of that adjustment can never link back to it and restore it. Combined with #2 this means any deferred-income residual on a charged-off loan has no path back onto the ledger.
4. UC49's reversed-charge-off table doesn't actually assert the reversal
Then Working Capital Loan Transactions tab has a reversed "CHARGE_OFF" transaction ...
| EXPENSE | ... | Credit Loss/Bad Debt-Fraud | 10000.0 | |
| ASSET | ... | Loans Receivable | | 10000.0 |
| EXPENSE | ... | Credit Loss/Bad Debt-Fraud | 10000.0 | | <- duplicate, should flip sides
| ASSET | ... | Loans Receivable | | 10000.0 | <- duplicate, should flip sides
The mirror entries should flip debit/credit sides (as UC48 and the pre-existing UC2/UC4 correctly do), but the step def only checks row containment + count, so a duplicated row passes silently and the actual reversal legs go unverified.
5. No business events on the new lump-sum transaction, its reversal, or its restatement - processFinalDiscountFeeAmortizationOnChargeOff, undoDiscountFeeAmortizationOnChargeOff, and restateFinalDiscountFeeAmortization all publish nothing, unlike the periodic amortization path.
The rounding/precision side is fine - no division anywhere in the new path, the final amount is always a residual (pool - realized), so it self-corrects against periodic drift. That part I have no complaints about.
Recommendation: CHANGES_REQUESTED
Description
Describe the changes made and why they were made. (Ignore if these details are present on the associated Apache Fineract JIRA ticket.)
Checklist
Please make sure these boxes are checked before submitting your pull request - thanks!
Your assigned reviewer(s) will follow our guidelines for code reviews.