Skip to content

Commit

Permalink
FINERACT-1981: When paying down payment, that should not be reflected…
Browse files Browse the repository at this point in the history
… on the InterestModel
  • Loading branch information
kulminsky committed Nov 1, 2024
1 parent b053c90 commit 2332200
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,14 @@ private Money handlingPaymentAllocationForInterestBearingProgressiveLoan(LoanTra
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@
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.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;

import java.math.BigDecimal;
import java.math.MathContext;
Expand All @@ -51,19 +47,10 @@
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.*;
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;
Expand Down Expand Up @@ -423,6 +410,87 @@ public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterestWi
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));

// Настроим loanTransaction, чтобы его transactionDate совпадала с dueDate installment
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);
Expand Down

0 comments on commit 2332200

Please sign in to comment.