[
https://issues.apache.org/jira/browse/FINERACT-3006?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18133042#comment-18133042
]
Piotr Wargulak commented on FINERACT-3006:
------------------------------------------
Reproduced on stock develop ({{{}106816440a{}}}, PostgreSQL) with a plain
progressive product. Integration test: {{FeignLoanOverpaidAddFeeTest}}
(integration-tests, {{{}client/feign/tests{}}}), attached.
*Setup*
* Progressive product, {{{}advanced-payment-allocation-strategy{}}}, default
payment allocation, 6 monthly installments, EUR, no interest (also checked with
10% and interest recalculation: same result).
* Loan 1 000.00 disbursed 01 January 2026. Specified-due-date fees of 10.00
due 01 Feb, 01 Mar, 01 Apr, 01 May (installments 1–4).
* Repayment of 1 200.00 on 15 January 2026: principal 1 000.00, fees 40.00,
overpayment 160.00. Loan is Overpaid.
* On 15 March 2026, add a 1.00 specified-due-date fee due 15 March 2026
(inside installment 3, whose 10.00 fee the repayment already paid).
*Actual* (no accounting):
* {{totalOverpaid}} stays 160.00; {{summary.totalOutstanding}} 1.00;
installment 3 fee due 11.00, paid 10.00, outstanding 1.00; status Overpaid.
* The new fee reads paid (amountPaid 1.00, outstanding 0.00). The repayment is
unchanged (fee 40.00, overpayment 160.00); no new transaction.
* {{m_loan_charge_paid_by}} for the repayment: 10.00 / 10.00 / *9.00* / 10.00
for the four original fees, and *1.00* for the new fee. So the replay re-splits
the 10.00 already paid for installment 3 between its two fees (the new one
first, as its due date is earlier) instead of paying the new fee from the
overpayment. The original 10.00 fee is left with 1.00 outstanding.
*Actual* (periodic accrual accounting, {{accountingRule}} 3): adding the fee is
refused and rolled back:
{code:java}
403 Sum of charge credits (30.00) does not equal transaction total (29.00) for
loan <id>, transaction <id>
{code}
*Expected:* the new fee is paid from the overpayment: {{totalOverpaid}} 159.00,
nothing outstanding, as happens in the control cases below.
*Control cases* (all correct, with and without accounting, with and without
interest):
* the same fee due 10 January 2026, before the repayment;
* a penalty instead of a fee, due 15 March 2026;
* a fee due 15 May 2026, in installment 5, which has no fee.
*Deterministic* when the new fee's due date differs from the installment fee's
due date: 20 of 20 runs without accounting, 20 of 20 refused with accounting.
When both are due the same day (fee due 01 April 2026) the result varies
between runs (3 of 12 runs correct), because which of two same-day fees is paid
first is random (FINERACT-3007).
Run: build the image ({{{}./gradlew :fineract-provider:jibDockerBuild -x
test{}}}), start it with {{{}docker-compose-postgresql-test.yml{}}}, then
{{{}./gradlew :integration-tests:test --tests
'*FeignLoanOverpaidAddFeeTest'{}}}. On develop three tests fail (the two
no-accounting variants on the outstanding 1.00, the accounting variant on the
403) and three controls pass.
> A fee added to an overpaid progressive loan reads as paid, but no transaction
> pays it
> -------------------------------------------------------------------------------------
>
> Key: FINERACT-3006
> URL: https://issues.apache.org/jira/browse/FINERACT-3006
> Project: Apache Fineract
> Issue Type: Bug
> Reporter: Piotr Wargulak
> Priority: Major
> Attachments: FeignLoanOverpaidAddFeeTest.java
>
>
> Adding a specified-due-date fee to an overpaid progressive loan
> (advanced-payment-allocation-strategy) can leave the loan both owing and
> owed: the fee is marked paid, no transaction pays it, and the overpayment
> stays the same.
> Measured (one fee of 1.00, loan overpaid by 6 200.00, status 700):
> * the charge reads {{{}paid: true{}}}, {{amountPaid}} 1.00, outstanding
> 0.00, with a paid-by row on the old repayment;
> * that repayment is unchanged (its overpayment portion is still 6 200.00),
> no new transaction, no journal entry;
> * the loan stays Overpaid with {{totalOverpaid}} 6 200.00, while
> {{summary.totalOutstanding}} and the installment's fee outstanding read 1.00.
> Trigger, from eight variants:
> * BUG: a non-penalty fee, due after the overpaying payment, in an
> installment whose own fee is already paid.
> * OK: a fee due before the overpaying payment (the repayment is replaced,
> overpaid 6 199.00, ledger consistent); a penalty on the same date; a fee in
> an installment that had no fee; an active (not overpaid) loan with prepaid
> installments; a fee due the day after a refund of the overpayment.
> * Refund of the overpayment and the fee due the same day: the loan reopens
> with 1.00 outstanding, but the charge still reads paid by the old repayment.
> Steps to reproduce:
> # Progressive loan, no interest, an installment fee or specified-due-date
> fee in each installment.
> # Repay the whole loan plus an overpayment in one repayment (status 700
> Overpaid).
> # Add a 1.00 specified-due-date fee due after that repayment, in an
> installment whose fee the repayment paid.
> # Read the loan: the charge is paid, the repayment is unchanged,
> {{totalOverpaid}} is unchanged and {{totalOutstanding}} is 1.00.
> Where it goes:
> {{LoanChargeWritePlatformServiceImpl}} (:255-279) sets {{overpaidReprocess}}
> and runs a full replay;
> {{AdvancedPaymentScheduleTransactionProcessor#processOverpaidTransactions}}
> then records the fee as paid, but the amount is not taken from the
> overpayment, so {{useOldTransactionIfApplicable}} keeps the old repayment and
> copies only the paid-by mapping.
> Expected:
> the fee is paid from the overpayment (the repayment is replaced and the
> overpaid amount drops by 1.00), as it is for a fee due before the repayment.
> Workaround:
> refund the overpayment first and date the fee after the refund.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)