FINERACT-2319: Add penalty-first payment allocation strategy for progressive loans with backdated repayment charge handling
This commit is contained in:
committed by
Adam Saghy
parent
68b2fcb7e1
commit
fc93db16b5
+46
-15
@@ -645,26 +645,57 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected void verifyTransactions(Long loanId, TransactionExt... transactions) {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
|
||||
protected void verifyTransactions(final Long loanId, final TransactionExt... transactions) {
|
||||
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
|
||||
if (transactions == null || transactions.length == 0) {
|
||||
assertNull(loanDetails.getTransactions(), "No transaction is expected on loan " + loanId);
|
||||
} else {
|
||||
Assertions.assertNotNull(loanDetails.getTransactions());
|
||||
Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size(), "Number of transactions on loan " + loanId);
|
||||
|
||||
Arrays.stream(transactions).forEach(tr -> {
|
||||
boolean found = loanDetails.getTransactions().stream()
|
||||
.anyMatch(item -> Objects.equals(Utils.getDoubleValue(item.getAmount()), tr.amount) //
|
||||
&& Objects.equals(item.getType().getValue(), tr.type) //
|
||||
&& Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getOutstandingLoanBalance()), tr.outstandingPrincipal) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getPrincipalPortion()), tr.principalPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getInterestPortion()), tr.interestPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getFeeChargesPortion()), tr.feePortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getPenaltyChargesPortion()), tr.penaltyPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getOverpaymentPortion()), tr.overpaymentPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getUnrecognizedIncomePortion()), tr.unrecognizedPortion) //
|
||||
);
|
||||
Assertions.assertTrue(found, "Required transaction not found: " + tr + " on loan " + loanId);
|
||||
final List<GetLoansLoanIdTransactions> transactionsByDate = loanDetails.getTransactions().stream()
|
||||
.filter(item -> Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter))).toList();
|
||||
|
||||
if (transactionsByDate.isEmpty()) {
|
||||
Assertions.fail("No transactions found for date " + tr.date + " on loan " + loanId);
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean found = transactionsByDate.stream()
|
||||
.anyMatch(item -> Objects.equals(Utils.getDoubleValue(item.getAmount()), tr.amount)
|
||||
&& Objects.equals(item.getType().getValue(), tr.type)
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getOutstandingLoanBalance()), tr.outstandingPrincipal)
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getPrincipalPortion()), tr.principalPortion)
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getInterestPortion()), tr.interestPortion)
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getFeeChargesPortion()), tr.feePortion)
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getPenaltyChargesPortion()), tr.penaltyPortion)
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getOverpaymentPortion()), tr.overpaymentPortion)
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getUnrecognizedIncomePortion()), tr.unrecognizedPortion));
|
||||
|
||||
if (!found) {
|
||||
final StringBuilder errorMessage = new StringBuilder();
|
||||
errorMessage.append("Required transaction not found: ").append(tr).append(" on loan ").append(loanId);
|
||||
errorMessage.append("\nTransactions found for date ").append(tr.date).append(":");
|
||||
|
||||
for (int i = 0; i < transactionsByDate.size(); i++) {
|
||||
GetLoansLoanIdTransactions item = transactionsByDate.get(i);
|
||||
errorMessage.append("\n Transaction ").append(i + 1).append(": ");
|
||||
errorMessage.append("amount=").append(Utils.getDoubleValue(item.getAmount()));
|
||||
errorMessage.append(", type=").append(item.getType().getValue());
|
||||
errorMessage.append(", date=").append(item.getDate().format(dateTimeFormatter));
|
||||
errorMessage.append(", outstandingPrincipal=").append(Utils.getDoubleValue(item.getOutstandingLoanBalance()));
|
||||
errorMessage.append(", principalPortion=").append(Utils.getDoubleValue(item.getPrincipalPortion()));
|
||||
errorMessage.append(", interestPortion=").append(Utils.getDoubleValue(item.getInterestPortion()));
|
||||
errorMessage.append(", feePortion=").append(Utils.getDoubleValue(item.getFeeChargesPortion()));
|
||||
errorMessage.append(", penaltyPortion=").append(Utils.getDoubleValue(item.getPenaltyChargesPortion()));
|
||||
errorMessage.append(", unrecognizedPortion=").append(Utils.getDoubleValue(item.getUnrecognizedIncomePortion()));
|
||||
errorMessage.append(", overpaymentPortion=").append(Utils.getDoubleValue(item.getOverpaymentPortion()));
|
||||
errorMessage.append(", reversed=").append(item.getManuallyReversed() != null ? item.getManuallyReversed() : false);
|
||||
}
|
||||
|
||||
Assertions.fail(errorMessage.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+22
-24
@@ -714,7 +714,6 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
final String creationBusinessDay = "15 January 2023";
|
||||
AtomicReference<Long> loanId = new AtomicReference<>();
|
||||
runAt(creationBusinessDay, () -> {
|
||||
|
||||
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation();
|
||||
loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
|
||||
disbursementDay, BigDecimal.ZERO));
|
||||
@@ -723,53 +722,52 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
||||
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay)
|
||||
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000)).locale("en"));
|
||||
|
||||
chargePenalty(loanId.get(), 20.0, chargeDueDate1st);
|
||||
|
||||
addRepaymentForLoan(loanId.get(), 50.0, "10 January 2023");
|
||||
verifyTransactions(loanId.get(), //
|
||||
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
transaction(50.0, "Repayment", "10 January 2023", 970, 30, 0, 0, 20, 0.0, 0.0));
|
||||
transaction(1000, "Disbursement", disbursementDay, 1000, 0, 0, 0, 0, 0, 0),
|
||||
transaction(50, "Repayment", "10 January 2023", 950, 50, 0, 0, 0, 0, 0));
|
||||
|
||||
});
|
||||
runAt(repaymentPeriod1CloseDate, () -> {
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
||||
verifyTransactions(loanId.get(), //
|
||||
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||
transaction(50.0, "Repayment", "10 January 2023", 970, 30, 0, 0, 20, 0.0, 0.0),
|
||||
transaction(20.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 20.0, 0.0, 0.0));
|
||||
transaction(1000, "Disbursement", disbursementDay, 1000, 0, 0, 0, 0, 0, 0),
|
||||
transaction(20, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0, 0),
|
||||
transaction(50, "Repayment", "10 January 2023", 950, 50, 0, 0, 0, 0, 0),
|
||||
transaction(20, "Accrual Activity", "01 February 2023", 0, 0, 0, 0, 20, 0, 0));
|
||||
|
||||
});
|
||||
runAt(repaymentPeriod1OneDayAfterCloseDate, () -> {
|
||||
|
||||
addRepaymentForLoan(loanId.get(), 220.0, "8 January 2023");
|
||||
|
||||
verifyTransactions(loanId.get(), //
|
||||
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||
transaction(50.0, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0.0, 0.0),
|
||||
transaction(220.0, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0.0, 0.0),
|
||||
transaction(20.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 20.0, 0.0, 0.0));
|
||||
transaction(1000, "Disbursement", disbursementDay, 1000, 0, 0, 0, 0, 0, 0),
|
||||
transaction(20, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0, 0),
|
||||
transaction(50, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0, 0),
|
||||
transaction(220, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0, 0),
|
||||
transaction(20, "Accrual Activity", "01 February 2023", 0, 0, 0, 0, 20, 0, 0));
|
||||
|
||||
chargePenalty(loanId.get(), 33.0, chargeDueDate2st);
|
||||
|
||||
verifyTransactions(loanId.get(), //
|
||||
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||
transaction(50.0, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0.0, 0.0),
|
||||
transaction(220.0, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0.0, 0.0),
|
||||
transaction(53.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 53.0, 0.0, 0.0));
|
||||
transaction(1000, "Disbursement", disbursementDay, 1000, 0, 0, 0, 0, 0, 0),
|
||||
transaction(20, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0, 0),
|
||||
transaction(50, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0, 0),
|
||||
transaction(220, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0, 0),
|
||||
transaction(53, "Accrual Activity", "01 February 2023", 0, 0, 0, 0, 53, 0, 0));
|
||||
|
||||
chargeFee(loanId.get(), 12.0, chargeDueDate3st);
|
||||
|
||||
verifyTransactions(loanId.get(), //
|
||||
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||
transaction(50.0, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0.0, 0.0),
|
||||
transaction(220.0, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0.0, 0.0),
|
||||
transaction(65.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 12.0, 53.0, 0.0, 0.0));
|
||||
transaction(1000, "Disbursement", disbursementDay, 1000, 0, 0, 0, 0, 0, 0),
|
||||
transaction(20, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0, 0),
|
||||
transaction(50, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0, 0),
|
||||
transaction(220, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0, 0),
|
||||
transaction(65, "Accrual Activity", "01 February 2023", 0, 0, 0, 12, 53, 0, 0));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ public class LoanTransactionReprocessForAdvancedPaymentAllocationTest extends Ba
|
||||
.transactionAmount(50.0).externalId(loanTransactionExternalIdStr));
|
||||
|
||||
// verify transaction amounts
|
||||
verifyTransaction(LocalDate.of(2023, 2, 20), 50.0f, 0.0f, 0.0f, 50.0f, 0.0f, loanId, "repayment");
|
||||
verifyTransaction(LocalDate.of(2023, 2, 20), 50.0f, 50.0f, 0.0f, 0.0f, 0.0f, loanId, "repayment");
|
||||
|
||||
// add loan charge for a date later than repayment date
|
||||
// apply penalty
|
||||
|
||||
+9
-9
@@ -389,11 +389,11 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest extends BaseL
|
||||
assertEquals(LocalDate.of(2023, 1, 31), firstRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(feePortion, Utils.getDoubleValue(secondRepaymentInstallment.getFeeChargesDue()));
|
||||
assertEquals(0.00, Utils.getDoubleValue(secondRepaymentInstallment.getFeeChargesOutstanding()));
|
||||
assertEquals(50.00, Utils.getDoubleValue(secondRepaymentInstallment.getFeeChargesOutstanding()));
|
||||
assertEquals(penaltyPortion, Utils.getDoubleValue(secondRepaymentInstallment.getPenaltyChargesDue()));
|
||||
assertEquals(0.00, Utils.getDoubleValue(secondRepaymentInstallment.getPenaltyChargesOutstanding()));
|
||||
assertEquals(100.00, Utils.getDoubleValue(secondRepaymentInstallment.getPenaltyChargesOutstanding()));
|
||||
assertEquals(400.00, Utils.getDoubleValue(secondRepaymentInstallment.getTotalDueForPeriod()));
|
||||
assertEquals(0.00, Utils.getDoubleValue(secondRepaymentInstallment.getTotalOutstandingForPeriod()));
|
||||
assertEquals(150.00, Utils.getDoubleValue(secondRepaymentInstallment.getTotalOutstandingForPeriod()));
|
||||
assertEquals(LocalDate.of(2023, 3, 2), secondRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(0.00, Utils.getDoubleValue(thirdRepaymentInstallment.getFeeChargesDue()));
|
||||
@@ -401,7 +401,7 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest extends BaseL
|
||||
assertEquals(0.00, Utils.getDoubleValue(thirdRepaymentInstallment.getPenaltyChargesDue()));
|
||||
assertEquals(0.00, Utils.getDoubleValue(thirdRepaymentInstallment.getPenaltyChargesOutstanding()));
|
||||
assertEquals(250.00, Utils.getDoubleValue(thirdRepaymentInstallment.getTotalDueForPeriod()));
|
||||
assertEquals(240.00, Utils.getDoubleValue(thirdRepaymentInstallment.getTotalOutstandingForPeriod()));
|
||||
assertEquals(90.00, Utils.getDoubleValue(thirdRepaymentInstallment.getTotalOutstandingForPeriod()));
|
||||
assertEquals(LocalDate.of(2023, 4, 1), thirdRepaymentInstallment.getDueDate());
|
||||
|
||||
loanTransactionHelper.makeRefundByCash("28 January 2023", 15.0f, loanId);
|
||||
@@ -424,13 +424,13 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest extends BaseL
|
||||
assertEquals(LocalDate.of(2023, 1, 31), firstRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(feePortion, Utils.getDoubleValue(secondRepaymentInstallment.getFeeChargesDue()));
|
||||
assertEquals(0.00, Utils.getDoubleValue(secondRepaymentInstallment.getFeeChargesOutstanding()));
|
||||
assertEquals(50.00, Utils.getDoubleValue(secondRepaymentInstallment.getFeeChargesOutstanding()));
|
||||
assertEquals(penaltyPortion, Utils.getDoubleValue(secondRepaymentInstallment.getPenaltyChargesDue()));
|
||||
assertEquals(0.00, Utils.getDoubleValue(secondRepaymentInstallment.getPenaltyChargesOutstanding()));
|
||||
assertEquals(100.00, Utils.getDoubleValue(secondRepaymentInstallment.getPenaltyChargesOutstanding()));
|
||||
assertEquals(250.00, Utils.getDoubleValue(secondRepaymentInstallment.getPrincipalDue()));
|
||||
assertEquals(5.00, Utils.getDoubleValue(secondRepaymentInstallment.getPrincipalOutstanding()));
|
||||
assertEquals(0.00, Utils.getDoubleValue(secondRepaymentInstallment.getPrincipalOutstanding()));
|
||||
assertEquals(400.00, Utils.getDoubleValue(secondRepaymentInstallment.getTotalDueForPeriod()));
|
||||
assertEquals(5.00, Utils.getDoubleValue(secondRepaymentInstallment.getTotalOutstandingForPeriod()));
|
||||
assertEquals(150.00, Utils.getDoubleValue(secondRepaymentInstallment.getTotalOutstandingForPeriod()));
|
||||
assertEquals(LocalDate.of(2023, 3, 2), secondRepaymentInstallment.getDueDate());
|
||||
|
||||
assertEquals(0.00, Utils.getDoubleValue(thirdRepaymentInstallment.getFeeChargesDue()));
|
||||
@@ -438,7 +438,7 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest extends BaseL
|
||||
assertEquals(0.00, Utils.getDoubleValue(thirdRepaymentInstallment.getPenaltyChargesDue()));
|
||||
assertEquals(0.00, Utils.getDoubleValue(thirdRepaymentInstallment.getPenaltyChargesOutstanding()));
|
||||
assertEquals(250.00, Utils.getDoubleValue(thirdRepaymentInstallment.getTotalDueForPeriod()));
|
||||
assertEquals(250.00, Utils.getDoubleValue(thirdRepaymentInstallment.getTotalOutstandingForPeriod()));
|
||||
assertEquals(105.0, Utils.getDoubleValue(thirdRepaymentInstallment.getTotalOutstandingForPeriod()));
|
||||
assertEquals(LocalDate.of(2023, 4, 1), thirdRepaymentInstallment.getDueDate());
|
||||
|
||||
// fully unpaying the second installment
|
||||
|
||||
Reference in New Issue
Block a user