Skip to content

Commit

Permalink
FINERACT-2081: Fix Accrual activity - reverse and replayed couple of …
Browse files Browse the repository at this point in the history
…times after backdated repayment
  • Loading branch information
Jose Alberto Hernandez committed Feb 19, 2025
1 parent b9ddfb4 commit 6804c67
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ public enum DefaultLoanProduct implements LoanProduct {
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_CHARGEBACK_INTEREST_PENALTY_FEE_PRINCIPAL, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALCULATION_DAILY, //
LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_ACCRUAL_ACTIVITY, //
LP2_ADV_PYMNT_INTEREST_RECOGNITION_DISBURSEMENT_DAILY_EMI_360_30_ACCRUAL_ACTIVITY, //
LP2_ADV_PYMNT_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR, //
LP2_NO_INTEREST_RECALCULATION_CHARGEBACK_ALLOCATION_INTEREST_FIRST, //
LP2_NO_INTEREST_RECALCULATION_CHARGEBACK_ALLOCATION_PRINCIPAL_FIRST, //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,7 @@ public void loanLastPaymentAmount(double lastPaymentAmountExpected) throws IOExc

@Then("Loan Repayment schedule has {int} periods, with the following data for periods:")
public void loanRepaymentSchedulePeriodsCheck(int linesExpected, DataTable table) throws IOException {

Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();
String resourceId = String.valueOf(loanId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ public abstract class TestContextKey {
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_ACCRUAL_ACTIVITY = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030AccrualActivity";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_RECOGNITION_DISBURSEMENT_DAILY_EMI_360_30_ACCRUAL_ACTIVITY = "loanProductCreateResponseLP2AdvancedPaymentInterestRecognitionDisbursementDailyEmi36030AccrualActivity";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR = "loanProductCreateResponseLP2AdvancedPaymentAccelerateMaturityChargeOffBehaviour";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_RECOGNITION_DISBURSEMENT_DAILY_EMI_360_30_ACCRUAL_ACTIVITY = "loanProductCreateResponseLP2AdvancedPaymentInterestRecognitionDisbursementDailyEmi36030AccrualActivity";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_EMI_360_30_CHARGEBACK_INTEREST_PENALTY_FEE_PRINCIPAL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalcEmi36030ChargebackInterestPenaltyFeePrincipal";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_EMI_360_30_CHARGEBACK_INTEREST_FEE_PRINCIPAL = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalcEmi36030ChargebackInterestFeePrincipal";
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALC_EMI_360_30_CHARGEBACK_PRINCIPAL_INTEREST_FEE = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalcEmi36030ChargebackPrincipalInterestFee";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1035,4 +1035,8 @@ public void updateAmount(BigDecimal bigDecimal) {

// TODO missing hashCode(), equals(Object obj), but probably OK as long as
// this is never stored in a Collection.

public void updateTransactionDate(final LocalDate transactionDate) {
this.dateOf = transactionDate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ protected void calculateAccrualActivity(LoanTransaction loanTransaction, Monetar
Money feeChargesPortion = currentInstallment.getFeeChargesCharged(currency);
Money penaltyChargesPortion = currentInstallment.getPenaltyChargesCharged(currency);
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
if (currentInstallment.isObligationsMet()) {
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,6 @@ public void testInterestBearingProgressiveNoInterestRecalculationReopenDueRevers
transaction(6.48, "Accrual Activity", "17 November 2024"), //
transaction(4.75, "Accrual Activity", "17 December 2024"), //
transaction(18.31, "Accrual Activity", "17 January 2025")); //

});
}

Expand Down Expand Up @@ -719,7 +718,7 @@ public void testAccrualActivityPostingReverseReplayAdvancedPaymentAllocation(fin

Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation();
loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
disbursementDay));
disbursementDay, BigDecimal.ZERO));

loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
Expand Down Expand Up @@ -798,7 +797,7 @@ public void testAccrualActivityPostingReverseReplayAdvancedPaymentAllocationBasi

Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation();
loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
disbursementDay));
disbursementDay, BigDecimal.ZERO));

loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
Expand Down Expand Up @@ -850,7 +849,7 @@ public void testAccrualActivityPostingForProgressiveLoanWithEarlyRepaymentAndRev
runAt(creationBusinessDay, () -> {
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation();
loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
disbursementDay));
disbursementDay, BigDecimal.ZERO));

loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
Expand Down Expand Up @@ -955,7 +954,7 @@ public void testAccrualActivityPostingForProgressiveMultiDisburseLoanWithEarlyRe
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(true);

loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
disbursementDay, disbursementDay2));
disbursementDay, disbursementDay2, BigDecimal.ZERO));

loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
Expand Down Expand Up @@ -1014,7 +1013,7 @@ public void testAccrualActivityPostingForProgressiveMultiDisburseLoanWithEarlyRe
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(true);

loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
disbursementDay, disbursementDay2));
disbursementDay, disbursementDay2, BigDecimal.ZERO));

loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
Expand Down Expand Up @@ -1119,7 +1118,7 @@ public void testAccrualActivityPostingForMultiDisburseProgressiveLoan() {
runAt(creationBusinessDay, () -> {
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(true);
loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(1000),
disbursementDay, disbursementDay2));
disbursementDay, disbursementDay2, BigDecimal.ZERO));
loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));

Expand Down Expand Up @@ -1391,6 +1390,41 @@ public void test() {
});
}

@Test
public void testReverseAndReplayedCoupleOfTimesAfterBackdatedRepayment() {
final String disbursementDay = "01 January 2025";
AtomicReference<Long> loanId = new AtomicReference<>();
runAt(disbursementDay, () -> {
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocationInterestRecalculation(true);
loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(800),
disbursementDay, disbursementDay, BigDecimal.valueOf(0.3)));

loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(800))
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));

loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay)
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(800.0)).locale("en"));
});

runAt("02 February 2025", () -> {
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
verifyTransactions(loanId.get(),
transaction(10.60, "Accrual Activity", "01 February 2025", 0.0, 0.0, 10.60, 0.0, 0.0, 0.0, 0.0, false), //
transaction(10.60, "Accrual", "01 February 2025", 0.0, 0.0, 10.60, 0.0, 0.0, 0.0, 0.0, false), //
transaction(800.0, "Disbursement", disbursementDay, 800.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false));

loanTransactionHelper.makeLoanRepayment("31 January 2025", 900.0F, loanId.get().intValue());

verifyTransactions(loanId.get(),
transaction(0.34, "Accrual Adjustment", "02 February 2025", 0.0, 0.0, 0.34, 0.0, 0.0, 0.0, 0.0, false), //
transaction(10.60, "Accrual", "01 February 2025", 0.0, 0.0, 10.60, 0.0, 0.0, 0.0, 0.0, false), //
transaction(10.26, "Accrual Activity", "31 January 2025", 0.0, 0.0, 10.26, 0.0, 0.0, 0.0, 0.0, false), //
transaction(900.0, "Repayment", "31 January 2025", 0.0, 800, 10.26, 0.0, 0.0, 0.0, 89.74, false), //
transaction(800.0, "Disbursement", disbursementDay, 800.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false));

});
}

private PostLoanProductsRequest loanProductsRequestInterestDecliningBalanceDailyRecalculationCompoundingNoneAccrualActivity() {
String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6);
String shortName = Utils.uniqueRandomStringGenerator("", 4);
Expand Down Expand Up @@ -1439,6 +1473,7 @@ private PostLoanProductsRequest loanProductsRequestInterestDecliningBalanceDaily
.minInterestRatePerPeriod(0.0)//
.interestRatePerPeriod(12.0)//
.maxInterestRatePerPeriod(30.0)//
.interestRateFrequencyType(2)// Month
.interestRateFrequencyType(3)//
.repaymentEvery(30)//
.repaymentFrequencyType(0L)//
Expand Down Expand Up @@ -1631,19 +1666,28 @@ private Long createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation
}

private Long createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation(boolean isMultiDisburse) {
Long resourceId = loanTransactionHelper
.createLoanProduct(loanProductAccountingAccrualAdvanvedPaymentAllocationAccrualActivity(isMultiDisburse)).getResourceId();
LOG.info("Test Progressive Loan Product Id {} isMultiDisburse {} http://localhost:4200/#/products/loan-products/{1}/general",
resourceId, isMultiDisburse);
return resourceId;
}

