FINERACT-2208: Penalties are not recalculated after backdated transactions
This commit is contained in:
committed by
Arnold Gálovics
parent
91f0662998
commit
3198b1b99e
+33
@@ -61,6 +61,7 @@ import org.apache.fineract.client.models.AdvancedPaymentData;
|
||||
import org.apache.fineract.client.models.AllowAttributeOverrides;
|
||||
import org.apache.fineract.client.models.BusinessDateRequest;
|
||||
import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdChargesChargeIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdStatus;
|
||||
@@ -71,6 +72,7 @@ import org.apache.fineract.client.models.LoanPointInTimeData;
|
||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||
import org.apache.fineract.client.models.PostChargesResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdChargesRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
@@ -796,6 +798,14 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
return chargeId.longValue();
|
||||
}
|
||||
|
||||
protected Long createOverduePenaltyPercentageCharge(double percentageAmount, Integer feeFrequency, int feeInterval) {
|
||||
Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||
ChargesHelper.getLoanOverdueFeeJSONWithCalculationTypePercentageWithFeeInterval(String.valueOf(percentageAmount),
|
||||
feeFrequency, feeInterval));
|
||||
assertNotNull(chargeId);
|
||||
return chargeId.longValue();
|
||||
}
|
||||
|
||||
protected void verifyRepaymentSchedule(GetLoansLoanIdResponse savedLoanResponse, GetLoansLoanIdResponse actualLoanResponse,
|
||||
int totalPeriods, int identicalPeriods) {
|
||||
List<GetLoansLoanIdRepaymentPeriod> savedPeriods = savedLoanResponse.getRepaymentSchedule().getPeriods();
|
||||
@@ -934,6 +944,18 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
protected void runFromToInclusive(String fromDate, String toDate, Runnable runnable) {
|
||||
DateTimeFormatter format = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
||||
LocalDate startDate = LocalDate.parse(fromDate, format);
|
||||
LocalDate endDate = LocalDate.parse(toDate, format);
|
||||
|
||||
LocalDate currentDate = startDate;
|
||||
while (currentDate.isBefore(endDate) || currentDate.isEqual(endDate)) {
|
||||
runAt(format.format(currentDate), runnable);
|
||||
currentDate = currentDate.plusDays(1);
|
||||
}
|
||||
}
|
||||
|
||||
protected void runAt(String date, Runnable runnable) {
|
||||
try {
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
@@ -1092,6 +1114,17 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
return loanTransactionHelper.addChargeForLoan(loanId.intValue(), payload, responseSpec);
|
||||
}
|
||||
|
||||
protected List<GetLoansLoanIdChargesChargeIdResponse> getOverdueInstallmentLoanCharges(Long loanId) {
|
||||
return ok(fineractClient().loanCharges.retrieveAllLoanCharges(loanId)).stream() //
|
||||
.filter(ch -> ch.getChargeTimeType().getId().intValue() == ChargesHelper.CHARGE_OVERDUE_INSTALLMENT_FEE) //
|
||||
.toList(); //
|
||||
}
|
||||
|
||||
protected void deactivateOverdueLoanCharges(Long loanId, String fromDueDate) {
|
||||
ok(fineractClient().loanCharges.executeLoanCharge(loanId,
|
||||
new PostLoansLoanIdChargesRequest().dueDate(fromDueDate).dateFormat(DATETIME_PATTERN).locale("en"), "deactivateOverdue"));
|
||||
}
|
||||
|
||||
protected void waiveLoanCharge(Long loanId, Long chargeId, Integer installmentNumber) {
|
||||
String payload = LoanTransactionHelper.getWaiveChargeJSON(installmentNumber.toString());
|
||||
loanTransactionHelper.waiveChargesForLoan(loanId.intValue(), chargeId.intValue(), payload);
|
||||
|
||||
+36
-17
@@ -50,19 +50,19 @@ public final class ChargesHelper {
|
||||
private static final Integer CHARGE_APPLIES_TO_CLIENT = 3;
|
||||
private static final Integer CHARGE_APPLIES_TO_SHARES = 4;
|
||||
|
||||
private static final Integer CHARGE_DISBURSEMENT_FEE = 1;
|
||||
private static final Integer CHARGE_SPECIFIED_DUE_DATE = 2;
|
||||
private static final Integer CHARGE_SAVINGS_ACTIVATION_FEE = 3;
|
||||
private static final Integer CHARGE_WITHDRAWAL_FEE = 5;
|
||||
private static final Integer CHARGE_ANNUAL_FEE = 6;
|
||||
private static final Integer CHARGE_MONTHLY_FEE = 7;
|
||||
private static final Integer CHARGE_INSTALLMENT_FEE = 8;
|
||||
private static final Integer CHARGE_OVERDUE_INSTALLMENT_FEE = 9;
|
||||
private static final Integer CHARGE_OVERDRAFT_FEE = 10;
|
||||
private static final Integer WEEKLY_FEE = 11;
|
||||
private static final Integer SHAREACCOUNT_ACTIVATION = 13;
|
||||
private static final Integer SHARE_PURCHASE = 14;
|
||||
private static final Integer SHARE_REDEEM = 15;
|
||||
public static final Integer CHARGE_DISBURSEMENT_FEE = 1;
|
||||
public static final Integer CHARGE_SPECIFIED_DUE_DATE = 2;
|
||||
public static final Integer CHARGE_SAVINGS_ACTIVATION_FEE = 3;
|
||||
public static final Integer CHARGE_WITHDRAWAL_FEE = 5;
|
||||
public static final Integer CHARGE_ANNUAL_FEE = 6;
|
||||
public static final Integer CHARGE_MONTHLY_FEE = 7;
|
||||
public static final Integer CHARGE_INSTALLMENT_FEE = 8;
|
||||
public static final Integer CHARGE_OVERDUE_INSTALLMENT_FEE = 9;
|
||||
public static final Integer CHARGE_OVERDRAFT_FEE = 10;
|
||||
public static final Integer WEEKLY_FEE = 11;
|
||||
public static final Integer SHAREACCOUNT_ACTIVATION = 13;
|
||||
public static final Integer SHARE_PURCHASE = 14;
|
||||
public static final Integer SHARE_REDEEM = 15;
|
||||
|
||||
private static final Integer CHARGE_SAVINGS_NO_ACTIVITY_FEE = 16;
|
||||
|
||||
@@ -76,10 +76,10 @@ public final class ChargesHelper {
|
||||
private static final Integer CHARGE_PAYMENT_MODE_REGULAR = 0;
|
||||
private static final Integer CHARGE_PAYMENT_MODE_ACCOUNT_TRANSFER = 1;
|
||||
|
||||
private static final Integer CHARGE_FEE_FREQUENCY_DAYS = 0;
|
||||
private static final Integer CHARGE_FEE_FREQUENCY_WEEKS = 1;
|
||||
private static final Integer CHARGE_FEE_FREQUENCY_MONTHS = 2;
|
||||
private static final Integer CHARGE_FEE_FREQUENCY_YEARS = 3;
|
||||
public static final Integer CHARGE_FEE_FREQUENCY_DAYS = 0;
|
||||
public static final Integer CHARGE_FEE_FREQUENCY_WEEKS = 1;
|
||||
public static final Integer CHARGE_FEE_FREQUENCY_MONTHS = 2;
|
||||
public static final Integer CHARGE_FEE_FREQUENCY_YEARS = 3;
|
||||
|
||||
private static final boolean ACTIVE = true;
|
||||
private static final boolean PENALTY = true;
|
||||
@@ -491,6 +491,25 @@ public final class ChargesHelper {
|
||||
return chargesCreateJson;
|
||||
}
|
||||
|
||||
// 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)
|
||||
@Deprecated(forRemoval = true)
|
||||
public static String getLoanOverdueFeeJSONWithCalculationTypePercentageWithFeeInterval(String penaltyPercentageAmount,
|
||||
Integer feeFrequency, int feeInterval) {
|
||||
final HashMap<String, Object> map = populateDefaultsForLoan();
|
||||
map.put("penalty", ChargesHelper.PENALTY);
|
||||
map.put("amount", penaltyPercentageAmount);
|
||||
map.put("chargePaymentMode", ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
|
||||
map.put("chargeTimeType", CHARGE_OVERDUE_INSTALLMENT_FEE);
|
||||
map.put("chargeCalculationType", ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST);
|
||||
map.put("feeFrequency", feeFrequency);
|
||||
map.put("feeInterval", feeInterval);
|
||||
String chargesCreateJson = new Gson().toJson(map);
|
||||
LOG.info("{}", chargesCreateJson);
|
||||
return chargesCreateJson;
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
+410
@@ -0,0 +1,410 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.integrationtests.loan.penalty;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.apache.fineract.client.models.ChargeData;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
||||
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanPenaltyBackdatedTransactionTest extends BaseLoanIntegrationTest {
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
PutGlobalConfigurationsRequest request = new PutGlobalConfigurationsRequest().value(0L).enabled(true);
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.PENALTY_WAIT_PERIOD, request);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
// go back to defaults
|
||||
PutGlobalConfigurationsRequest request = new PutGlobalConfigurationsRequest().value(2L).enabled(true);
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.PENALTY_WAIT_PERIOD, request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PenaltyRecalculationWorksForBackdatedTx_WhenCumulative_1() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 3;
|
||||
int repaymentEvery = 4;
|
||||
|
||||
// Create charges
|
||||
double chargeAmount = 1.0;
|
||||
Long chargeId = createOverduePenaltyPercentageCharge(chargeAmount, ChargesHelper.CHARGE_FEE_FREQUENCY_DAYS, 1);
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
.graceOnArrearsAgeing(0).numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.installmentAmountInMultiplesOf(null) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.DAYS.longValue()) //
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.REDUCE_EMI_AMOUNT)//
|
||||
.allowPartialPeriodInterestCalcualtion(false)//
|
||||
.disallowExpectedDisbursements(false)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(false)//
|
||||
.overAppliedNumber(null)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.multiDisburseLoan(null)//
|
||||
.charges(List.of(new ChargeData().id(chargeId)));//
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
// Apply and Approve Loan
|
||||
double amount = 5000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.loanTermFrequency(numberOfRepayments * repaymentEvery)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.DAYS)//
|
||||
.loanTermFrequencyType(RepaymentFrequencyType.DAYS)//
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY);
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 January 2023"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(5000.0), "01 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023") //
|
||||
);
|
||||
});
|
||||
|
||||
runFromToInclusive("01 January 2023", "09 January 2023", () -> {
|
||||
// run accrual posting
|
||||
schedulerJobHelper.executeAndAwaitJob("Apply penalty to overdue loans");
|
||||
schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions");
|
||||
});
|
||||
|
||||
runAt("09 January 2023", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// repay 1k
|
||||
addRepaymentForLoan(loanId, 1000.0, "07 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(1000.0, "Repayment", "07 January 2023", 4016.67, 983.33, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// reverse accruals
|
||||
deactivateOverdueLoanCharges(loanId, "07 January 2023");
|
||||
|
||||
// run accrual posting
|
||||
schedulerJobHelper.executeAndAwaitJob("Apply penalty to overdue loans");
|
||||
schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(1000.0, "Repayment", "07 January 2023", 4016.67, 983.33, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0, true), //
|
||||
transaction(30.33, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 30.33, 0.0, 0.0) //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PenaltyRecalculationWorksForBackdatedTx_WhenCumulative_2() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 3;
|
||||
int repaymentEvery = 4;
|
||||
|
||||
// Create charges
|
||||
double chargeAmount = 1.0;
|
||||
Long chargeId = createOverduePenaltyPercentageCharge(chargeAmount, ChargesHelper.CHARGE_FEE_FREQUENCY_DAYS, 1);
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
.graceOnArrearsAgeing(0).numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.installmentAmountInMultiplesOf(null) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.DAYS.longValue()) //
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.REDUCE_EMI_AMOUNT)//
|
||||
.allowPartialPeriodInterestCalcualtion(false)//
|
||||
.disallowExpectedDisbursements(false)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(false)//
|
||||
.overAppliedNumber(null)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.multiDisburseLoan(null)//
|
||||
.charges(List.of(new ChargeData().id(chargeId)));//
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
// Apply and Approve Loan
|
||||
double amount = 5000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.loanTermFrequency(numberOfRepayments * repaymentEvery)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.DAYS)//
|
||||
.loanTermFrequencyType(RepaymentFrequencyType.DAYS)//
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY);
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 January 2023"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(5000.0), "01 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023") //
|
||||
);
|
||||
});
|
||||
|
||||
runFromToInclusive("01 January 2023", "09 January 2023", () -> {
|
||||
// run accrual posting
|
||||
schedulerJobHelper.executeAndAwaitJob("Apply penalty to overdue loans");
|
||||
schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions");
|
||||
});
|
||||
|
||||
runAt("09 January 2023", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// repay 1k
|
||||
addRepaymentForLoan(loanId, 1000.0, "07 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(1000.0, "Repayment", "07 January 2023", 4016.67, 983.33, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// reverse accruals
|
||||
deactivateOverdueLoanCharges(loanId, "05 January 2023");
|
||||
|
||||
// run accrual posting
|
||||
schedulerJobHelper.executeAndAwaitJob("Apply penalty to overdue loans");
|
||||
schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0, true), //
|
||||
transaction(6.83, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 6.83, 0.0, 0.0), //
|
||||
transaction(1000.0, "Repayment", "07 January 2023", 4006.83, 993.17, 0.0, 0.0, 6.83, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0, true), //
|
||||
transaction(20.49, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 20.49, 0.0, 0.0) //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PenaltyRecalculationWorksForBackdatedTx_WhenCumulative_3() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 3;
|
||||
int repaymentEvery = 4;
|
||||
|
||||
// Create charges
|
||||
double chargeAmount = 1.0;
|
||||
Long chargeId = createOverduePenaltyPercentageCharge(chargeAmount, ChargesHelper.CHARGE_FEE_FREQUENCY_DAYS, 1);
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
.graceOnArrearsAgeing(0).numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.installmentAmountInMultiplesOf(null) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.DAYS.longValue()) //
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.REDUCE_EMI_AMOUNT)//
|
||||
.allowPartialPeriodInterestCalcualtion(false)//
|
||||
.disallowExpectedDisbursements(false)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(false)//
|
||||
.overAppliedNumber(null)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.multiDisburseLoan(null)//
|
||||
.charges(List.of(new ChargeData().id(chargeId)));//
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
// Apply and Approve Loan
|
||||
double amount = 5000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.loanTermFrequency(numberOfRepayments * repaymentEvery)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.DAYS)//
|
||||
.loanTermFrequencyType(RepaymentFrequencyType.DAYS)//
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY);
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 January 2023"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(5000.0), "01 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023") //
|
||||
);
|
||||
});
|
||||
|
||||
runFromToInclusive("01 January 2023", "09 January 2023", () -> {
|
||||
// run accrual posting
|
||||
schedulerJobHelper.executeAndAwaitJob("Apply penalty to overdue loans");
|
||||
schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions");
|
||||
});
|
||||
|
||||
runAt("09 January 2023", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// repay 1k
|
||||
addRepaymentForLoan(loanId, 1000.0, "07 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(1000.0, "Repayment", "07 January 2023", 4016.67, 983.33, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// reverse accruals
|
||||
deactivateOverdueLoanCharges(loanId, "07 January 2023");
|
||||
|
||||
// run accrual posting
|
||||
schedulerJobHelper.executeAndAwaitJob("Apply penalty to overdue loans");
|
||||
schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(1000.0, "Repayment", "07 January 2023", 4016.67, 983.33, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0, true), //
|
||||
transaction(30.33, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 30.33, 0.0, 0.0) //
|
||||
);
|
||||
});
|
||||
|
||||
runAt("10 January 2023", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// repay 1k
|
||||
addRepaymentForLoan(loanId, 1000.0, "10 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(5000.0, "Disbursement", "01 January 2023", 5000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(16.67, "Accrual", "05 January 2023", 0.0, 0.0, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(1000.0, "Repayment", "07 January 2023", 4016.67, 983.33, 0.0, 0.0, 16.67, 0.0, 0.0), //
|
||||
transaction(50.01, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 50.01, 0.0, 0.0, true), //
|
||||
transaction(30.33, "Accrual", "09 January 2023", 0.0, 0.0, 0.0, 0.0, 30.33, 0.0, 0.0), //
|
||||
transaction(1000.0, "Repayment", "10 January 2023", 3047.0, 969.67, 0.0, 0.0, 30.33, 0.0, 0.0) //
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user