FINERACT-2228: Fix incorrect current balance calculation and unintentional transaction reversals

This commit is contained in:
Adam Saghy
2025-04-01 08:50:14 +02:00
parent b7845166c4
commit 833f4a0d33
9 changed files with 202 additions and 49 deletions
@@ -4449,4 +4449,122 @@ Feature: LoanRepayment
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
| 01 February 2024 | Repayment | 20.0 | 19.42 | 0.58 | 0.0 | 0.0 | 80.58 | false | false |
When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
@TestRailId:C3569
Scenario: Verify Loan is fully paid and closed after partial repayments, Merchant issued refund which overpays the loan, partial CBR and reversal of 1st repayment
When Admin sets the business date to "28 March 2025"
When Admin creates a client with random data
When Admin creates a fully customized loan with the following data:
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
| LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 26 March 2025 | 120 | 35.29 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "26 March 2025" with "120" amount and expected disbursement date on "26 March 2025"
When Admin successfully disburse the loan on "26 March 2025" with "120" EUR transaction amount
Then Loan Repayment schedule has 3 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 26 April 2025 | | 81.15 | 38.85 | 3.53 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 |
| 2 | 30 | 26 May 2025 | | 41.16 | 39.99 | 2.39 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 |
| 3 | 31 | 26 June 2025 | | 0.0 | 41.16 | 1.21 | 0.0 | 0.0 | 42.37 | 0.0 | 0.0 | 0.0 | 42.37 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 120.0 | 7.13 | 0.0 | 0.0 | 127.13 | 0.0 | 0.0 | 0.0 | 127.13 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR transaction amount
Then Loan Repayment schedule has 3 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 26 April 2025 | | 80.58 | 39.42 | 2.96 | 0.0 | 0.0 | 42.38 | 20.0 | 20.0 | 0.0 | 22.38 |
| 2 | 30 | 26 May 2025 | | 40.57 | 40.01 | 2.37 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 |
| 3 | 31 | 26 June 2025 | | 0.0 | 40.57 | 1.19 | 0.0 | 0.0 | 41.76 | 0.0 | 0.0 | 0.0 | 41.76 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 120.0 | 6.52 | 0.0 | 0.0 | 126.52 | 20.0 | 20.0 | 0.0 | 106.52 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false |
And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR transaction amount
Then Loan Repayment schedule has 3 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 26 April 2025 | | 80.01 | 39.99 | 2.39 | 0.0 | 0.0 | 42.38 | 40.0 | 40.0 | 0.0 | 2.38 |
| 2 | 30 | 26 May 2025 | | 39.98 | 40.03 | 2.35 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 |
| 3 | 31 | 26 June 2025 | | 0.0 | 39.98 | 1.18 | 0.0 | 0.0 | 41.16 | 0.0 | 0.0 | 0.0 | 41.16 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 120.0 | 5.92 | 0.0 | 0.0 | 125.92 | 40.0 | 40.0 | 0.0 | 85.92 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false |
| 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false |
And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "27 March 2025" with 120 EUR transaction amount and self-generated Idempotency key
Then Loan Repayment schedule has 3 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 26 April 2025 | 27 March 2025 | 80.11 | 39.89 | 0.11 | 0.0 | 0.0 | 40.0 | 40.0 | 40.0 | 0.0 | 0.0 |
| 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 37.73 | 0.0 | 0.0 | 0.0 | 37.73 | 37.73 | 37.73 | 0.0 | 0.0 |
| 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false |
| 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false |
| 27 March 2025 | Merchant Issued Refund | 120.0 | 80.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
| 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
| 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
| 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
Then Loan status will be "OVERPAID"
Then Loan has 0 outstanding amount
Then Loan has 40 overpaid amount
And Admin makes Credit Balance Refund transaction on "28 March 2025" with 20 EUR transaction amount
Then Loan Repayment schedule has 3 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 26 April 2025 | 27 March 2025 | 80.11 | 39.89 | 0.11 | 0.0 | 0.0 | 40.0 | 40.0 | 40.0 | 0.0 | 0.0 |
| 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 37.73 | 0.0 | 0.0 | 0.0 | 37.73 | 37.73 | 37.73 | 0.0 | 0.0 |
| 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false |
| 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false |
| 27 March 2025 | Merchant Issued Refund | 120.0 | 80.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
| 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
| 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
| 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
| 28 March 2025 | Credit Balance Refund | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
Then Loan status will be "OVERPAID"
Then Loan has 0 outstanding amount
Then Loan has 20 overpaid amount
When Customer undo "1"th "Repayment" transaction made on "27 March 2025"
Then Loan Repayment schedule has 3 periods, with the following data for periods:
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 26 April 2025 | 27 March 2025 | 84.76 | 35.24 | 0.11 | 0.0 | 0.0 | 35.35 | 35.35 | 35.35 | 0.0 | 0.0 |
| 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 |
| 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 |
Then Loan Transactions tab has the following data:
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
| 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false |
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | true | false |
| 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | true |
| 27 March 2025 | Merchant Issued Refund | 120.0 | 100.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | true |
| 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true |
| 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | true |
| 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false |
| 28 March 2025 | Credit Balance Refund | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
Then Loan has 0 outstanding amount
@@ -60,7 +60,6 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
loan.retrieveListOfTransactionsForReprocessing());
loan.doPostLoanTransactionChecks(transactionDate, loan.getLoanLifecycleStateMachine());
handleChangedDetail(changedTransactionDetail);
}
@@ -275,8 +275,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
continue;
}
Money outstandingBalance = emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel, periodDueDate,
disbursementDate);
Money outstandingBalance = emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel, disbursementDate);
final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal(), mc);
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
@@ -119,8 +119,7 @@ public interface EMICalculator {
Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
@NotNull LocalDate targetDate, boolean includeChargebackInterest);
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate repaymentPeriodDueDate,
LocalDate targetDate);
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate targetDate);
OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, LocalDate targetDate);
@@ -256,7 +256,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
@NotNull LocalDate targetDate) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
periodDueDate, targetDate);
targetDate);
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
long notFullyRepaidRepaymentPeriodCount = recalculatedScheduleModelTillDate.repaymentPeriods().stream()
.filter(rp -> !rp.isFullyPaid()).count();
@@ -265,6 +265,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
if (multiplePeriodIsUnpaid) {
repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi());
Money totalOutstandingPrincipal = recalculatedScheduleModelTillDate.getTotalOutstandingPrincipal();
Money outstandingPrincipal = repaymentPeriod.getOutstandingPrincipal();
// If there are less outstanding principal than anticipated
Money emiAdjustment = MathUtil.negativeToZero(outstandingPrincipal.minus(totalOutstandingPrincipal));
repaymentPeriod.setEmi(repaymentPeriod.getEmi().minus(emiAdjustment));
} else if (repaymentPeriod.isFullyPaid() && onePeriodIsUnpaid) {
repaymentPeriod.setEmi(MathUtil.min(repaymentPeriod.getOriginalEmi(), //
recalculatedScheduleModelTillDate.getTotalDuePrincipal() //
@@ -285,7 +290,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
@NotNull LocalDate targetDate, boolean includeChargebackInterest) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
periodDueDate, targetDate);
targetDate);
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
return includeChargebackInterest ? repaymentPeriod.getCalculatedDueInterest()
: repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getChargebackInterest(),
@@ -293,11 +298,18 @@ public final class ProgressiveEMICalculator implements EMICalculator {
}
@Override
public Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate periodDueDate,
LocalDate targetDate) {
public Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate targetDate) {
ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel,
periodDueDate, targetDate);
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
targetDate);
RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriod(targetDate).orElseGet(() -> {
// If target date is after maturity date
if (targetDate.isAfter(recalculatedScheduleModelTillDate.getLastRepaymentPeriod().getDueDate())) {
return recalculatedScheduleModelTillDate.getLastRepaymentPeriod();
} else {
// if target date is before 1st disbursement date, we use 1st repayment period
return recalculatedScheduleModelTillDate.repaymentPeriods().get(0);
}
});
return repaymentPeriod.getOutstandingLoanBalance();
}
@@ -343,43 +355,58 @@ public final class ProgressiveEMICalculator implements EMICalculator {
@NotNull
private ProgressiveLoanInterestScheduleModel recalculateScheduleModelTillDate(
@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) {
@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate targetDate) {
MathContext mc = scheduleModel.mc();
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
RepaymentPeriod repaymentPeriod = scheduleModelCopy.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow();
LocalDate adjustedTargetDate = targetDate;
InterestPeriod interestPeriod;
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
interestPeriod = repaymentPeriod.getFirstInterestPeriod();
adjustedTargetDate = repaymentPeriod.getFromDate();
} else if (targetDate.isAfter(repaymentPeriod.getDueDate())) {
interestPeriod = repaymentPeriod.getLastInterestPeriod();
adjustedTargetDate = repaymentPeriod.getDueDate();
boolean isBeforeFirstDisbursement = targetDate.isBefore(scheduleModelCopy.repaymentPeriods().get(0).getFromDate());
boolean isAfterMaturityDate = !targetDate.isBefore(scheduleModelCopy.getLastRepaymentPeriod().getDueDate());
if (isBeforeFirstDisbursement) {
scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().clear());
return scheduleModelCopy;
} else if (isAfterMaturityDate) {
return scheduleModelCopy;
} else {
interestPeriod = repaymentPeriod.findInterestPeriod(targetDate).orElseThrow();
}
// TODO use findInterestPeriod
scheduleModelCopy.findRepaymentPeriod(targetDate)//
.flatMap(rp -> rp.findInterestPeriod(targetDate)).ifPresent(ip -> ip.setDueDate(targetDate)); //
interestPeriod.setDueDate(adjustedTargetDate);
int index = repaymentPeriod.getInterestPeriods().indexOf(interestPeriod);
int nextIdx = index + 1;
boolean thereIsInterestPeriodFromDateOnTargetDate = repaymentPeriod.getInterestPeriods().size() > nextIdx
&& repaymentPeriod.getInterestPeriods().get(nextIdx).getFromDate().isEqual(targetDate);
if (thereIsInterestPeriodFromDateOnTargetDate) {
// NOTE: If there is a next interest period with fromDate on the target date
// then the related chargeback amounts comes from the next interest period too.
InterestPeriod nextInterestPeriod = repaymentPeriod.getInterestPeriods().get(nextIdx);
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);
calculateOutstandingBalance(scheduleModelCopy);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
RepaymentPeriod repaymentPeriod = scheduleModelCopy.findRepaymentPeriod(targetDate).orElseThrow();
scheduleModelCopy.repaymentPeriods().forEach(rp -> {
if (rp.getDueDate().isAfter(targetDate)) {
if (rp.equals(repaymentPeriod)) {
rp.findInterestPeriod(targetDate).ifPresent(ip -> {
ip.setDueDate(targetDate);
int index = rp.getInterestPeriods().indexOf(ip);
int nextIdx = index + 1;
boolean thereIsInterestPeriodFromDateOnTargetDate = ip.getRepaymentPeriod().getInterestPeriods()
.size() > nextIdx;
if (thereIsInterestPeriodFromDateOnTargetDate) {
// NOTE: If there is a next interest period with fromDate on the target date
// then the related chargeback amounts comes from the next interest period too.
InterestPeriod nextInterestPeriod = ip.getRepaymentPeriod().getInterestPeriods().get(nextIdx);
ip.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal());
ip.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest());
}
ip.getRepaymentPeriod().getInterestPeriods()
.subList(nextIdx, ip.getRepaymentPeriod().getInterestPeriods().size()).clear();
});
} else if (rp.getPrevious().isPresent() && rp.getPrevious().get().equals(repaymentPeriod)
&& (rp.getInterestPeriods().get(0).getChargebackInterest().isGreaterThanZero()
|| rp.getInterestPeriods().get(0).getChargebackPrincipal().isGreaterThanZero())) {
// NOTE: we need to check whether there is chargeback on the 1st interest period of the next
// period
// if so, we need to retain that interest period, but need to update due date to match with from
// date -> 0 interest
rp.getInterestPeriods().get(0).setDueDate(rp.getInterestPeriods().get(0).getFromDate());
if (rp.getInterestPeriods().size() > 1) {
rp.getInterestPeriods().subList(1, rp.getInterestPeriods().size()).clear();
}
} else {
rp.getInterestPeriods().clear();
}
}
});
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
calculateOutstandingBalance(scheduleModelCopy);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
}
return scheduleModelCopy;
}
@@ -42,6 +42,7 @@ import java.util.stream.Collectors;
import lombok.Data;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
@@ -338,6 +339,14 @@ public class ProgressiveLoanInterestScheduleModel {
return repaymentPeriods().stream().map(RepaymentPeriod::getChargebackPrincipal).reduce(zero, Money::plus);
}
public Money getTotalOutstandingPrincipal() {
return MathUtil.negativeToZero(getTotalDuePrincipal().minus(getTotalPaidPrincipal()));
}
public Money getTotalOutstandingInterest() {
return MathUtil.negativeToZero(getTotalDueInterest().minus(getTotalPaidInterest()));
}
public Optional<RepaymentPeriod> findRepaymentPeriod(@NotNull LocalDate transactionDate) {
return repaymentPeriods.stream() //
.filter(period -> isInPeriod(transactionDate, period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))//
@@ -318,4 +318,8 @@ public final class RepaymentPeriod {
public boolean isFirstRepaymentPeriod() {
return previous == null;
}
public Money getOutstandingPrincipal() {
return getDuePrincipal().minus(getPaidPrincipal());
}
}
@@ -302,7 +302,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
} else {
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
loan.doPostLoanTransactionChecks(transactionDate, loan.getLoanLifecycleStateMachine());
loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
}
@@ -184,8 +184,8 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
if (!transactionIds.isEmpty()) {
this.accountTransfersWritePlatformService.reverseTransfersWithFromAccountTransactions(transactionIds,
PortfolioAccountType.LOAN);
loan.updateLoanSummaryAndStatus();
}
loan.updateLoanSummaryAndStatus();
loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(),
false);
@@ -267,8 +267,6 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
writeOffTransaction.reverse();
}
loan.updateLoanSummaryAndStatus();
if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) {
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail,
loanLifecycleStateMachine, transactionForAdjustment, scheduleGeneratorDTO);