private PostLoanProductsRequest loanProductAccountingAccrualAdvanvedPaymentAllocationAccrualActivity(boolean isMultiDisburse) {
String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6);
String shortName = Utils.uniqueRandomStringGenerator("", 4);
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
Long resourceId = loanTransactionHelper.createLoanProduct(new PostLoanProductsRequest().name(name).shortName(shortName)
.multiDisburseLoan(isMultiDisburse).maxTrancheCount(isMultiDisburse ? 2 : 1).interestType(isMultiDisburse ? 0 : 1)
return new PostLoanProductsRequest().name(name).shortName(shortName).multiDisburseLoan(isMultiDisburse)
.maxTrancheCount(isMultiDisburse ? 2 : 1).interestType(isMultiDisburse ? 0 : 1)
.interestCalculationPeriodType(isMultiDisburse ? 0 : 1).disallowExpectedDisbursements(isMultiDisburse)
.description("Test loan description").currencyCode("USD").digitsAfterDecimal(2).daysInYearType(1).daysInMonthType(1)
.recalculationRestFrequencyType(1).rescheduleStrategyMethod(1).loanScheduleType(LoanScheduleType.PROGRESSIVE.name())
.recalculationRestFrequencyInterval(0).isInterestRecalculationEnabled(false).locale("en_GB").numberOfRepayments(4)
.repaymentFrequencyType(2L).repaymentEvery(1).minPrincipal(100.0).principal(1000.0).maxPrincipal(10000000.0)
.amortizationType(1).interestRatePerPeriod(0.0).interestRateFrequencyType(1).dateFormat("dd MMMM yyyy")
.recalculationRestFrequencyInterval(0).locale("en_GB").numberOfRepayments(4).repaymentFrequencyType(2L).repaymentEvery(1)
.minPrincipal(100.0).principal(1000.0).maxPrincipal(10000000.0).amortizationType(1).interestRatePerPeriod(0.0)
.interestRateFrequencyType(1).dateFormat("dd MMMM yyyy")
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).paymentAllocation(List.of(defaultAllocation))
.accountingRule(3).enableAccrualActivityPosting(true).fundSourceAccountId(fundSource.getAccountID().longValue())//
.accountingRule(3).isInterestRecalculationEnabled(false).enableAccrualActivityPosting(true)
.fundSourceAccountId(fundSource.getAccountID().longValue())//
.loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())//
.transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())//
.interestOnLoanAccountId(interestIncomeAccount.getAccountID().longValue())//
Expand All @@ -1663,18 +1707,30 @@ private Long createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation
.incomeFromChargeOffFeesAccountId(feeChargeOffAccount.getAccountID().longValue())//
.chargeOffExpenseAccountId(chargeOffExpenseAccount.getAccountID().longValue())//
.chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())//
.incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())//
).getResourceId();
.incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue());//
}

private Long createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocationInterestRecalculation(boolean isMultiDisburse) {
Long resourceId = loanTransactionHelper
.createLoanProduct(loanProductAccountingAccrualAdvanvedPaymentAllocationAccrualActivity(isMultiDisburse) //
.interestRatePerPeriod(10.0) //
.isInterestRecalculationEnabled(true)//
.preClosureInterestCalculationStrategy(1) // TILL_PRE_CLOSE_DATE
.rescheduleStrategyMethod(4) // ADJUST_LAST_UNPAID_PERIOD
.interestRecalculationCompoundingMethod(0) // NONE
.recalculationRestFrequencyType(2) // DAILY
.recalculationRestFrequencyInterval(1)//
).getResourceId();
LOG.info("Test Progressive Loan Product Id {} isMultiDisburse {} http://localhost:4200/#/products/loan-products/{1}/general",
resourceId, isMultiDisburse);
return resourceId;
}

private static Long applyForLoanApplicationAdvancedPaymentAllocation(final Long clientID, final Long loanProductID,
BigDecimal principal, String applicationDisbursementDate) {
BigDecimal principal, String applicationDisbursementDate, BigDecimal interestRatePerPeriod) {
final PostLoansRequest loanRequest = new PostLoansRequest() //
.loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2)
.repaymentEvery(1).principal(principal).amortizationType(1).interestType(0).interestRatePerPeriod(BigDecimal.ZERO)
.repaymentEvery(1).principal(principal).amortizationType(1).interestType(0).interestRatePerPeriod(interestRatePerPeriod)
.interestCalculationPeriodType(1).dateFormat("dd MMMM yyyy")
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).loanType("individual")
.expectedDisbursementDate(applicationDisbursementDate).submittedOnDate(applicationDisbursementDate).clientId(clientID)
Expand All @@ -1686,10 +1742,11 @@ private static Long applyForLoanApplicationAdvancedPaymentAllocation(final Long
}

private static Long applyForLoanApplicationAdvancedPaymentAllocation(final Long clientID, final Long loanProductID,
BigDecimal principal, String applicationDisbursementDate, String applicationDisbursementDate2) {
BigDecimal principal, String applicationDisbursementDate, String applicationDisbursementDate2,
BigDecimal interestRatePerPeriod) {
final PostLoansRequest loanRequest = new PostLoansRequest() //
.loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2)
.repaymentEvery(1).principal(principal).amortizationType(1).interestType(0).interestRatePerPeriod(BigDecimal.ZERO)
.repaymentEvery(1).principal(principal).amortizationType(1).interestType(0).interestRatePerPeriod(interestRatePerPeriod)
.interestCalculationPeriodType(0).dateFormat("dd MMMM yyyy")
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).loanType("individual")
.submittedOnDate(applicationDisbursementDate).clientId(clientID).expectedDisbursementDate(applicationDisbursementDate2)
Expand Down

0 comments on commit 6804c67

Please sign in to comment.