[FINERACT-1968] horizontal processing for refund for active loans transaction
This commit is contained in:
+123
-26
@@ -60,6 +60,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Slf4j
|
||||
public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
@@ -188,12 +189,23 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
if (LoanScheduleProcessingType.HORIZONTAL
|
||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||
// TODO: implementation
|
||||
LinkedHashMap<DueType, List<PaymentAllocationType>> paymentAllocationsMap = paymentAllocationTypes.stream().collect(
|
||||
Collectors.groupingBy(PaymentAllocationType::getDueType, LinkedHashMap::new, mapping(Function.identity(), toList())));
|
||||
|
||||
for (Map.Entry<DueType, List<PaymentAllocationType>> paymentAllocationsEntry : paymentAllocationsMap.entrySet()) {
|
||||
transactionAmountUnprocessed = refundTransactionHorizontally(loanTransaction, currency, installments,
|
||||
transactionAmountUnprocessed, paymentAllocationsEntry.getValue(), futureInstallmentAllocationRule,
|
||||
transactionMappings, balances);
|
||||
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (LoanScheduleProcessingType.VERTICAL
|
||||
.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getLoanScheduleProcessingType())) {
|
||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
||||
transactionAmountUnprocessed = refundTransaction(loanTransaction, currency, installments, zero, transactionMappings,
|
||||
transactionAmountUnprocessed, futureInstallmentAllocationRule, balances, paymentAllocationType);
|
||||
transactionAmountUnprocessed = refundTransactionVertically(loanTransaction, currency, installments, zero,
|
||||
transactionMappings, transactionAmountUnprocessed, futureInstallmentAllocationRule, balances,
|
||||
paymentAllocationType);
|
||||
if (!transactionAmountUnprocessed.isGreaterThanZero()) {
|
||||
break;
|
||||
}
|
||||
@@ -471,7 +483,74 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
|
||||
private Money refundTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
private Money refundTransactionHorizontally(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed,
|
||||
List<PaymentAllocationType> paymentAllocationTypes, FutureInstallmentAllocationRule futureInstallmentAllocationRule,
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Balances balances) {
|
||||
Money zero = Money.zero(currency);
|
||||
Money refundedPortion;
|
||||
outerLoop: do {
|
||||
LoanRepaymentScheduleInstallment latestPastDueInstallment = getLatestPastDueInstallmentForRefund(loanTransaction, currency,
|
||||
installments, zero);
|
||||
LoanRepaymentScheduleInstallment dueInstallment = getDueInstallmentForRefund(loanTransaction, currency, installments, zero);
|
||||
|
||||
List<LoanRepaymentScheduleInstallment> inAdvanceInstallments = getFutureInstallmentsForRefund(loanTransaction, currency,
|
||||
installments, futureInstallmentAllocationRule, zero);
|
||||
|
||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationTypes) {
|
||||
switch (paymentAllocationType.getDueType()) {
|
||||
case PAST_DUE -> {
|
||||
if (latestPastDueInstallment != null) {
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||
transactionMappings, loanTransaction, latestPastDueInstallment, currency);
|
||||
refundedPortion = processPaymentAllocation(paymentAllocationType, latestPastDueInstallment, loanTransaction,
|
||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
||||
} else {
|
||||
break outerLoop;
|
||||
}
|
||||
}
|
||||
case DUE -> {
|
||||
if (dueInstallment != null) {
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||
transactionMappings, loanTransaction, dueInstallment, currency);
|
||||
refundedPortion = processPaymentAllocation(paymentAllocationType, dueInstallment, loanTransaction,
|
||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances,
|
||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
||||
} else {
|
||||
break outerLoop;
|
||||
}
|
||||
}
|
||||
case IN_ADVANCE -> {
|
||||
int numberOfInstallments = inAdvanceInstallments.size();
|
||||
if (numberOfInstallments > 0) {
|
||||
Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode());
|
||||
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
||||
for (LoanRepaymentScheduleInstallment inAdvanceInstallment : inAdvanceInstallments) {
|
||||
if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) {
|
||||
evenPortion = evenPortion.add(balanceAdjustment);
|
||||
}
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||
transactionMappings, loanTransaction, inAdvanceInstallment, currency);
|
||||
refundedPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction,
|
||||
evenPortion, loanTransactionToRepaymentScheduleMapping, balances,
|
||||
LoanRepaymentScheduleInstallment.PaymentAction.UNPAY);
|
||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(refundedPortion);
|
||||
}
|
||||
} else {
|
||||
break outerLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (installments.stream().anyMatch(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
&& transactionAmountUnprocessed.isGreaterThanZero());
|
||||
return transactionAmountUnprocessed;
|
||||
}
|
||||
|
||||
private Money refundTransactionVertically(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Money zero,
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Money transactionAmountUnprocessed,
|
||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule, Balances balances,
|
||||
@@ -481,9 +560,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
do {
|
||||
switch (paymentAllocationType.getDueType()) {
|
||||
case PAST_DUE -> {
|
||||
currentInstallment = installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(e -> loanTransaction.isAfter(e.getDueDate()))
|
||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||
currentInstallment = getLatestPastDueInstallmentForRefund(loanTransaction, currency, installments, zero);
|
||||
if (currentInstallment != null) {
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||
@@ -494,9 +571,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
case DUE -> {
|
||||
currentInstallment = installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(installment -> loanTransaction.isOn(installment.getDueDate()))
|
||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||
currentInstallment = getDueInstallmentForRefund(loanTransaction, currency, installments, zero);
|
||||
if (currentInstallment != null) {
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||
@@ -507,22 +582,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
case IN_ADVANCE -> {
|
||||
List<LoanRepaymentScheduleInstallment> currentInstallments = new ArrayList<>();
|
||||
if (FutureInstallmentAllocationRule.REAMORTIZATION.equals(futureInstallmentAllocationRule)) {
|
||||
currentInstallments = installments.stream()
|
||||
.filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(e -> loanTransaction.isBefore(e.getDueDate())).toList();
|
||||
} else if (FutureInstallmentAllocationRule.NEXT_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
|
||||
currentInstallments = installments.stream()
|
||||
.filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(e -> loanTransaction.isBefore(e.getDueDate()))
|
||||
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
|
||||
} else if (FutureInstallmentAllocationRule.LAST_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
|
||||
currentInstallments = installments.stream()
|
||||
.filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(e -> loanTransaction.isBefore(e.getDueDate()))
|
||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
|
||||
}
|
||||
List<LoanRepaymentScheduleInstallment> currentInstallments = getFutureInstallmentsForRefund(loanTransaction, currency,
|
||||
installments, futureInstallmentAllocationRule, zero);
|
||||
int numberOfInstallments = currentInstallments.size();
|
||||
refundedPortion = zero;
|
||||
if (numberOfInstallments > 0) {
|
||||
@@ -552,6 +613,42 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
return transactionAmountUnprocessed;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LoanRepaymentScheduleInstallment getDueInstallmentForRefund(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Money zero) {
|
||||
return installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(installment -> loanTransaction.isOn(installment.getDueDate()))
|
||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LoanRepaymentScheduleInstallment getLatestPastDueInstallmentForRefund(LoanTransaction loanTransaction,
|
||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Money zero) {
|
||||
return installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(e -> loanTransaction.isAfter(e.getDueDate()))
|
||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<LoanRepaymentScheduleInstallment> getFutureInstallmentsForRefund(LoanTransaction loanTransaction,
|
||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule, Money zero) {
|
||||
List<LoanRepaymentScheduleInstallment> inAdvanceInstallments = new ArrayList<>();
|
||||
if (FutureInstallmentAllocationRule.REAMORTIZATION.equals(futureInstallmentAllocationRule)) {
|
||||
inAdvanceInstallments = installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(e -> loanTransaction.isBefore(e.getDueDate())).toList();
|
||||
} else if (FutureInstallmentAllocationRule.NEXT_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
|
||||
inAdvanceInstallments = installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(e -> loanTransaction.isBefore(e.getDueDate()))
|
||||
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
|
||||
} else if (FutureInstallmentAllocationRule.LAST_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
|
||||
inAdvanceInstallments = installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
|
||||
.filter(e -> loanTransaction.isBefore(e.getDueDate()))
|
||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
|
||||
}
|
||||
return inAdvanceInstallments;
|
||||
}
|
||||
|
||||
private void processTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Money transactionAmountUnprocessed, Set<LoanCharge> charges) {
|
||||
Money zero = Money.zero(currency);
|
||||
|
||||
+205
-5
@@ -90,9 +90,8 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refundForActiveLoanWithDefaultPaymentAllocation() {
|
||||
public void refundForActiveLoanWithDefaultPaymentAllocationProcessingVertically() {
|
||||
try {
|
||||
|
||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
|
||||
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||
.date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en"));
|
||||
@@ -102,7 +101,8 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest {
|
||||
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
|
||||
|
||||
Integer loanProductId = createLoanProduct("1000", "30", "4", assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
|
||||
Integer loanProductId = createLoanProduct("1000", "30", "4", LoanScheduleProcessingType.VERTICAL, assetAccount, incomeAccount,
|
||||
expenseAccount, overpaymentAccount);
|
||||
|
||||
final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), loanProductId, 1000L, 90, 30, 3, 0,
|
||||
"01 January 2023", "01 January 2023");
|
||||
@@ -288,15 +288,215 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refundForActiveLoanWithDefaultPaymentAllocationProcessingHorizontally() {
|
||||
try {
|
||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE);
|
||||
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||
.date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en"));
|
||||
|
||||
final Account assetAccount = accountHelper.createAssetAccount();
|
||||
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
|
||||
|
||||
Integer loanProductId = createLoanProduct("1000", "30", "4", LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount,
|
||||
expenseAccount, overpaymentAccount);
|
||||
|
||||
final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), loanProductId, 1000L, 90, 30, 3, 0,
|
||||
"01 January 2023", "01 January 2023");
|
||||
|
||||
int loanId = loanResponse.getLoanId().intValue();
|
||||
|
||||
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN)
|
||||
.approvedOnDate("01 January 2023").locale("en"));
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN)
|
||||
.transactionAmount(BigDecimal.valueOf(1000.00)).locale("en"));
|
||||
|
||||
final float feePortion = 50.0f;
|
||||
final float penaltyPortion = 100.0f;
|
||||
|
||||
Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(feePortion), false));
|
||||
|
||||
Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||
.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(penaltyPortion), true));
|
||||
|
||||
final String firstInstallmentChargeAddedDate = DATE_FORMATTER.format(LocalDate.of(2023, 1, 3));
|
||||
loanTransactionHelper.addChargesForLoan(loanId, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
|
||||
String.valueOf(fee), firstInstallmentChargeAddedDate, String.valueOf(feePortion)));
|
||||
|
||||
loanTransactionHelper.addChargesForLoan(loanId, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
|
||||
String.valueOf(penalty), firstInstallmentChargeAddedDate, String.valueOf(penaltyPortion)));
|
||||
|
||||
final String secondInstallmentChargeAddedDate = DATE_FORMATTER.format(LocalDate.of(2023, 2, 3));
|
||||
loanTransactionHelper.addChargesForLoan(loanId, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
|
||||
String.valueOf(fee), secondInstallmentChargeAddedDate, String.valueOf(feePortion)));
|
||||
|
||||
loanTransactionHelper.addChargesForLoan(loanId, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(
|
||||
String.valueOf(penalty), secondInstallmentChargeAddedDate, String.valueOf(penaltyPortion)));
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
|
||||
|
||||
GetLoansLoanIdRepaymentPeriod firstRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(2);
|
||||
GetLoansLoanIdRepaymentPeriod secondRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(3);
|
||||
GetLoansLoanIdRepaymentPeriod thirdRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(4);
|
||||
|
||||
assertEquals(5, loanDetails.getRepaymentSchedule().getPeriods().size());
|
||||
assertEquals(feePortion, firstRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(feePortion, firstRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyPortion, firstRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(penaltyPortion, firstRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, firstRepaymentInstallment.getPrincipalDue());
|
||||
assertEquals(250.0f, firstRepaymentInstallment.getPrincipalOutstanding());
|
||||
assertEquals(400.0f, firstRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(400.0f, firstRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 1, 31), firstRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(feePortion, secondRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(feePortion, secondRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyPortion, secondRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(penaltyPortion, secondRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(400.0f, secondRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(400.0f, secondRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 3, 2), secondRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, thirdRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(250.0f, thirdRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 4, 1), thirdRepaymentInstallment.getDueDate());
|
||||
|
||||
businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName())
|
||||
.date("2023.03.01").dateFormat("yyyy.MM.dd").locale("en"));
|
||||
loanTransactionHelper.makeRepayment("28 January 2023", 810.0f, loanId);
|
||||
|
||||
loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
|
||||
|
||||
firstRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(2);
|
||||
secondRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(3);
|
||||
thirdRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(4);
|
||||
|
||||
assertEquals(5, loanDetails.getRepaymentSchedule().getPeriods().size());
|
||||
assertEquals(feePortion, firstRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyPortion, firstRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, firstRepaymentInstallment.getPrincipalDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getPrincipalOutstanding());
|
||||
assertEquals(400.0f, firstRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 1, 31), firstRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(feePortion, secondRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, secondRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyPortion, secondRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, secondRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(400.0f, secondRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(0.0f, secondRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 3, 2), secondRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, thirdRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(240.0f, thirdRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 4, 1), thirdRepaymentInstallment.getDueDate());
|
||||
|
||||
loanTransactionHelper.makeRefundByCash("28 January 2023", 15.0f, loanId);
|
||||
|
||||
loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
|
||||
|
||||
firstRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(2);
|
||||
secondRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(3);
|
||||
thirdRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(4);
|
||||
|
||||
assertEquals(5, loanDetails.getRepaymentSchedule().getPeriods().size());
|
||||
assertEquals(feePortion, firstRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyPortion, firstRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, firstRepaymentInstallment.getPrincipalDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getPrincipalOutstanding());
|
||||
assertEquals(400.0f, firstRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 1, 31), firstRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(feePortion, secondRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, secondRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyPortion, secondRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, secondRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, secondRepaymentInstallment.getPrincipalDue());
|
||||
assertEquals(5.0f, secondRepaymentInstallment.getPrincipalOutstanding());
|
||||
assertEquals(400.0f, secondRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(5.0f, secondRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 3, 2), secondRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, thirdRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(250.0f, thirdRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 4, 1), thirdRepaymentInstallment.getDueDate());
|
||||
|
||||
// fully unpaying the second installment
|
||||
loanTransactionHelper.makeRefundByCash("28 January 2023", 395.0f, loanId);
|
||||
|
||||
loanDetails = loanTransactionHelper.getLoanDetails((long) loanId);
|
||||
|
||||
firstRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(2);
|
||||
secondRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(3);
|
||||
thirdRepaymentInstallment = loanDetails.getRepaymentSchedule().getPeriods().get(4);
|
||||
|
||||
assertEquals(5, loanDetails.getRepaymentSchedule().getPeriods().size());
|
||||
assertEquals(feePortion, firstRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyPortion, firstRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, firstRepaymentInstallment.getPrincipalDue());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getPrincipalOutstanding());
|
||||
assertEquals(400.0f, firstRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(0.0f, firstRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 1, 31), firstRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(feePortion, secondRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(feePortion, secondRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyPortion, secondRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(penaltyPortion, secondRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, secondRepaymentInstallment.getPrincipalDue());
|
||||
assertEquals(250.0f, secondRepaymentInstallment.getPrincipalOutstanding());
|
||||
assertEquals(400.0f, secondRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(400.0f, secondRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 3, 2), secondRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesDue());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getFeeChargesOutstanding());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getPenaltyChargesDue());
|
||||
assertEquals(0.0f, thirdRepaymentInstallment.getPenaltyChargesOutstanding());
|
||||
assertEquals(250.0f, thirdRepaymentInstallment.getTotalDueForPeriod());
|
||||
assertEquals(250.0f, thirdRepaymentInstallment.getTotalOutstandingForPeriod());
|
||||
assertEquals(LocalDate.of(2023, 4, 1), thirdRepaymentInstallment.getDueDate());
|
||||
} finally {
|
||||
GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
private Integer createLoanProduct(final String principal, final String repaymentAfterEvery, final String numberOfRepayments,
|
||||
final Account... accounts) {
|
||||
LoanScheduleProcessingType loanScheduleProcessingType, final Account... accounts) {
|
||||
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
|
||||
log.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||
final String loanProductJSON = new LoanProductTestBuilder().withMinPrincipal(principal).withPrincipal(principal)
|
||||
.withRepaymentTypeAsDays().withRepaymentAfterEvery(repaymentAfterEvery).withNumberOfRepayments(numberOfRepayments)
|
||||
.withEnableDownPayment(true, "25", true).withinterestRatePerPeriod("0").withInterestRateFrequencyTypeAsMonths()
|
||||
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
|
||||
.withLoanScheduleType(LoanScheduleType.PROGRESSIVE).withLoanScheduleProcessingType(LoanScheduleProcessingType.VERTICAL)
|
||||
.withLoanScheduleType(LoanScheduleType.PROGRESSIVE).withLoanScheduleProcessingType(loanScheduleProcessingType)
|
||||
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
|
||||
.addAdvancedPaymentAllocation(defaultAllocation).withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0")
|
||||
.build(null);
|
||||
|
||||
Reference in New Issue
Block a user