FINERACT-2326: Loan point in time API now properly handles future dates
This commit is contained in:
committed by
Arnold Gálovics
parent
ccfe062649
commit
c9c9e9f835
+15
-2
@@ -65,11 +65,24 @@ public class LoanPointInTimeServiceImpl implements LoanPointInTimeService {
|
|||||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, date)));
|
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, date)));
|
||||||
|
|
||||||
Loan loan = loanAssembler.assembleFrom(loanId);
|
Loan loan = loanAssembler.assembleFrom(loanId);
|
||||||
|
|
||||||
|
int txCount = loan.getLoanTransactions().size();
|
||||||
|
int chargeCount = loan.getCharges().size();
|
||||||
removeAfterDateTransactions(loan, date);
|
removeAfterDateTransactions(loan, date);
|
||||||
removeAfterDateCharges(loan, date);
|
removeAfterDateCharges(loan, date);
|
||||||
|
int afterRemovalTxCount = loan.getLoanTransactions().size();
|
||||||
|
int afterRemovalChargeCount = loan.getCharges().size();
|
||||||
|
|
||||||
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
|
// In case the loan is cumulative and is being prepaid by the latest repayment tx, we need the
|
||||||
loanScheduleService.recalculateSchedule(loan, scheduleGeneratorDTO);
|
// recalculateFrom and recalculateTill
|
||||||
|
// set to the same date which is the prepaying transaction's date
|
||||||
|
// currently this is not implemented and opens up buggy edge cases
|
||||||
|
// we work this around only for cases when the loan is already closed or the requested date doesn't change
|
||||||
|
// the loan's state
|
||||||
|
if (txCount != afterRemovalTxCount || chargeCount != afterRemovalChargeCount) {
|
||||||
|
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
|
||||||
|
loanScheduleService.recalculateSchedule(loan, scheduleGeneratorDTO);
|
||||||
|
}
|
||||||
|
|
||||||
LoanArrearsData arrearsData = arrearsAgingService.calculateArrearsForLoan(loan);
|
LoanArrearsData arrearsData = arrearsAgingService.calculateArrearsForLoan(loan);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -992,7 +992,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void verifyArreals(LoanPointInTimeData pointInTimeData, boolean isOverDue, String overdueSince) {
|
protected void verifyArrears(LoanPointInTimeData pointInTimeData, boolean isOverDue, String overdueSince) {
|
||||||
assertThat(Objects.requireNonNull(pointInTimeData.getArrears()).getOverdue()).isEqualTo(isOverDue);
|
assertThat(Objects.requireNonNull(pointInTimeData.getArrears()).getOverdue()).isEqualTo(isOverDue);
|
||||||
if (isOverDue) {
|
if (isOverDue) {
|
||||||
assertThat(Objects.requireNonNull(pointInTimeData.getArrears().getOverDueSince()).toString()).isEqualTo(overdueSince);
|
assertThat(Objects.requireNonNull(pointInTimeData.getArrears().getOverDueSince()).toString()).isEqualTo(overdueSince);
|
||||||
|
|||||||
+108
-4
@@ -658,7 +658,7 @@ public class LoanPointInTimeTest extends BaseLoanIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_LoanPointInTimeDataWorks_ForArrealDataCalculation() {
|
public void test_LoanPointInTimeDataWorks_ForArrearsDataCalculation() {
|
||||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||||
|
|
||||||
runAt("01 January 2023", () -> {
|
runAt("01 January 2023", () -> {
|
||||||
@@ -683,7 +683,6 @@ public class LoanPointInTimeTest extends BaseLoanIntegrationTest {
|
|||||||
.interestType(InterestType.DECLINING_BALANCE)//
|
.interestType(InterestType.DECLINING_BALANCE)//
|
||||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||||
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)//
|
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)//
|
||||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)//
|
|
||||||
.isInterestRecalculationEnabled(true)//
|
.isInterestRecalculationEnabled(true)//
|
||||||
.recalculationRestFrequencyInterval(1)//
|
.recalculationRestFrequencyInterval(1)//
|
||||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
|
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
|
||||||
@@ -737,14 +736,14 @@ public class LoanPointInTimeTest extends BaseLoanIntegrationTest {
|
|||||||
|
|
||||||
LoanPointInTimeData pointInTimeData = getPointInTimeData(loanId, "10 February 2023");
|
LoanPointInTimeData pointInTimeData = getPointInTimeData(loanId, "10 February 2023");
|
||||||
verifyOutstanding(pointInTimeData, outstanding(5000.0, 0.0, 0.0, 0.0, 5000.0));
|
verifyOutstanding(pointInTimeData, outstanding(5000.0, 0.0, 0.0, 0.0, 5000.0));
|
||||||
verifyArreals(pointInTimeData, true, "2023-02-01");
|
verifyArrears(pointInTimeData, true, "2023-02-01");
|
||||||
|
|
||||||
// repay 500
|
// repay 500
|
||||||
addRepaymentForLoan(loanId, 2500.0, "01 February 2023");
|
addRepaymentForLoan(loanId, 2500.0, "01 February 2023");
|
||||||
|
|
||||||
LoanPointInTimeData pointInTimeDataAfterRepay = getPointInTimeData(loanId, "10 February 2023");
|
LoanPointInTimeData pointInTimeDataAfterRepay = getPointInTimeData(loanId, "10 February 2023");
|
||||||
verifyOutstanding(pointInTimeDataAfterRepay, outstanding(2500.0, 0.0, 0.0, 0.0, 2500.0));
|
verifyOutstanding(pointInTimeDataAfterRepay, outstanding(2500.0, 0.0, 0.0, 0.0, 2500.0));
|
||||||
verifyArreals(pointInTimeDataAfterRepay, false, null);
|
verifyArrears(pointInTimeDataAfterRepay, false, null);
|
||||||
|
|
||||||
// verify transactions
|
// verify transactions
|
||||||
verifyTransactions(loanId, //
|
verifyTransactions(loanId, //
|
||||||
@@ -754,4 +753,109 @@ public class LoanPointInTimeTest extends BaseLoanIntegrationTest {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_LoanPointInTimeDataWorks_ForArrearsDataCalculation_ForFutureDate_WithInterest() {
|
||||||
|
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||||
|
|
||||||
|
runAt("01 January 2023", () -> {
|
||||||
|
// Create Client
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
|
||||||
|
int numberOfRepayments = 3;
|
||||||
|
int repaymentEvery = 1;
|
||||||
|
|
||||||
|
// Create charges
|
||||||
|
double charge1Amount = 1.0;
|
||||||
|
double charge2Amount = 1.5;
|
||||||
|
Long charge1Id = createDisbursementPercentageCharge(charge1Amount);
|
||||||
|
Long charge2Id = createDisbursementPercentageCharge(charge2Amount);
|
||||||
|
|
||||||
|
// Create Loan Product
|
||||||
|
double interestRatePerPeriod = 10.0;
|
||||||
|
PostLoanProductsRequest product = createOnePeriod30DaysPeriodicAccrualProduct(interestRatePerPeriod) //
|
||||||
|
.numberOfRepayments(numberOfRepayments) //
|
||||||
|
.repaymentEvery(repaymentEvery) //
|
||||||
|
.installmentAmountInMultiplesOf(null) //
|
||||||
|
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()) //
|
||||||
|
.interestType(InterestType.DECLINING_BALANCE)//
|
||||||
|
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||||
|
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)//
|
||||||
|
.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 LoanProductChargeData().id(charge1Id), new LoanProductChargeData().id(charge2Id)));//
|
||||||
|
|
||||||
|
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)//
|
||||||
|
.interestRatePerPeriod(BigDecimal.valueOf(interestRatePerPeriod)).loanTermFrequency(numberOfRepayments)//
|
||||||
|
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||||
|
.loanTermFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||||
|
.interestType(InterestType.DECLINING_BALANCE)//
|
||||||
|
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||||
|
.charges(List.of(//
|
||||||
|
new PostLoansRequestChargeData().chargeId(charge1Id).amount(BigDecimal.valueOf(charge1Amount)), //
|
||||||
|
new PostLoansRequestChargeData().chargeId(charge2Id).amount(BigDecimal.valueOf(charge2Amount))//
|
||||||
|
));//
|
||||||
|
|
||||||
|
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"), //
|
||||||
|
transaction(125.0, "Repayment (at time of disbursement)", "01 January 2023") //
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("05 March 2023", () -> {
|
||||||
|
Long loanId = aLoanId.get();
|
||||||
|
|
||||||
|
// repay
|
||||||
|
addRepaymentForLoan(loanId, 5897.89, "05 March 2023");
|
||||||
|
|
||||||
|
// verify transactions
|
||||||
|
verifyTransactions(loanId, //
|
||||||
|
transaction(5000.0, "Disbursement", "01 January 2023"), //
|
||||||
|
transaction(125.0, "Repayment (at time of disbursement)", "01 January 2023"), //
|
||||||
|
transaction(5897.89, "Repayment", "05 March 2023"), //
|
||||||
|
transaction(897.89, "Accrual", "05 March 2023") //
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("05 June 2023", () -> {
|
||||||
|
Long loanId = aLoanId.get();
|
||||||
|
|
||||||
|
LoanPointInTimeData pointInTimeData = getPointInTimeData(loanId, "05 June 2023");
|
||||||
|
|
||||||
|
verifyOutstanding(pointInTimeData, outstanding(0.0, 0.0, 0.0, 0.0, 0.0));
|
||||||
|
verifyArrears(pointInTimeData, false, null);
|
||||||
|
assertThat(pointInTimeData.getArrears().getPrincipalOverdue()).isZero();
|
||||||
|
assertThat(pointInTimeData.getArrears().getFeeOverdue()).isZero();
|
||||||
|
assertThat(pointInTimeData.getArrears().getInterestOverdue()).isZero();
|
||||||
|
assertThat(pointInTimeData.getArrears().getPenaltyOverdue()).isZero();
|
||||||
|
assertThat(pointInTimeData.getArrears().getTotalOverdue()).isZero();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user