FINERACT-1981: interest recalculation fix for overdue cases
This commit is contained in:
committed by
Adam Saghy
parent
a29aaf0d3c
commit
04ec56382a
+57
@@ -22,8 +22,12 @@ import io.cucumber.java.en.Given;
|
|||||||
import io.cucumber.java.en.Then;
|
import io.cucumber.java.en.Then;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.client.models.BusinessStep;
|
import org.apache.fineract.client.models.BusinessStep;
|
||||||
|
import org.apache.fineract.client.models.GetBusinessStepConfigResponse;
|
||||||
import org.apache.fineract.client.models.UpdateBusinessStepConfigRequest;
|
import org.apache.fineract.client.models.UpdateBusinessStepConfigRequest;
|
||||||
import org.apache.fineract.client.services.BusinessStepConfigurationApi;
|
import org.apache.fineract.client.services.BusinessStepConfigurationApi;
|
||||||
import org.apache.fineract.test.helper.ErrorHelper;
|
import org.apache.fineract.test.helper.ErrorHelper;
|
||||||
@@ -31,6 +35,7 @@ import org.apache.fineract.test.stepdef.AbstractStepDef;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class BusinessStepStepDef extends AbstractStepDef {
|
public class BusinessStepStepDef extends AbstractStepDef {
|
||||||
|
|
||||||
private static final String WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS = "LOAN_CLOSE_OF_BUSINESS";
|
private static final String WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS = "LOAN_CLOSE_OF_BUSINESS";
|
||||||
@@ -149,4 +154,56 @@ public class BusinessStepStepDef extends AbstractStepDef {
|
|||||||
.execute();
|
.execute();
|
||||||
ErrorHelper.checkSuccessfulApiCall(response);
|
ErrorHelper.checkSuccessfulApiCall(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Given("Admin puts {string} business step into LOAN_CLOSE_OF_BUSINESS workflow")
|
||||||
|
public void putGivenJobInCOB(String businessStepName) throws IOException {
|
||||||
|
List<BusinessStep> businessSteps = retrieveLoanCOBJobSteps();
|
||||||
|
if (businessSteps.stream().anyMatch(businessStep -> businessStep.getStepName().equals(businessStepName))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
businessSteps.add(new BusinessStep().stepName(businessStepName).order((long) (1 + businessSteps.size())));
|
||||||
|
|
||||||
|
UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps);
|
||||||
|
Response<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
|
||||||
|
.execute();
|
||||||
|
ErrorHelper.checkSuccessfulApiCall(response);
|
||||||
|
|
||||||
|
logChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Then("Admin removes {string} business step into LOAN_CLOSE_OF_BUSINESS workflow")
|
||||||
|
public void removeGivenJobInCOB(String businessStepName) throws IOException {
|
||||||
|
List<BusinessStep> businessSteps = retrieveLoanCOBJobSteps();
|
||||||
|
businessSteps.removeIf(businessStep -> businessStep.getStepName().equals(businessStepName));
|
||||||
|
|
||||||
|
UpdateBusinessStepConfigRequest request = new UpdateBusinessStepConfigRequest().businessSteps(businessSteps);
|
||||||
|
Response<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
|
||||||
|
.execute();
|
||||||
|
ErrorHelper.checkSuccessfulApiCall(response);
|
||||||
|
|
||||||
|
logChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BusinessStep> retrieveLoanCOBJobSteps() throws IOException {
|
||||||
|
Response<GetBusinessStepConfigResponse> businessStepConfigResponse = businessStepConfigurationApi
|
||||||
|
.retrieveAllConfiguredBusinessStep(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS).execute();
|
||||||
|
ErrorHelper.checkSuccessfulApiCall(businessStepConfigResponse);
|
||||||
|
return businessStepConfigResponse.body().getBusinessSteps();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logChanges() throws IOException {
|
||||||
|
// --- log changes ---
|
||||||
|
Response<GetBusinessStepConfigResponse> changesResponse = businessStepConfigurationApi
|
||||||
|
.retrieveAllConfiguredBusinessStep(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS).execute();
|
||||||
|
List<BusinessStep> businessStepsChanged = changesResponse.body().getBusinessSteps();
|
||||||
|
List<String> changes = businessStepsChanged//
|
||||||
|
.stream()//
|
||||||
|
.sorted(Comparator.comparingLong(BusinessStep::getOrder))//
|
||||||
|
.map(BusinessStep::getStepName)//
|
||||||
|
.collect(Collectors.toList());//
|
||||||
|
|
||||||
|
log.info("Business steps has been CHANGED to the following:");
|
||||||
|
changes.forEach(e -> log.info(e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5657,3 +5657,74 @@ Feature: Loan
|
|||||||
| 15 February 2024 | Repayment | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
|
| 15 February 2024 | Repayment | 84.06 | 83.57 | 0.49 | 0.0 | 0.0 | 0.0 | false |
|
||||||
Then Loan's all installments have obligations met
|
Then Loan's all installments have obligations met
|
||||||
When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule
|
When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_REST_FREQUENCY" loan product "DEFAULT" transaction type to "LAST_INSTALLMENT" future installment allocation rule
|
||||||
|
|
||||||
|
Scenario: Interest recalculation - S1 daily for overdue loan
|
||||||
|
Given Global configuration "enable_business_date" is enabled
|
||||||
|
When Admin sets the business date to "1 January 2024"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin creates a fully customized loan with the following data:
|
||||||
|
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||||
|
| LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount
|
||||||
|
When Admin sets the business date to "15 July 2024"
|
||||||
|
When Admin puts "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow
|
||||||
|
When Admin runs inline COB job for Loan
|
||||||
|
Then Loan Repayment schedule has 6 periods, with the following data for periods:
|
||||||
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 50.71 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 34.28 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 5 | 31 | 01 June 2024 | | 17.85 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 6 | 30 | 01 July 2024 | | 0.0 | 17.85 | 0.58 | 0.0 | 0.0 | 18.43 | 0.0 | 0.0 | 0.0 | 18.43 |
|
||||||
|
When Admin removes "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow
|
||||||
|
|
||||||
|
Scenario: Interest recalculation - S2 2 overdue
|
||||||
|
Given Global configuration "enable_business_date" is enabled
|
||||||
|
When Admin sets the business date to "1 January 2024"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin creates a fully customized loan with the following data:
|
||||||
|
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||||
|
| LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount
|
||||||
|
When Admin sets the business date to "10 March 2024"
|
||||||
|
When Admin puts "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow
|
||||||
|
When Admin runs inline COB job for Loan
|
||||||
|
Then Loan Repayment schedule has 6 periods, with the following data for periods:
|
||||||
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 50.58 | 16.56 | 0.45 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 33.87 | 16.71 | 0.3 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 5 | 31 | 01 June 2024 | | 17.06 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 6 | 30 | 01 July 2024 | | 0.0 | 17.06 | 0.1 | 0.0 | 0.0 | 17.16 | 0.0 | 0.0 | 0.0 | 17.16 |
|
||||||
|
When Admin removes "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow
|
||||||
|
|
||||||
|
Scenario: Interest recalculation - S3 1 paid, 1 overdue
|
||||||
|
Given Global configuration "enable_business_date" is enabled
|
||||||
|
When Admin sets the business date to "1 January 2024"
|
||||||
|
When Admin creates a client with random data
|
||||||
|
When Admin creates a fully customized loan with the following data:
|
||||||
|
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||||
|
| LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 01 January 2024 | 100 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||||
|
And Admin successfully approves the loan on "1 January 2024" with "100" amount and expected disbursement date on "1 January 2024"
|
||||||
|
When Admin successfully disburse the loan on "1 January 2024" with "100" EUR transaction amount
|
||||||
|
When Admin sets the business date to "1 February 2024"
|
||||||
|
And Customer makes "AUTOPAY" repayment on "01 February 2024" with 17.01 EUR transaction amount
|
||||||
|
When Admin sets the business date to "10 March 2024"
|
||||||
|
When Admin puts "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow
|
||||||
|
When Admin runs inline COB job for Loan
|
||||||
|
Then Loan Repayment schedule has 6 periods, with the following data for periods:
|
||||||
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 |
|
||||||
|
| 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 3 | 31 | 01 April 2024 | | 50.46 | 16.59 | 0.42 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 4 | 30 | 01 May 2024 | | 33.74 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 5 | 31 | 01 June 2024 | | 16.93 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 |
|
||||||
|
| 6 | 30 | 01 July 2024 | | 0.0 | 16.93 | 0.1 | 0.0 | 0.0 | 17.03 | 0.0 | 0.0 | 0.0 | 17.03 |
|
||||||
|
When Admin removes "LOAN_INTEREST_RECALCULATION" business step into LOAN_CLOSE_OF_BUSINESS workflow
|
||||||
|
|||||||
+22
-16
@@ -871,9 +871,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
|
|
||||||
private List<LoanRepaymentScheduleInstallment> findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(LocalDate currentDate,
|
private List<LoanRepaymentScheduleInstallment> findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(LocalDate currentDate,
|
||||||
ProgressiveTransactionCtx transactionCtx) {
|
ProgressiveTransactionCtx transactionCtx) {
|
||||||
final LocalDate fromDate = transactionCtx.getInstallments().get(0).getLoan().getApprovedOnDate();
|
return transactionCtx.getInstallments().stream() //
|
||||||
return transactionCtx.getInstallments().stream().filter(installment -> !installment.getFromDate().isBefore(fromDate))
|
.filter(installment -> !installment.isDownPayment() && !installment.isAdditional())
|
||||||
.filter(installment -> installment.getDueDate().isBefore(currentDate))
|
|
||||||
.filter(installment -> installment.isOverdueOn(currentDate))
|
.filter(installment -> installment.isOverdueOn(currentDate))
|
||||||
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).toList();
|
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).toList();
|
||||||
}
|
}
|
||||||
@@ -885,27 +884,33 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
List<LoanRepaymentScheduleInstallment> overdueInstallmentsSortedByInstallmentNumber = findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(
|
List<LoanRepaymentScheduleInstallment> overdueInstallmentsSortedByInstallmentNumber = findOverdueInstallmentsBeforeDateSortedByInstallmentNumber(
|
||||||
currentDate, ctx);
|
currentDate, ctx);
|
||||||
if (!overdueInstallmentsSortedByInstallmentNumber.isEmpty()) {
|
if (!overdueInstallmentsSortedByInstallmentNumber.isEmpty()) {
|
||||||
List<LoanRepaymentScheduleInstallment> possibleCurrentInstallment = ctx.getInstallments().stream().filter(
|
List<LoanRepaymentScheduleInstallment> normalInstallments = ctx.getInstallments().stream() //
|
||||||
|
.filter(installment -> !installment.isAdditional() && !installment.isDownPayment()).toList();
|
||||||
|
|
||||||
|
Optional<LoanRepaymentScheduleInstallment> currentInstallmentOptional = normalInstallments.stream().filter(
|
||||||
installment -> installment.getFromDate().isBefore(currentDate) && !installment.getDueDate().isBefore(currentDate))
|
installment -> installment.getFromDate().isBefore(currentDate) && !installment.getDueDate().isBefore(currentDate))
|
||||||
.toList();
|
.findAny();
|
||||||
|
|
||||||
// get DUE installment or last installment
|
// get DUE installment or last installment
|
||||||
LoanRepaymentScheduleInstallment currentInstallment = !possibleCurrentInstallment.isEmpty()
|
LoanRepaymentScheduleInstallment lastInstallment = normalInstallments.stream()
|
||||||
? possibleCurrentInstallment.get(0)
|
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).get();
|
||||||
: ctx.getInstallments().stream().max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber))
|
LoanRepaymentScheduleInstallment currentInstallment = currentInstallmentOptional.orElse(lastInstallment);
|
||||||
.orElseThrow();
|
|
||||||
|
|
||||||
Money overDuePrincipal = Money.zero(ctx.getCurrency());
|
Money overDuePrincipal = Money.zero(ctx.getCurrency());
|
||||||
for (LoanRepaymentScheduleInstallment processingInstallment : overdueInstallmentsSortedByInstallmentNumber) {
|
for (LoanRepaymentScheduleInstallment processingInstallment : overdueInstallmentsSortedByInstallmentNumber) {
|
||||||
// add and subtract outstanding principal
|
// add and subtract outstanding principal
|
||||||
if (overDuePrincipal.compareTo(Money.zero(ctx.getCurrency())) != 0) {
|
if (!overDuePrincipal.isZero()) {
|
||||||
adjustOverduePrincipalForInstallment(currentDate, isLastRecalculation, processingInstallment, overDuePrincipal,
|
adjustOverduePrincipalForInstallment(currentDate, isLastRecalculation, processingInstallment, overDuePrincipal,
|
||||||
ctx);
|
ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
overDuePrincipal = overDuePrincipal.add(processingInstallment.getPrincipalOutstanding(ctx.getCurrency()).getAmount());
|
overDuePrincipal = overDuePrincipal.add(processingInstallment.getPrincipalOutstanding(ctx.getCurrency()).getAmount());
|
||||||
}
|
}
|
||||||
adjustOverduePrincipalForInstallment(currentDate, isLastRecalculation, currentInstallment, overDuePrincipal, ctx);
|
|
||||||
|
boolean adjustNeeded = !currentInstallment.equals(lastInstallment) || !lastInstallment.isOverdueOn(currentDate);
|
||||||
|
if (adjustNeeded) {
|
||||||
|
adjustOverduePrincipalForInstallment(currentDate, isLastRecalculation, currentInstallment, overDuePrincipal, ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -916,7 +921,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
boolean hasUpdate = false;
|
boolean hasUpdate = false;
|
||||||
|
|
||||||
if (currentInstallment.getLoan().getLoanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment()) {
|
if (currentInstallment.getLoan().getLoanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment()) {
|
||||||
// if we have same date for fromDate & last overdue balance change then it meas we have the up-to-date
|
// if we have same date for fromDate & last overdue balance change then it means we have the up-to-date
|
||||||
// model.
|
// model.
|
||||||
if (ctx.getLastOverdueBalanceChange() == null || fromDate.isAfter(ctx.getLastOverdueBalanceChange())) {
|
if (ctx.getLastOverdueBalanceChange() == null || fromDate.isAfter(ctx.getLastOverdueBalanceChange())) {
|
||||||
emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal);
|
emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal);
|
||||||
@@ -954,12 +959,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateInstallmentsPrincipalAndInterestByModel(ProgressiveTransactionCtx ctx) {
|
private void updateInstallmentsPrincipalAndInterestByModel(ProgressiveTransactionCtx ctx) {
|
||||||
ctx.getModel().repayments().forEach(rm -> {
|
ctx.getModel().repayments().forEach(repayment -> {
|
||||||
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
|
LoanRepaymentScheduleInstallment installment = ctx.getInstallments().stream()
|
||||||
.filter(ri -> !ri.isDownPayment() && Objects.equals(ri.getFromDate(), rm.getFromDate())).findFirst().orElse(null);
|
.filter(ri -> !ri.isDownPayment() && Objects.equals(ri.getFromDate(), repayment.getFromDate())) //
|
||||||
|
.findFirst().orElse(null);
|
||||||
if (installment != null) {
|
if (installment != null) {
|
||||||
installment.updatePrincipal(rm.getPrincipalDue().getAmount());
|
installment.updatePrincipal(repayment.getPrincipalDue().getAmount());
|
||||||
installment.updateInterestCharged(rm.getInterestDue().getAmount());
|
installment.updateInterestCharged(repayment.getInterestDue().getAmount());
|
||||||
installment.setRecalculatedInterestComponent(true);
|
installment.setRecalculatedInterestComponent(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+14
@@ -168,6 +168,20 @@ public class LoanInterestRecalculationCOBTest extends BaseLoanIntegrationTest {
|
|||||||
validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April 2023", 2008.09, 0.0, 0.0, 33.75);
|
validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April 2023", 2008.09, 0.0, 0.0, 33.75);
|
||||||
validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2023", 2041.57, 0.0, 0.0, 17.01);
|
validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2023", 2041.57, 0.0, 0.0, 17.01);
|
||||||
|
|
||||||
|
});
|
||||||
|
runAt("20 April 2023", () -> {
|
||||||
|
Long loanId = loanIdRef.get();
|
||||||
|
|
||||||
|
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
logLoanDetails(loanDetails);
|
||||||
|
|
||||||
|
validateFullyUnpaidRepaymentPeriod(loanDetails, 1, "01 February 2023", 1975.17, 0.0, 0.0, 66.67);
|
||||||
|
validateFullyUnpaidRepaymentPeriod(loanDetails, 2, "01 March 2023", 1975.17, 0.0, 0.0, 66.67);
|
||||||
|
validateFullyUnpaidRepaymentPeriod(loanDetails, 3, "01 April 2023", 1975.17, 0.0, 0.0, 66.67);
|
||||||
|
validateFullyUnpaidRepaymentPeriod(loanDetails, 4, "01 May 2023", 2074.49, 0.0, 0.0, 48.56);
|
||||||
|
|
||||||
});
|
});
|
||||||
payoffOnDateAndVerifyStatus("1 February 2023", loanIdRef.get());
|
payoffOnDateAndVerifyStatus("1 February 2023", loanIdRef.get());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user