FINERACT-1981: Fix calculation new way when applies multi disbursement
This commit is contained in:
committed by
Adam Saghy
parent
81ed0260b1
commit
b4b9d3ea71
+9
-9
@@ -89,7 +89,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
final Money zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
|
||||
final AtomicReference<RepaymentPeriod> prev = new AtomicReference<>();
|
||||
List<RepaymentPeriod> repaymentPeriods = periods.stream().map(e -> {
|
||||
RepaymentPeriod rp = new RepaymentPeriod(prev.get(), from.apply(e), to.apply(e), zero, mc);
|
||||
RepaymentPeriod rp = RepaymentPeriod.create(prev.get(), from.apply(e), to.apply(e), zero, mc);
|
||||
prev.set(rp);
|
||||
return rp;
|
||||
}).toList();
|
||||
@@ -374,14 +374,14 @@ 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;
|
||||
|| operation.getAction() == EmiChangeOperation.Action.INTEREST_RATE_CHANGE || scheduleModel.isCopiedForCalculation();
|
||||
|
||||
calculateRateFactorForPeriods(relatedRepaymentPeriods, scheduleModel);
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
if (onlyOnActualModelShouldApply) {
|
||||
calculateEMIOnActualModel(relatedRepaymentPeriods, scheduleModel);
|
||||
} else {
|
||||
calculateEMIOnNewEmptyModelAndMerge(relatedRepaymentPeriods, scheduleModel, operation);
|
||||
calculateEMIOnNewModelAndMerge(relatedRepaymentPeriods, scheduleModel, operation);
|
||||
}
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
@@ -733,18 +733,18 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
});
|
||||
}
|
||||
|
||||
private void calculateEMIOnNewEmptyModelAndMerge(List<RepaymentPeriod> repaymentPeriods,
|
||||
ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||
private void calculateEMIOnNewModelAndMerge(List<RepaymentPeriod> repaymentPeriods, ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||
final EmiChangeOperation operation) {
|
||||
if (repaymentPeriods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.emptyCopy();
|
||||
addDisbursement(scheduleModelCopy, operation);
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.copyWithoutPaidAmounts();
|
||||
addDisbursement(scheduleModelCopy, operation.withZeroAmount());
|
||||
|
||||
final LocalDate firstDueDate = repaymentPeriods.get(0).getDueDate();
|
||||
scheduleModel.copyPeriodsFrom(firstDueDate, scheduleModelCopy.repaymentPeriods(), (newRepaymentPeriod, actualRepaymentPeriod) -> {
|
||||
actualRepaymentPeriod.setEmi(actualRepaymentPeriod.getEmi().plus(newRepaymentPeriod.getEmi()));
|
||||
actualRepaymentPeriod.setOriginalEmi(actualRepaymentPeriod.getOriginalEmi().plus(newRepaymentPeriod.getOriginalEmi()));
|
||||
actualRepaymentPeriod.setEmi(newRepaymentPeriod.getEmi());
|
||||
actualRepaymentPeriod.setOriginalEmi(newRepaymentPeriod.getOriginalEmi());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -46,4 +46,11 @@ public class EmiChangeOperation {
|
||||
public static EmiChangeOperation changeInterestRate(final LocalDate newInterestSubmittedOnDate, final BigDecimal newInterestRate) {
|
||||
return new EmiChangeOperation(EmiChangeOperation.Action.INTEREST_RATE_CHANGE, newInterestSubmittedOnDate, null, newInterestRate);
|
||||
}
|
||||
|
||||
public EmiChangeOperation withZeroAmount() {
|
||||
if (action == Action.DISBURSEMENT) {
|
||||
return new EmiChangeOperation(action, submittedOnDate, amount.zero(), null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -56,6 +56,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
private final Integer installmentAmountInMultiplesOf;
|
||||
private final MathContext mc;
|
||||
private final Money zero;
|
||||
private final boolean isCopiedForCalculation;
|
||||
|
||||
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
|
||||
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
|
||||
@@ -67,33 +68,34 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
|
||||
this.mc = mc;
|
||||
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
|
||||
this.isCopiedForCalculation = false;
|
||||
}
|
||||
|
||||
private ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods, final TreeSet<InterestRate> interestRates,
|
||||
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
|
||||
final Map<LoanTermVariationType, List<LoanTermVariationsData>> loanTermVariations, final Integer installmentAmountInMultiplesOf,
|
||||
final MathContext mc) {
|
||||
final MathContext mc, final boolean isCopiedForCalculation) {
|
||||
this.mc = mc;
|
||||
this.repaymentPeriods = copyRepaymentPeriods(repaymentPeriods,
|
||||
(previousPeriod, repaymentPeriod) -> new RepaymentPeriod(previousPeriod, repaymentPeriod, mc));
|
||||
(previousPeriod, repaymentPeriod) -> RepaymentPeriod.copy(previousPeriod, repaymentPeriod, mc));
|
||||
this.interestRates = new TreeSet<>(interestRates);
|
||||
this.loanProductRelatedDetail = loanProductRelatedDetail;
|
||||
this.loanTermVariations = loanTermVariations;
|
||||
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
|
||||
this.zero = Money.zero(loanProductRelatedDetail.getCurrencyData(), mc);
|
||||
this.isCopiedForCalculation = isCopiedForCalculation;
|
||||
}
|
||||
|
||||
public ProgressiveLoanInterestScheduleModel deepCopy(MathContext mc) {
|
||||
return new ProgressiveLoanInterestScheduleModel(repaymentPeriods, interestRates, loanProductRelatedDetail, loanTermVariations,
|
||||
installmentAmountInMultiplesOf, mc);
|
||||
installmentAmountInMultiplesOf, mc, false);
|
||||
}
|
||||
|
||||
public ProgressiveLoanInterestScheduleModel emptyCopy() {
|
||||
public ProgressiveLoanInterestScheduleModel copyWithoutPaidAmounts() {
|
||||
final List<RepaymentPeriod> repaymentPeriodCopies = copyRepaymentPeriods(repaymentPeriods,
|
||||
(previousPeriod, repaymentPeriod) -> new RepaymentPeriod(previousPeriod, repaymentPeriod.getFromDate(),
|
||||
repaymentPeriod.getDueDate(), repaymentPeriod.getEmi().zero(), mc));
|
||||
(previousPeriod, repaymentPeriod) -> RepaymentPeriod.copyWithoutPaidAmounts(previousPeriod, repaymentPeriod, mc));
|
||||
return new ProgressiveLoanInterestScheduleModel(repaymentPeriodCopies, interestRates, loanProductRelatedDetail, loanTermVariations,
|
||||
installmentAmountInMultiplesOf, mc);
|
||||
installmentAmountInMultiplesOf, mc, true);
|
||||
}
|
||||
|
||||
private List<RepaymentPeriod> copyRepaymentPeriods(final List<RepaymentPeriod> repaymentPeriods,
|
||||
|
||||
+39
-20
@@ -37,7 +37,7 @@ import org.apache.fineract.portfolio.util.Memo;
|
||||
|
||||
@ToString(exclude = { "previous" })
|
||||
@EqualsAndHashCode(exclude = { "previous" })
|
||||
public class RepaymentPeriod {
|
||||
public final class RepaymentPeriod {
|
||||
|
||||
private final RepaymentPeriod previous;
|
||||
@Getter
|
||||
@@ -59,40 +59,59 @@ public class RepaymentPeriod {
|
||||
@Getter
|
||||
private Money paidInterest;
|
||||
|
||||
private final MathContext mc;
|
||||
|
||||
private Memo<BigDecimal> rateFactorPlus1Calculation;
|
||||
private Memo<Money> calculatedDueInterestCalculation;
|
||||
private Memo<Money> dueInterestCalculation;
|
||||
private Memo<Money> outstandingBalanceCalculation;
|
||||
private final MathContext mc;
|
||||
|
||||
public RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc) {
|
||||
private RepaymentPeriod(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, List<InterestPeriod> interestPeriods,
|
||||
Money emi, Money originalEmi, Money paidPrincipal, Money paidInterest, MathContext mc) {
|
||||
this.previous = previous;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.interestPeriods = interestPeriods;
|
||||
this.emi = emi;
|
||||
this.originalEmi = emi;
|
||||
this.originalEmi = originalEmi;
|
||||
this.paidPrincipal = paidPrincipal;
|
||||
this.paidInterest = paidInterest;
|
||||
this.mc = mc;
|
||||
this.interestPeriods = new ArrayList<>();
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
getInterestPeriods().add(InterestPeriod.withEmptyAmounts(this, getFromDate(), getDueDate()));
|
||||
this.paidInterest = getZero(mc);
|
||||
this.paidPrincipal = getZero(mc);
|
||||
}
|
||||
|
||||
public RepaymentPeriod(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
this.previous = previous;
|
||||
this.fromDate = repaymentPeriod.fromDate;
|
||||
this.dueDate = repaymentPeriod.dueDate;
|
||||
this.emi = repaymentPeriod.emi;
|
||||
this.originalEmi = repaymentPeriod.originalEmi;
|
||||
this.interestPeriods = new ArrayList<>();
|
||||
this.paidPrincipal = repaymentPeriod.paidPrincipal;
|
||||
this.paidInterest = repaymentPeriod.paidInterest;
|
||||
this.mc = mc;
|
||||
public static RepaymentPeriod create(RepaymentPeriod previous, LocalDate fromDate, LocalDate dueDate, Money emi, MathContext mc) {
|
||||
final Money zero = emi.zero();
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, fromDate, dueDate, new ArrayList<>(), emi, emi, zero, zero,
|
||||
mc);
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
newRepaymentPeriod.interestPeriods.add(InterestPeriod.withEmptyAmounts(newRepaymentPeriod, fromDate, dueDate));
|
||||
return newRepaymentPeriod;
|
||||
}
|
||||
|
||||
public static RepaymentPeriod copy(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
|
||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, repaymentPeriod.paidPrincipal,
|
||||
repaymentPeriod.paidInterest, mc);
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
||||
interestPeriods.add(InterestPeriod.copy(this, interestPeriod));
|
||||
newRepaymentPeriod.interestPeriods.add(InterestPeriod.copy(newRepaymentPeriod, interestPeriod));
|
||||
}
|
||||
return newRepaymentPeriod;
|
||||
}
|
||||
|
||||
public static RepaymentPeriod copyWithoutPaidAmounts(RepaymentPeriod previous, RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
final Money zero = repaymentPeriod.emi.zero();
|
||||
final RepaymentPeriod newRepaymentPeriod = new RepaymentPeriod(previous, repaymentPeriod.fromDate, repaymentPeriod.dueDate,
|
||||
new ArrayList<>(), repaymentPeriod.emi, repaymentPeriod.originalEmi, zero, zero, mc);
|
||||
// There is always at least 1 interest period, by default with same from-due date as repayment period
|
||||
for (InterestPeriod interestPeriod : repaymentPeriod.interestPeriods) {
|
||||
var interestPeriodCopy = InterestPeriod.copy(newRepaymentPeriod, interestPeriod);
|
||||
if (!interestPeriodCopy.getBalanceCorrectionAmount().isZero()) {
|
||||
interestPeriodCopy.addBalanceCorrectionAmount(interestPeriodCopy.getBalanceCorrectionAmount().negated());
|
||||
}
|
||||
newRepaymentPeriod.interestPeriods.add(interestPeriodCopy);
|
||||
}
|
||||
return newRepaymentPeriod;
|
||||
}
|
||||
|
||||
public Optional<RepaymentPeriod> getPrevious() {
|
||||
|
||||
+11
-11
@@ -934,16 +934,16 @@ class ProgressiveEMICalculatorTest {
|
||||
disbursedAmount = toMoney(25.0);
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 8), disbursedAmount);
|
||||
|
||||
checkTotalInterestDue(interestSchedule, 4.65);
|
||||
checkTotalInterestDue(interestSchedule, 4.64);
|
||||
|
||||
checkPeriod(interestSchedule, 0, 0, 29.93, 0.001019591398, 0.00, 1.15, 28.78, 146.22);
|
||||
checkPeriod(interestSchedule, 0, 1, 29.93, 0.000764693548, 0.08, 1.15, 28.78, 146.22);
|
||||
checkPeriod(interestSchedule, 0, 2, 29.93, 0.006117548387, 1.07, 1.15, 28.78, 146.22);
|
||||
checkPeriod(interestSchedule, 1, 0, 29.93, 0.007901833333, 1.16, 28.77, 117.45);
|
||||
checkPeriod(interestSchedule, 2, 0, 29.93, 0.007901833333, 0.93, 29.00, 88.45);
|
||||
checkPeriod(interestSchedule, 3, 0, 29.93, 0.007901833333, 0.70, 29.23, 59.22);
|
||||
checkPeriod(interestSchedule, 4, 0, 29.93, 0.007901833333, 0.47, 29.46, 29.76);
|
||||
checkPeriod(interestSchedule, 5, 0, 30.0, 0.007901833333, 0.24, 29.76, 0.0);
|
||||
checkPeriod(interestSchedule, 0, 0, 29.94, 0.001019591398, 0.00, 1.15, 28.79, 146.21);
|
||||
checkPeriod(interestSchedule, 0, 1, 29.94, 0.000764693548, 0.08, 1.15, 28.79, 146.21);
|
||||
checkPeriod(interestSchedule, 0, 2, 29.94, 0.006117548387, 1.07, 1.15, 28.79, 146.21);
|
||||
checkPeriod(interestSchedule, 1, 0, 29.94, 0.007901833333, 1.16, 28.78, 117.43);
|
||||
checkPeriod(interestSchedule, 2, 0, 29.94, 0.007901833333, 0.93, 29.01, 88.42);
|
||||
checkPeriod(interestSchedule, 3, 0, 29.94, 0.007901833333, 0.70, 29.24, 59.18);
|
||||
checkPeriod(interestSchedule, 4, 0, 29.94, 0.007901833333, 0.47, 29.47, 29.71);
|
||||
checkPeriod(interestSchedule, 5, 0, 29.94, 0.007901833333, 0.23, 29.71, 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1024,8 +1024,8 @@ class ProgressiveEMICalculatorTest {
|
||||
emiCalculator.addDisbursement(interestSchedule, LocalDate.of(2024, 1, 1), toMoney(200.0));
|
||||
checkTotalInterestDue(interestSchedule, 4.11);
|
||||
|
||||
checkEmi(interestSchedule, 4, 85.05);
|
||||
checkEmi(interestSchedule, 5, 78.86);
|
||||
checkEmi(interestSchedule, 4, 85.04);
|
||||
checkEmi(interestSchedule, 5, 78.91);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user