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")
|
@Schema(example = "200.000000")
|
||||||
public Double amount;
|
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;
|
public GetLoanCurrency currency;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -2722,7 +2722,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
|
public OutstandingAmountsDTO fetchPrepaymentDetail(final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
|
||||||
OutstandingAmountsDTO outstandingAmounts;
|
OutstandingAmountsDTO outstandingAmounts;
|
||||||
|
|
||||||
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
|
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled() && !isChargeOffOnDate(onDate)) {
|
||||||
final MathContext mc = MoneyHelper.getMathContext();
|
final MathContext mc = MoneyHelper.getMathContext();
|
||||||
|
|
||||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||||
@@ -3571,4 +3571,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
public boolean isProgressiveSchedule() {
|
public boolean isProgressiveSchedule() {
|
||||||
return getLoanProductRelatedDetail().getLoanScheduleType() == PROGRESSIVE;
|
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) {
|
public void recalculateScheduleFromLastTransaction(final Loan loan, final ScheduleGeneratorDTO generatorDTO) {
|
||||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && !loan.isChargedOff()) {
|
||||||
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
|
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
|
||||||
} else {
|
} else {
|
||||||
regenerateRepaymentSchedule(loan, generatorDTO);
|
regenerateRepaymentSchedule(loan, generatorDTO);
|
||||||
@@ -73,7 +73,7 @@ public class LoanScheduleService {
|
|||||||
* loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { recalculateFrom =
|
* loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { recalculateFrom =
|
||||||
* loanTransaction.getTransactionDate(); } } generatorDTO.setRecalculateFrom(recalculateFrom);
|
* loanTransaction.getTransactionDate(); } } generatorDTO.setRecalculateFrom(recalculateFrom);
|
||||||
*/
|
*/
|
||||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && !loan.isChargedOff()) {
|
||||||
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
|
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
|
||||||
} else {
|
} else {
|
||||||
regenerateRepaymentSchedule(loan, generatorDTO);
|
regenerateRepaymentSchedule(loan, generatorDTO);
|
||||||
|
|||||||
+1
-1
@@ -1154,7 +1154,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Loan runScheduleRecalculation(Loan loan, final LocalDate recalculateFrom) {
|
public Loan runScheduleRecalculation(Loan loan, final LocalDate recalculateFrom) {
|
||||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && !loan.isChargedOff()) {
|
||||||
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
final List<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
||||||
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
ScheduleGeneratorDTO generatorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
|
||||||
ChangedTransactionDetail changedTransactionDetail = loanScheduleService
|
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.GetLoansLoanIdRepaymentPeriod;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
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.LoanProduct;
|
||||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||||
import org.apache.fineract.client.models.PostClientsResponse;
|
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.LoanProductTestBuilder;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||||
import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
|
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.roles.RolesHelper;
|
||||||
import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
|
import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
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,
|
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
||||||
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
||||||
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
||||||
|
|||||||
+5
@@ -1920,6 +1920,11 @@ public class LoanTransactionHelper extends IntegrationTest {
|
|||||||
return chargebackPayload;
|
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,
|
public GetLoansLoanIdTransactionsTemplateResponse retrieveTransactionTemplate(String loanExternalIdStr, String command,
|
||||||
String dateFormat, String transactionDate, String locale) {
|
String dateFormat, String transactionDate, String locale) {
|
||||||
return ok(
|
return ok(
|
||||||
|
|||||||
Reference in New Issue
Block a user