FINERACT-2221: Fix - Interest not counted towards totalUnpaidPayableNotDueInterest after partial repayment
This commit is contained in:
+10
@@ -182,6 +182,16 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
.principal(outstandingAmounts.getOutstandingPrincipal()) //
|
||||
.interest(outstandingAmounts.getOutstandingInterest());//
|
||||
|
||||
// We need to deduct any paid amount if there is no interest recalculation
|
||||
if (!loan.isInterestRecalculationEnabled()) {
|
||||
BigDecimal paidInterest = installments.stream().map(LoanRepaymentScheduleInstallment::getInterestPaid).reduce(BigDecimal.ZERO,
|
||||
BigDecimal::add);
|
||||
BigDecimal paidPrincipal = installments.stream().map(LoanRepaymentScheduleInstallment::getPrincipal).reduce(BigDecimal.ZERO,
|
||||
BigDecimal::add);
|
||||
result.principal().minus(paidPrincipal);
|
||||
result.interest().minus(paidInterest);
|
||||
}
|
||||
|
||||
installments.forEach(installment -> {
|
||||
if (installment.isAdditional()) {
|
||||
result.plusPrincipal(installment.getPrincipalOutstanding(currency))
|
||||
|
||||
+1
-1
@@ -298,7 +298,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
* @return
|
||||
*/
|
||||
public Money getTotalDueInterest() {
|
||||
return repaymentPeriods().stream().map(RepaymentPeriod::getCalculatedDueInterest).reduce(zero(), Money::plus);
|
||||
return repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(zero(), Money::plus);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-4
@@ -1302,10 +1302,9 @@ class ProgressiveEMICalculatorTest {
|
||||
final LocalDate dueDate = LocalDate.of(2024, 2, 1);
|
||||
final LocalDate startDay = LocalDate.of(2024, 1, 1);
|
||||
|
||||
// TODO: work on interest calculation
|
||||
// emiCalculator.payInterest(interestModel, dueDate, startDay.plusDays(3), toMoney(0.56));
|
||||
// emiCalculator.chargebackInterest(interestModel, startDay.plusDays(3), toMoney(0.0));
|
||||
// emiCalculator.addBalanceCorrection(interestModel, startDay.plusDays(3), toMoney(0.0));
|
||||
emiCalculator.payInterest(interestModel, dueDate, startDay.plusDays(3), toMoney(0.56));
|
||||
emiCalculator.chargebackInterest(interestModel, startDay.plusDays(3), toMoney(0.0));
|
||||
emiCalculator.addBalanceCorrection(interestModel, startDay.plusDays(3), toMoney(0.0));
|
||||
|
||||
checkDailyInterest(interestModel, dueDate, startDay, 1, 0.19, 0.19);
|
||||
checkDailyInterest(interestModel, dueDate, startDay, 2, 0.19, 0.38);
|
||||
|
||||
+2
-2
@@ -87,13 +87,13 @@ public abstract class CommonLoanSummaryDataProvider implements LoanSummaryDataPr
|
||||
totalRepaymentTransactionReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||
LoanTransactionType.REPAYMENT);
|
||||
|
||||
if (repaymentSchedule != null) {
|
||||
if (repaymentSchedule != null && defaultSummaryData.getInterestCharged().compareTo(BigDecimal.ZERO) > 0) {
|
||||
// Outstanding Interest on Past due installments
|
||||
totalUnpaidPayableDueInterest = computeTotalUnpaidPayableDueInterestAmount(repaymentSchedule.getPeriods(), businessDate);
|
||||
|
||||
// Accumulated daily interest of the current Installment period
|
||||
totalUnpaidPayableNotDueInterest = computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(loan,
|
||||
repaymentSchedule.getPeriods(), businessDate, defaultSummaryData.getCurrency());
|
||||
repaymentSchedule.getPeriods(), businessDate, defaultSummaryData.getCurrency(), totalUnpaidPayableDueInterest);
|
||||
}
|
||||
|
||||
return LoanSummaryData.builder().currency(defaultSummaryData.getCurrency())
|
||||
|
||||
+2
-1
@@ -44,7 +44,8 @@ public class CumulativeLoanSummaryDataProvider extends CommonLoanSummaryDataProv
|
||||
|
||||
@Override
|
||||
public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
|
||||
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency) {
|
||||
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency,
|
||||
BigDecimal totalUnpaidPayableDueInterest) {
|
||||
// Find the current Period (If exists one) based on the Business date
|
||||
final Optional<LoanSchedulePeriodData> optCurrentPeriod = periods.stream().filter(period -> !period.isDownPaymentPeriod() //
|
||||
&& period.getPeriod() != null //
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ public interface LoanSummaryDataProvider {
|
||||
BigDecimal computeTotalUnpaidPayableDueInterestAmount(Collection<LoanSchedulePeriodData> periods, LocalDate businessDate);
|
||||
|
||||
BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(Loan loan, Collection<LoanSchedulePeriodData> periods,
|
||||
LocalDate businessDate, CurrencyData currency);
|
||||
LocalDate businessDate, CurrencyData currency, BigDecimal totalUnpaidPayableDueInterest);
|
||||
|
||||
LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData);
|
||||
|
||||
|
||||
+21
-27
@@ -21,9 +21,9 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
@@ -40,7 +40,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
|
||||
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -71,26 +71,25 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
|
||||
return super.withTransactionAmountsSummary(loan, defaultSummaryData, repaymentSchedule, loanTransactionBalances);
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(Loan loan, LocalDate businessDate) {
|
||||
private Optional<LoanRepaymentScheduleInstallment> getRelatedRepaymentScheduleInstallment(Loan loan, LocalDate businessDate) {
|
||||
return loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isDownPayment() && !i.isAdditional()
|
||||
&& !businessDate.isBefore(i.getFromDate()) && businessDate.isBefore(i.getDueDate())).findFirst().orElseGet(() -> {
|
||||
List<LoanRepaymentScheduleInstallment> list = loan.getRepaymentScheduleInstallments().stream()
|
||||
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
|
||||
return !list.isEmpty() ? list.get(list.size() - 1) : null;
|
||||
});
|
||||
&& businessDate.isAfter(i.getFromDate()) && !businessDate.isAfter(i.getDueDate())).findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
|
||||
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency) {
|
||||
if (loan.isMatured(businessDate)) {
|
||||
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency,
|
||||
BigDecimal totalUnpaidPayableDueInterest) {
|
||||
if (loan.isMatured(businessDate) || !loan.isInterestBearing()) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = getRelatedRepaymentScheduleInstallment(loan, businessDate);
|
||||
if (loan.isInterestBearing() && loanRepaymentScheduleInstallment != null) {
|
||||
Optional<LoanRepaymentScheduleInstallment> currentRepaymentPeriod = getRelatedRepaymentScheduleInstallment(loan, businessDate);
|
||||
|
||||
if (currentRepaymentPeriod.isPresent()) {
|
||||
if (loan.isChargedOff()) {
|
||||
return loanRepaymentScheduleInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
|
||||
return MathUtil.subtractToZero(currentRepaymentPeriod.get().getInterestOutstanding(loan.getCurrency()).getAmount(),
|
||||
totalUnpaidPayableDueInterest);
|
||||
} else {
|
||||
List<LoanTransaction> transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing().stream()
|
||||
.filter(t -> !t.isAccrualActivity()).toList();
|
||||
@@ -107,20 +106,15 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
|
||||
replayedTransactions);
|
||||
}
|
||||
if (model != null) {
|
||||
LoanRepaymentScheduleInstallment nextUnpaidInAdvanceInstallment = loanRepaymentScheduleInstallment.isNotFullyPaidOff()
|
||||
? loanRepaymentScheduleInstallment
|
||||
: loan.getRepaymentScheduleInstallments().stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
|
||||
.filter(i -> i.getInstallmentNumber() != null)
|
||||
.min(Comparator.comparingInt(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||
if (nextUnpaidInAdvanceInstallment == null) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
PeriodDueDetails dueAmounts = emiCalculator.getDueAmounts(model, nextUnpaidInAdvanceInstallment.getDueDate(),
|
||||
businessDate);
|
||||
if (dueAmounts != null) {
|
||||
BigDecimal interestPaid = nextUnpaidInAdvanceInstallment.getInterestPaid();
|
||||
BigDecimal dueInterest = dueAmounts.getDueInterest().getAmount();
|
||||
return MathUtil.subtractToZero(dueInterest, interestPaid);
|
||||
OutstandingDetails outstandingDetails = emiCalculator.getOutstandingAmountsTillDate(model, businessDate);
|
||||
if (!loan.isInterestRecalculationEnabled()) {
|
||||
BigDecimal interestPaid = periods.stream().map(LoanSchedulePeriodData::getInterestPaid).reduce(BigDecimal.ZERO,
|
||||
BigDecimal::add);
|
||||
BigDecimal dueInterest = outstandingDetails.getOutstandingInterest().getAmount();
|
||||
return MathUtil.subtractToZero(dueInterest, interestPaid, totalUnpaidPayableDueInterest);
|
||||
} else {
|
||||
return MathUtil.subtractToZero(outstandingDetails.getOutstandingInterest().getAmount(),
|
||||
totalUnpaidPayableDueInterest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
org.gradle.jvmargs=-Xmx6g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=null
|
||||
org.gradle.jvmargs=-Xmx12g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=null
|
||||
buildType=BUILD
|
||||
org.gradle.caching=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
+69
-4
@@ -19,9 +19,12 @@
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
@@ -51,11 +54,73 @@ public class LoanPrepayAmountTest extends BaseLoanIntegrationTest {
|
||||
for (int i = 7; i <= 31; i++) {
|
||||
runAt(i + " January 2024", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
HashMap prepayAmount = loanTransactionHelper.getPrepayAmount(requestSpec, responseSpec, loanId.intValue());
|
||||
Assertions.assertEquals((float) prepayAmount.get("interestPortion"),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().floatValue());
|
||||
GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = loanTransactionHelper.getPrepaymentAmount(loanId, null,
|
||||
DATETIME_PATTERN);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(prepayAmountResponse.getInterestPortion()).stripTrailingZeros(),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanPrepayAmountProgressivePartialRepayment() {
|
||||
runAt("15 March 2025", () -> {
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(
|
||||
create4IProgressive().interestRatePerPeriod(35.99).numberOfRepayments(12).isInterestRecalculationEnabled(true));
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
|
||||
loanProductsResponse.getResourceId(), "15 March 2025", 296.79, 35.99, 12, null));
|
||||
loanId = postLoansResponse.getLoanId();
|
||||
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(296.79, "15 March 2025"));
|
||||
disburseLoan(loanId, BigDecimal.valueOf(296.79), "15 March 2025");
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("16 March 2025", () -> {
|
||||
loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "16 March 2025", 59.0);
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = loanTransactionHelper.getPrepaymentAmount(loanId,
|
||||
"16 March 2025", DATETIME_PATTERN);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(prepayAmountResponse.getInterestPortion()).stripTrailingZeros(),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
for (int i = 0; i <= 45; i++) {
|
||||
LocalDate date = LocalDate.of(2025, 3, 17).plusDays(i);
|
||||
String formattedDate = DateTimeFormatter.ofPattern(DATETIME_PATTERN).format(date);
|
||||
runAt(formattedDate, () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = loanTransactionHelper.getPrepaymentAmount(loanId,
|
||||
formattedDate, DATETIME_PATTERN);
|
||||
Assertions.assertEquals(loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros(),
|
||||
BigDecimal.valueOf(prepayAmountResponse.getInterestPortion()).stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanPrepayAmountProgressivePartialRepaymentNoInterestRecalculation() {
|
||||
runAt("15 March 2025", () -> {
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(
|
||||
create4IProgressive().interestRatePerPeriod(35.99).numberOfRepayments(12).isInterestRecalculationEnabled(false));
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
|
||||
loanProductsResponse.getResourceId(), "15 March 2025", 296.79, 35.99, 12, null));
|
||||
loanId = postLoansResponse.getLoanId();
|
||||
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(296.79, "15 March 2025"));
|
||||
disburseLoan(loanId, BigDecimal.valueOf(296.79), "15 March 2025");
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("16 March 2025", () -> {
|
||||
loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "16 March 2025", 59.0);
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
GetLoansLoanIdTransactionsTemplateResponse prepayAmountResponse = loanTransactionHelper.getPrepaymentAmount(loanId,
|
||||
"16 March 2025", DATETIME_PATTERN);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(44.43).stripTrailingZeros(),
|
||||
BigDecimal.valueOf(prepayAmountResponse.getInterestPortion()).stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+145
-5
@@ -49,15 +49,17 @@ public class LoanSummaryTest extends BaseLoanIntegrationTest {
|
||||
runAt("15 January 2024", () -> {
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(3.05), loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(3.05),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "15 January 2024", 171.43);
|
||||
loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(0, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().compareTo(BigDecimal.ZERO));
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
});
|
||||
runAt("16 January 2024", () -> {
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.22), loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.22),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2024"),
|
||||
transaction(350.0, "Disbursement", "04 January 2024"), transaction(400.0, "Disbursement", "05 January 2024"),
|
||||
transaction(2.78, "Accrual", "14 January 2024"), transaction(171.43, "Repayment", "15 January 2024"),
|
||||
@@ -66,7 +68,8 @@ public class LoanSummaryTest extends BaseLoanIntegrationTest {
|
||||
runAt("17 January 2024", () -> {
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.44), loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.44),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2024"),
|
||||
transaction(350.0, "Disbursement", "04 January 2024"), transaction(400.0, "Disbursement", "05 January 2024"),
|
||||
transaction(2.78, "Accrual", "14 January 2024"), transaction(171.43, "Repayment", "15 January 2024"),
|
||||
@@ -75,7 +78,8 @@ public class LoanSummaryTest extends BaseLoanIntegrationTest {
|
||||
runAt("18 January 2024", () -> {
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.67), loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.67),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
verifyTransactions(loanId, transaction(250.0, "Disbursement", "01 January 2024"),
|
||||
transaction(350.0, "Disbursement", "04 January 2024"), transaction(400.0, "Disbursement", "05 January 2024"),
|
||||
transaction(2.78, "Accrual", "14 January 2024"), transaction(171.43, "Repayment", "15 January 2024"),
|
||||
@@ -91,4 +95,140 @@ public class LoanSummaryTest extends BaseLoanIntegrationTest {
|
||||
transaction(0.22, "Accrual", "17 January 2024"), transaction(0.23, "Accrual", "18 January 2024"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnpaidPayableNotDueInterestForProgressiveLoanInCaseOfEarlyRepaymentAlmostFullyPaid2ndPeriod() {
|
||||
runAt("15 March 2025", () -> {
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(
|
||||
create4IProgressive().interestRatePerPeriod(35.99).numberOfRepayments(12).isInterestRecalculationEnabled(true));
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
|
||||
loanProductsResponse.getResourceId(), "15 March 2025", 296.79, 35.99, 12, null));
|
||||
loanId = postLoansResponse.getLoanId();
|
||||
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(296.79, "15 March 2025"));
|
||||
disburseLoan(loanId, BigDecimal.valueOf(296.79), "15 March 2025");
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("16 March 2025", () -> {
|
||||
loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "16 March 2025", 59.0);
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("17 March 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.23),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("18 March 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.46),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("19 March 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.69),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("20 March 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.92),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("21 March 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(1.15),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("22 March 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(1.38),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("14 May 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(13.81),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
|
||||
runAt("15 May 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(14.05),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("16 May 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(14.28),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("17 May 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(14.51),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("18 May 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(14.74),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("19 May 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(14.97),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("20 May 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(15.20),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("14 June 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(20.96),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("15 June 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(21.19),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
runAt("16 June 2025", () -> {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertEquals(BigDecimal.valueOf(21.19),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
Assertions.assertEquals(BigDecimal.valueOf(0.24),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest().stripTrailingZeros());
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
@@ -2301,6 +2301,12 @@ public class LoanTransactionHelper {
|
||||
return response;
|
||||
}
|
||||
|
||||
public GetLoansLoanIdTransactionsTemplateResponse getPrepaymentAmount(final Long loanId, final String transactionDate,
|
||||
String dateformat) {
|
||||
return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.retrieveTransactionTemplate(loanId, "prepayLoan",
|
||||
dateformat, transactionDate, "en"));
|
||||
}
|
||||
|
||||
// TODO: Rewrite to use fineract-client instead!
|
||||
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
|
||||
// org.apache.fineract.client.models.PostLoansLoanIdRequest)
|
||||
|
||||
Reference in New Issue
Block a user