FINERACT-2148: Stop recalculating interest if the loan is charged-off
This commit is contained in:
committed by
Adam Saghy
parent
e1d043bb2d
commit
4f1184f20b
+8
@@ -79,6 +79,14 @@ final class LoanTransactionsApiResourceSwagger {
|
||||
|
||||
@Schema(example = "200.000000")
|
||||
public Double amount;
|
||||
@Schema(example = "100.000000")
|
||||
public Double principalPortion;
|
||||
@Schema(example = "80.000000")
|
||||
public Double interestPortion;
|
||||
@Schema(example = "20.000000")
|
||||
public Double feeChargesPortion;
|
||||
@Schema(example = "20.000000")
|
||||
public Double penaltyChargesPortion;
|
||||
|
||||
public GetLoanCurrency currency;
|
||||
}
|
||||
|
||||
+7
-1
@@ -2722,7 +2722,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
|
||||
OutstandingAmountsDTO outstandingAmounts;
|
||||
|
||||
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
|
||||
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled() && !isChargeOffOnDate(onDate)) {
|
||||
final MathContext mc = MoneyHelper.getMathContext();
|
||||
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
@@ -3571,4 +3571,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public boolean isProgressiveSchedule() {
|
||||
return getLoanProductRelatedDetail().getLoanScheduleType() == PROGRESSIVE;
|
||||
}
|
||||
|
||||
public boolean isChargeOffOnDate(final LocalDate onDate) {
|
||||
final LoanTransaction chargeOffTransaction = findChargedOffTransaction();
|
||||
return (chargeOffTransaction == null) ? false : (chargeOffTransaction.getDateOf().compareTo(onDate) <= 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -54,7 +54,7 @@ public class LoanScheduleService {
|
||||
}
|
||||
|
||||
public void recalculateScheduleFromLastTransaction(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && !loan.isChargedOff()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
|
||||
} else {
|
||||
regenerateRepaymentSchedule(loan, generatorDTO);
|
||||
@@ -73,7 +73,7 @@ public class LoanScheduleService {
|
||||
* loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { recalculateFrom =
|
||||
* loanTransaction.getTransactionDate(); } } generatorDTO.setRecalculateFrom(recalculateFrom);
|
||||
*/
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && !loan.isChargedOff()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
|
||||
} else {
|
||||
regenerateRepaymentSchedule(loan, generatorDTO);
|
||||
|
||||
+1
-1
@@ -1154,7 +1154,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
||||
}
|
||||
|
||||
public Loan runScheduleRecalculation(Loan loan, final LocalDate recalculateFrom) {
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && !loan.isChargedOff()) {
|
||||
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
||||
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||
ChangedTransactionDetail changedTransactionDetail = loanScheduleService
|
||||
|
||||
+88
@@ -52,6 +52,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
|
||||
import org.apache.fineract.client.models.LoanProduct;
|
||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||
import org.apache.fineract.client.models.PostClientsResponse;
|
||||
@@ -82,6 +83,7 @@ import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
|
||||
import org.apache.fineract.integrationtests.common.system.CodeHelper;
|
||||
import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper;
|
||||
import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||
@@ -5802,6 +5804,92 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
||||
});
|
||||
}
|
||||
|
||||
// UC153: Validate Stop recalculating interest if the loan is charged-off
|
||||
// 1. Create a Loan product with Adv. Pment. Alloc. and with Interest Recalculation enabled
|
||||
// 2. Submit Loan, approve and Disburse
|
||||
// 3. Apply Charge-Off
|
||||
// 4. Prepay the loan to get the same interest amount after Charge-Off
|
||||
@Test
|
||||
public void uc153() {
|
||||
AtomicLong createdLoanId = new AtomicLong();
|
||||
runAt("01 January 2024", () -> {
|
||||
String operationDate = "01 January 2024";
|
||||
Long clientId = client.getClientId();
|
||||
BigDecimal interestRatePerPeriod = BigDecimal.valueOf(7.0);
|
||||
|
||||
final Integer rescheduleStrategyMethod = 4; // Adjust last, unpaid period
|
||||
PostLoanProductsRequest loanProduct = createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocationAndInterestRecalculation(
|
||||
(double) 80.0, rescheduleStrategyMethod);
|
||||
final PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProduct);
|
||||
assertNotNull(loanProductResponse);
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, 100.0, 6);
|
||||
|
||||
applicationRequest = applicationRequest.numberOfRepayments(6)//
|
||||
.loanTermFrequency(6)//
|
||||
.loanTermFrequencyType(2)//
|
||||
.interestRatePerPeriod(interestRatePerPeriod)//
|
||||
.interestCalculationPeriodType(DAYS)//
|
||||
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
|
||||
.repaymentEvery(1)//
|
||||
.repaymentFrequencyType(2)//
|
||||
.maxOutstandingLoanBalance(BigDecimal.valueOf(10000.0))//
|
||||
;//
|
||||
|
||||
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()//
|
||||
.approvedLoanAmount(BigDecimal.valueOf(100))//
|
||||
.approvedOnDate(operationDate).dateFormat(DATETIME_PATTERN).locale("en"));//
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest()//
|
||||
.transactionAmount(BigDecimal.valueOf(100.0))//
|
||||
.actualDisbursementDate(operationDate).dateFormat(DATETIME_PATTERN).locale("en"));//
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||
validateLoanSummaryBalances(loanDetails, 125.67, 0.0, 100.0, 0.0, null);
|
||||
createdLoanId.set(loanResponse.getLoanId());
|
||||
});
|
||||
|
||||
runAt("01 February 2024", () -> {
|
||||
|
||||
loanTransactionHelper.makeLoanRepayment(createdLoanId.get(), new PostLoansLoanIdTransactionsRequest()
|
||||
.transactionDate("01 February 2024").dateFormat("dd MMMM yyyy").locale("en").transactionAmount(21.0));
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
validateLoanSummaryBalances(loanDetails, 104.67, 21.0, 86.11, 13.89, null);
|
||||
});
|
||||
|
||||
runAt("01 March 2024", () -> {
|
||||
String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6)
|
||||
+ Utils.randomStringGenerator("is", 5);
|
||||
String transactionExternalId = UUID.randomUUID().toString();
|
||||
Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
|
||||
|
||||
loanTransactionHelper.chargeOffLoan(createdLoanId.get(),
|
||||
new PostLoansLoanIdTransactionsRequest().transactionDate("01 March 2024").locale("en").dateFormat("dd MMMM yyyy")
|
||||
.externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
|
||||
|
||||
// Loan Prepayment (before) Charge-Off transaction - With Interest Recalculation
|
||||
GetLoansLoanIdTransactionsTemplateResponse transactionBefore = loanTransactionHelper
|
||||
.retrieveTransactionTemplate(createdLoanId.get(), "prepayLoan", "dd MMMM yyyy", "15 February 2024", "en");
|
||||
assertEquals(88.88, transactionBefore.getAmount());
|
||||
assertEquals(86.11, transactionBefore.getPrincipalPortion());
|
||||
assertEquals(2.77, transactionBefore.getInterestPortion());
|
||||
assertEquals(0.00, transactionBefore.getFeeChargesPortion());
|
||||
assertEquals(0.00, transactionBefore.getPenaltyChargesPortion());
|
||||
|
||||
// Loan Prepayment (after) Charge-Off transaction - WithOut Interest Recalculation
|
||||
GetLoansLoanIdTransactionsTemplateResponse transactionAfter = loanTransactionHelper
|
||||
.retrieveTransactionTemplate(createdLoanId.get(), "prepayLoan", "dd MMMM yyyy", "01 March 2024", "en");
|
||||
assertEquals(104.67, transactionAfter.getAmount());
|
||||
assertEquals(86.11, transactionAfter.getPrincipalPortion());
|
||||
assertEquals(18.56, transactionAfter.getInterestPortion());
|
||||
assertEquals(0.00, transactionAfter.getFeeChargesPortion());
|
||||
assertEquals(0.00, transactionAfter.getPenaltyChargesPortion());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
||||
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
||||
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
||||
|
||||
+5
@@ -1920,6 +1920,11 @@ public class LoanTransactionHelper extends IntegrationTest {
|
||||
return chargebackPayload;
|
||||
}
|
||||
|
||||
public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(Long loanId, String command, String dateFormat,
|
||||
String transactionDate, String locale) {
|
||||
return ok(fineract().loanTransactions.retrieveTransactionTemplate(loanId, command, dateFormat, transactionDate, locale));
|
||||
}
|
||||
|
||||
public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(String loanExternalIdStr, String command,
|
||||
String dateFormat, String transactionDate, String locale) {
|
||||
return ok(
|
||||
|
||||
Reference in New Issue
Block a user