FINERACT-2081: Fix Fineract writer hung infinte loop after merchant issued refund

This commit is contained in:
Adam Saghy
2025-02-21 17:21:50 +01:00
parent 5137fd65af
commit 259eb75d81
7 changed files with 221 additions and 165 deletions
@@ -2250,6 +2250,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
lastPeriod.setEmi(lastPeriod.getDuePrincipal().add(totalPrincipal).add(newInterest));
emiCalculator.calculateRateFactorForRepaymentPeriod(lastPeriod, transactionCtx.getModel());
transactionCtx.getModel().disableEMIRecalculation();
for (LoanTransaction processTransaction : transactionsToBeReprocessed) {
emiCalculator.addBalanceCorrection(transactionCtx.getModel(), processTransaction.getTransactionDate(),
@@ -182,20 +182,25 @@ public final class ProgressiveEMICalculator implements EMICalculator {
return;
}
Optional<RepaymentPeriod> repaymentPeriod = findRepaymentPeriod(scheduleModel, repaymentPeriodDueDate);
boolean transactionDateIsBefore = transactionDate.isBefore(repaymentPeriod.get().getFromDate());
repaymentPeriod.ifPresent(rp -> rp.addPaidPrincipalAmount(principalAmount));
// If it is paid late, we need to calculate with the period due date
LocalDate balanceCorrectionDate = DateUtils.isBefore(repaymentPeriodDueDate, transactionDate) ? repaymentPeriodDueDate
: transactionDate;
addBalanceCorrection(scheduleModel, balanceCorrectionDate, principalAmount.negated());
repaymentPeriod.ifPresent(rp -> {
// If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as
// fully paid
if (rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusChargeback())) {
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalChargebackAmount()));
if (scheduleModel.isEMIRecalculationEnabled()) {
repaymentPeriod.ifPresent(rp -> {
// If any period total paid > calculated EMI, then set EMI to total paid -> effectively it is marked as
// fully paid
if (transactionDateIsBefore && rp.getTotalPaidAmount().isGreaterThan(rp.getEmiPlusChargeback())) {
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalChargebackAmount()));
} else if (transactionDateIsBefore
&& rp.getTotalPaidAmount().isEqualTo(rp.getOriginalEmi().add(rp.getTotalChargebackAmount()))) {
rp.setEmi(rp.getTotalPaidAmount().minus(rp.getTotalChargebackAmount()));
}
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
}
});
});
}
}
private void addChargebackAmountsToInterestPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
@@ -357,7 +362,6 @@ public final class ProgressiveEMICalculator implements EMICalculator {
interestPeriod.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
interestPeriod.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
}
repaymentPeriod.getInterestPeriods().subList(nextIdx, repaymentPeriod.getInterestPeriods().size()).clear();
scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().removeIf(ip -> ip.getDueDate().isAfter(targetDate)));
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
@@ -374,7 +378,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
final List<RepaymentPeriod> relatedRepaymentPeriods = scheduleModel.getRelatedRepaymentPeriods(calculateFromRepaymentPeriodDueDate);
final boolean onlyOnActualModelShouldApply = scheduleModel.isEmpty()
|| operation.getAction() == EmiChangeOperation.Action.INTEREST_RATE_CHANGE || scheduleModel.isCopiedForCalculation();
|| operation.getAction() == EmiChangeOperation.Action.INTEREST_RATE_CHANGE || scheduleModel.isCopy();
calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
calculateOutstandingBalance(scheduleModel);
@@ -0,0 +1,23 @@
/**
* 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.
*/
package org.apache.fineract.portfolio.loanproduct.calc.data;
public enum LoanInterestScheduleModelModifiers {
EMI_RECALCULATION, COPY,
}
@@ -19,6 +19,8 @@
package org.apache.fineract.portfolio.loanproduct.calc.data;
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isInPeriod;
import static org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.COPY;
import static org.apache.fineract.portfolio.loanproduct.calc.data.LoanInterestScheduleModelModifiers.EMI_RECALCULATION;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
@@ -56,7 +58,7 @@ public class ProgressiveLoanInterestScheduleModel {
private final Integer installmentAmountInMultiplesOf;
private final MathContext mc;
private final Money zero;
private final boolean isCopiedForCalculation;
private final Map<LoanInterestScheduleModelModifiers, Boolean> modifiers;
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
@@ -68,7 +70,7 @@ public class ProgressiveLoanInterestScheduleModel {
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.mc = mc;
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
this.isCopiedForCalculation = false;
modifiers = new HashMap<>(Map.of(EMI_RECALCULATION, true, COPY, false));
}
private ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods, final TreeSet<InterestRate> interestRates,
@@ -83,7 +85,7 @@ public class ProgressiveLoanInterestScheduleModel {
this.loanTermVariations = loanTermVariations;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
this.isCopiedForCalculation = isCopiedForCalculation;
modifiers = new HashMap<>(Map.of(EMI_RECALCULATION, true, COPY, isCopiedForCalculation));
}
public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) {
@@ -405,4 +407,16 @@ public class ProgressiveLoanInterestScheduleModel {
return loanTermVariationsData.stream()
.collect(Collectors.groupingBy(ltvd -> LoanTermVariationType.fromInt(ltvd.getTermType().getId().intValue())));
}
public void disableEMIRecalculation() {
this.modifiers.put(EMI_RECALCULATION, false);
}
public boolean isEMIRecalculationEnabled() {
return this.modifiers.get(EMI_RECALCULATION);
}
public boolean isCopy() {
return this.modifiers.get(COPY);
}
}