FINERACT-2211: Second disbursement error fix

This commit is contained in:
budaidev
2025-06-06 10:55:17 +02:00
committed by Adam Saghy
parent 39a8d5c63e
commit 56e89ba544
3 changed files with 268 additions and 0 deletions
@@ -1246,6 +1246,22 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
emiCalculator.addDisbursement(model, transactionDate, amortizableAmount);
boolean needsNPlusOneInstallment = installments.stream()
.filter(i -> i.getDueDate().isAfter(transactionDate) || i.getDueDate().isEqual(transactionDate))
.filter(i -> !i.isDownPayment() && !i.isAdditional()).findAny().isEmpty();
if (needsNPlusOneInstallment) {
// CREATE N+1 installment like the non-EMI version does
LoanRepaymentScheduleInstallment newInstallment = new LoanRepaymentScheduleInstallment(disbursementTransaction.getLoan(),
installments.size() + 1, disbursementTransaction.getTransactionDate(), disbursementTransaction.getTransactionDate(),
Money.zero(currency).getAmount(), Money.zero(currency).getAmount(), Money.zero(currency).getAmount(),
Money.zero(currency).getAmount(), true, null);
newInstallment.updatePrincipal(amortizableAmount.getAmount());
newInstallment.markAsAdditional();
disbursementTransaction.getLoan().addLoanRepaymentScheduleInstallment(newInstallment);
installments.add(newInstallment);
}
disbursementTransaction.resetDerivedComponents();
recalculateRepaymentPeriodsWithEMICalculation(amortizableAmount, model, installments, transactionDate, currency);
allocateOverpayment(disbursementTransaction, transactionCtx);
@@ -1259,6 +1275,23 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments = installments.stream().filter(
i -> i.getDueDate().isAfter(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional())
.toList();
if (candidateRepaymentInstallments.isEmpty()) {
LoanRepaymentScheduleInstallment newInstallment;
if (installments.stream().filter(LoanRepaymentScheduleInstallment::isAdditional).findAny().isEmpty()) {
newInstallment = new LoanRepaymentScheduleInstallment(disbursementTransaction.getLoan(), installments.size() + 1,
disbursementTransaction.getTransactionDate(), disbursementTransaction.getTransactionDate(),
Money.zero(currency).getAmount(), Money.zero(currency).getAmount(), Money.zero(currency).getAmount(),
Money.zero(currency).getAmount(), false, null);
newInstallment.markAsAdditional();
disbursementTransaction.getLoan().addLoanRepaymentScheduleInstallment(newInstallment);
installments.add(newInstallment);
} else {
newInstallment = installments.stream().filter(LoanRepaymentScheduleInstallment::isAdditional).findFirst().orElseThrow();
newInstallment.updateDueDate(disbursementTransaction.getTransactionDate());
}
candidateRepaymentInstallments = Collections.singletonList(newInstallment);
}
LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail();
Integer installmentAmountInMultiplesOf = disbursementTransaction.getLoan().getLoanProductRelatedDetail()
.getInstallmentAmountInMultiplesOf();
@@ -1325,7 +1358,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private void recalculateRepaymentPeriodsWithEMICalculation(Money amortizableAmount, ProgressiveLoanInterestScheduleModel model,
List<LoanRepaymentScheduleInstallment> installments, LocalDate transactionDate, MonetaryCurrency currency) {
boolean isPostMaturityDisbursement = installments.stream().filter(i -> !i.isDownPayment() && !i.isAdditional())
.allMatch(i -> i.getDueDate().isBefore(transactionDate));
if (amortizableAmount.isGreaterThanZero()) {
if (isPostMaturityDisbursement) {
LoanRepaymentScheduleInstallment additionalInstallment = installments.stream()
.filter(LoanRepaymentScheduleInstallment::isAdditional).findFirst().orElse(null);
if (additionalInstallment != null && additionalInstallment.getPrincipal(currency).isZero()) {
additionalInstallment.updatePrincipal(amortizableAmount.getAmount());
}
}
model.repaymentPeriods().forEach(rm -> {
LoanRepaymentScheduleInstallment installment = installments.stream().filter(
ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment() && !ri.getDueDate().isBefore(transactionDate))
@@ -1344,6 +1388,15 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
Integer installmentAmountInMultiplesOf) {
if (amortizableAmount.isGreaterThanZero()) {
int noCandidateRepaymentInstallments = candidateRepaymentInstallments.size();
// Handle the case where no future installments exist (e.g., second disbursement after loan closure)
if (noCandidateRepaymentInstallments == 0) {
log.debug("No candidate repayment installments found for disbursement on {}. Creating new installments.",
loanTransaction.getTransactionDate());
return;
}
// Original logic for when candidate installments exist
Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, MoneyHelper.getMathContext());
MoneyHolder moneyHolder = new MoneyHolder(amortizableAmount);