FINERACT-1981: When paying down payment, that should not be reflected on the InterestModel
This commit is contained in:
committed by
Adam Saghy
parent
d202145a88
commit
7288037614
+8
@@ -1549,6 +1549,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
Money paidPortion;
|
||||
ProgressiveLoanInterestScheduleModel model = ctx.getModel();
|
||||
LocalDate payDate = loanTransaction.getTransactionDate();
|
||||
|
||||
if (installment.isDownPayment() || installment.getDueDate().isAfter(ctx.getModel().getMaturityDate())) {
|
||||
// Skip interest and principal payment processing for down payment period or periods after loan maturity
|
||||
// date
|
||||
return processPaymentAllocation(paymentAllocationType, installment, loanTransaction, transactionAmountUnprocessed,
|
||||
loanTransactionToRepaymentScheduleMapping, charges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY);
|
||||
}
|
||||
|
||||
if (DueType.IN_ADVANCE.equals(paymentAllocationType.getDueType())) {
|
||||
payDate = calculateNewPayDateInCaseOfInAdvancePayment(loanTransaction, installment);
|
||||
updateRepaymentPeriodBalances(paymentAllocationType, installment, model, payDate);
|
||||
|
||||
+84
@@ -24,9 +24,11 @@ import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.IN
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.refEq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
@@ -64,6 +66,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationT
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||
@@ -423,6 +426,87 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessLatestTransaction_PassesThroughHandlingPaymentAllocationForInterestBearingProgressiveLoan() {
|
||||
// Set up transaction amount, currency, and dates
|
||||
BigDecimal transactionAmount = BigDecimal.valueOf(100.00);
|
||||
LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
|
||||
LocalDate transactionDate = disbursementDate.plusMonths(1);
|
||||
|
||||
MonetaryCurrency currency = MONETARY_CURRENCY;
|
||||
Money transactionAmountMoney = Money.of(currency, transactionAmount);
|
||||
|
||||
// Set up LoanTransaction
|
||||
LoanTransaction loanTransaction = mock(LoanTransaction.class);
|
||||
when(loanTransaction.getTypeOf()).thenReturn(LoanTransactionType.CHARGE_PAYMENT);
|
||||
when(loanTransaction.getAmount()).thenReturn(transactionAmount);
|
||||
when(loanTransaction.getAmount(currency)).thenReturn(transactionAmountMoney);
|
||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
|
||||
// Set up Loan and related details
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanProductRelatedDetail loanProductRelatedDetail = mock(LoanProductRelatedDetail.class);
|
||||
LoanPaymentAllocationRule loanPaymentAllocationRule = mock(LoanPaymentAllocationRule.class);
|
||||
when(loan.isInterestBearing()).thenReturn(true);
|
||||
when(loanProductRelatedDetail.isInterestRecalculationEnabled()).thenReturn(true);
|
||||
|
||||
when(loanTransaction.getLoan()).thenReturn(loan);
|
||||
when(loan.loanCurrency()).thenReturn(currency);
|
||||
when(loan.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
|
||||
when(loanProductRelatedDetail.getLoanScheduleProcessingType()).thenReturn(LoanScheduleProcessingType.HORIZONTAL);
|
||||
when(loan.getPaymentAllocationRules()).thenReturn(List.of(loanPaymentAllocationRule));
|
||||
when(loan.isInterestBearing()).thenReturn(true);
|
||||
when(loanProductRelatedDetail.isInterestRecalculationEnabled()).thenReturn(true);
|
||||
|
||||
when(loanPaymentAllocationRule.getTransactionType()).thenReturn(PaymentAllocationTransactionType.DEFAULT);
|
||||
when(loanPaymentAllocationRule.getAllocationTypes())
|
||||
.thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL, PaymentAllocationType.DUE_INTEREST));
|
||||
|
||||
// Create an installment that is due
|
||||
LoanRepaymentScheduleInstallment installment = spy(
|
||||
new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, transactionDate, BigDecimal.valueOf(100L),
|
||||
BigDecimal.valueOf(0L), BigDecimal.valueOf(100L), BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
|
||||
// Let's set up a credit transaction so that its transaction date coincides with the payment due date
|
||||
when(loanTransaction.getTransactionDate()).thenReturn(transactionDate);
|
||||
when(loanTransaction.isOn(transactionDate)).thenReturn(true);
|
||||
|
||||
List<LoanRepaymentScheduleInstallment> installments = List.of(installment);
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
|
||||
ProgressiveLoanInterestScheduleModel model = mock(ProgressiveLoanInterestScheduleModel.class);
|
||||
when(model.getMaturityDate()).thenReturn(LocalDate.of(2023, 12, 31));
|
||||
ChangedTransactionDetail changedTransactionDetail = mock(ChangedTransactionDetail.class);
|
||||
|
||||
// Set up TransactionCtx with installments and charges
|
||||
TransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, Set.of(), overpaymentHolder, changedTransactionDetail,
|
||||
model);
|
||||
|
||||
// Mock additional necessary methods
|
||||
LoanCharge loanCharge = mock(LoanCharge.class);
|
||||
when(loanTransaction.getLoanChargesPaid())
|
||||
.thenReturn(Set.of(new LoanChargePaidBy(loanTransaction, loanCharge, transactionAmount, 1)));
|
||||
when(loanCharge.getAmountOutstanding(currency)).thenReturn(transactionAmountMoney);
|
||||
when(loanTransaction.isAfter(any(LocalDate.class))).thenReturn(true); // Mock to simulate past due date check
|
||||
|
||||
// Run the processLatestTransaction method
|
||||
underTest.processLatestTransaction(loanTransaction, ctx);
|
||||
|
||||
// Verification of expected behavior
|
||||
ArgumentCaptor<Money> paidPortionCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<LocalDate> payDateCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
|
||||
// Verify `payPrincipal` is called when allocation type is DUE_PRINCIPAL
|
||||
Mockito.verify(emiCalculator, atLeastOnce()).payPrincipal(eq(model), eq(transactionDate), payDateCaptor.capture(),
|
||||
paidPortionCaptor.capture());
|
||||
Money paidPortion = paidPortionCaptor.getValue();
|
||||
LocalDate payDate = payDateCaptor.getValue();
|
||||
|
||||
// Assert that `paidPortion` and `payDate` match expected values
|
||||
assertNotNull(paidPortion);
|
||||
assertNotNull(payDate);
|
||||
assertEquals(transactionAmountMoney.toString(), paidPortion.toString());
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment createMockInstallment(LocalDate localDate, boolean isAdditional) {
|
||||
LoanRepaymentScheduleInstallment installment = mock(LoanRepaymentScheduleInstallment.class);
|
||||
lenient().when(installment.isAdditional()).thenReturn(isAdditional);
|
||||
|
||||
Reference in New Issue
Block a user