FINERACT-1971: Fix wrong due date calculation when loan got submitted
This commit is contained in:
+5
-3
@@ -431,10 +431,12 @@ public final class LoanApplicationTerms {
|
|||||||
this.variationsDataWrapper = new LoanTermVariationsDataWrapper(loanTermVariations);
|
this.variationsDataWrapper = new LoanTermVariationsDataWrapper(loanTermVariations);
|
||||||
this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments();
|
this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments();
|
||||||
this.adjustPrincipalForFlatLoans = principal.zero();
|
this.adjustPrincipalForFlatLoans = principal.zero();
|
||||||
if (this.calculatedRepaymentsStartingFromDate == null) {
|
// We only change the seed date if `repaymentStartingFromDate was provided`
|
||||||
this.seedDate = this.expectedDisbursementDate;
|
if (this.repaymentsStartingFromDate == null) {
|
||||||
|
this.seedDate = repaymentStartDateType.isDisbursementDate() ? expectedDisbursementDate : submittedOnDate;
|
||||||
} else {
|
} else {
|
||||||
this.seedDate = this.calculatedRepaymentsStartingFromDate;
|
// When we change the seed date we are taking the `repaymentsStartingFromDate`
|
||||||
|
this.seedDate = repaymentsStartingFromDate;
|
||||||
}
|
}
|
||||||
this.calendarHistoryDataWrapper = calendarHistoryDataWrapper;
|
this.calendarHistoryDataWrapper = calendarHistoryDataWrapper;
|
||||||
this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled;
|
this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled;
|
||||||
|
|||||||
+19
-29
@@ -274,9 +274,16 @@ public class LoanScheduleAssembler {
|
|||||||
* If user has not passed the first repayments date then then derive the same based on loan type.
|
* If user has not passed the first repayments date then then derive the same based on loan type.
|
||||||
*/
|
*/
|
||||||
if (calculatedRepaymentsStartingFromDate == null) {
|
if (calculatedRepaymentsStartingFromDate == null) {
|
||||||
|
LocalDate tmpCalculatedRepaymentsStartingFromDate = deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
|
||||||
|
repaymentPeriodFrequencyType, 0, calendar, submittedOnDate, repaymentStartDateType);
|
||||||
calculatedRepaymentsStartingFromDate = deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
|
calculatedRepaymentsStartingFromDate = deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
|
||||||
repaymentPeriodFrequencyType, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar, submittedOnDate,
|
repaymentPeriodFrequencyType, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar, submittedOnDate,
|
||||||
repaymentStartDateType);
|
repaymentStartDateType);
|
||||||
|
// If calculated repayment start date does not match due to minimum days between disbursal and first
|
||||||
|
// repayment rule, we set repaymentsStartingFromDate (which will be used as seed date later)
|
||||||
|
if (!tmpCalculatedRepaymentsStartingFromDate.equals(calculatedRepaymentsStartingFromDate)) {
|
||||||
|
repaymentsStartingFromDate = calculatedRepaymentsStartingFromDate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -1102,13 +1109,12 @@ public class LoanScheduleAssembler {
|
|||||||
final RepaymentStartDateType repaymentStartDateType) {
|
final RepaymentStartDateType repaymentStartDateType) {
|
||||||
LocalDate derivedFirstRepayment = null;
|
LocalDate derivedFirstRepayment = null;
|
||||||
|
|
||||||
final LocalDate dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = RepaymentStartDateType.DISBURSEMENT_DATE.equals(
|
final LocalDate dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = expectedDisbursementDate
|
||||||
repaymentStartDateType) ? expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment) : submittedOnDate;
|
.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||||
|
final LocalDate seedDate = repaymentStartDateType.isDisbursementDate() ? expectedDisbursementDate : submittedOnDate;
|
||||||
if (calendar != null) {
|
if (calendar != null) {
|
||||||
derivedFirstRepayment = deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, expectedDisbursementDate,
|
derivedFirstRepayment = deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, seedDate,
|
||||||
repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate,
|
repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate);
|
||||||
repaymentStartDateType);
|
|
||||||
} else { // Individual or group account, or JLG not linked to a meeting
|
} else { // Individual or group account, or JLG not linked to a meeting
|
||||||
LocalDate dateBasedOnRepaymentFrequency;
|
LocalDate dateBasedOnRepaymentFrequency;
|
||||||
// Derive the first repayment date as greater date among
|
// Derive the first repayment date as greater date among
|
||||||
@@ -1116,25 +1122,13 @@ public class LoanScheduleAssembler {
|
|||||||
// (disbursement date + minimum between disbursal and first
|
// (disbursement date + minimum between disbursal and first
|
||||||
// repayment )
|
// repayment )
|
||||||
if (repaymentPeriodFrequencyType.isDaily()) {
|
if (repaymentPeriodFrequencyType.isDaily()) {
|
||||||
dateBasedOnRepaymentFrequency = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
|
dateBasedOnRepaymentFrequency = seedDate.plusDays(repaymentEvery);
|
||||||
? expectedDisbursementDate.plusDays(repaymentEvery)
|
|
||||||
: submittedOnDate.plusDays(repaymentEvery);
|
|
||||||
|
|
||||||
} else if (repaymentPeriodFrequencyType.isWeekly()) {
|
} else if (repaymentPeriodFrequencyType.isWeekly()) {
|
||||||
dateBasedOnRepaymentFrequency = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
|
dateBasedOnRepaymentFrequency = seedDate.plusWeeks(repaymentEvery);
|
||||||
? expectedDisbursementDate.plusWeeks(repaymentEvery)
|
|
||||||
: submittedOnDate.plusWeeks(repaymentEvery);
|
|
||||||
|
|
||||||
} else if (repaymentPeriodFrequencyType.isMonthly()) {
|
} else if (repaymentPeriodFrequencyType.isMonthly()) {
|
||||||
dateBasedOnRepaymentFrequency = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
|
dateBasedOnRepaymentFrequency = seedDate.plusMonths(repaymentEvery);
|
||||||
? expectedDisbursementDate.plusMonths(repaymentEvery)
|
|
||||||
: submittedOnDate.plusMonths(repaymentEvery);
|
|
||||||
|
|
||||||
} else { // yearly loan
|
} else { // yearly loan
|
||||||
dateBasedOnRepaymentFrequency = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
|
dateBasedOnRepaymentFrequency = seedDate.plusYears(repaymentEvery);
|
||||||
? expectedDisbursementDate.plusYears(repaymentEvery)
|
|
||||||
: submittedOnDate.plusYears(repaymentEvery);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
derivedFirstRepayment = DateUtils.isAfter(dateBasedOnRepaymentFrequency,
|
derivedFirstRepayment = DateUtils.isAfter(dateBasedOnRepaymentFrequency,
|
||||||
dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment) ? dateBasedOnRepaymentFrequency
|
dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment) ? dateBasedOnRepaymentFrequency
|
||||||
@@ -1146,20 +1140,16 @@ public class LoanScheduleAssembler {
|
|||||||
|
|
||||||
private LocalDate deriveFirstRepaymentDateForLoans(final Integer repaymentEvery, final LocalDate expectedDisbursementDate,
|
private LocalDate deriveFirstRepaymentDateForLoans(final Integer repaymentEvery, final LocalDate expectedDisbursementDate,
|
||||||
final LocalDate refernceDateForCalculatingFirstRepaymentDate, final PeriodFrequencyType repaymentPeriodFrequencyType,
|
final LocalDate refernceDateForCalculatingFirstRepaymentDate, final PeriodFrequencyType repaymentPeriodFrequencyType,
|
||||||
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar, final LocalDate submittedOnDate,
|
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar, final LocalDate submittedOnDate) {
|
||||||
final RepaymentStartDateType repaymentStartDateType) {
|
|
||||||
boolean isMeetingSkipOnFirstDayOfMonth = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
|
boolean isMeetingSkipOnFirstDayOfMonth = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
|
||||||
int numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
|
int numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
|
||||||
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
|
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
|
||||||
final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar,
|
final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar,
|
||||||
refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency, isMeetingSkipOnFirstDayOfMonth, numberOfDays);
|
refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency, isMeetingSkipOnFirstDayOfMonth, numberOfDays);
|
||||||
final LocalDate minimumFirstRepaymentDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
|
final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
|
||||||
? expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment)
|
|
||||||
: submittedOnDate;
|
|
||||||
return DateUtils.isBefore(minimumFirstRepaymentDate, derivedFirstRepayment) ? derivedFirstRepayment
|
return DateUtils.isBefore(minimumFirstRepaymentDate, derivedFirstRepayment) ? derivedFirstRepayment
|
||||||
: deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, derivedFirstRepayment,
|
: deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, derivedFirstRepayment,
|
||||||
repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate,
|
repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate);
|
||||||
repaymentStartDateType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final LocalDate disbursalDate, final LocalDate firstRepaymentDate,
|
private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final LocalDate disbursalDate, final LocalDate firstRepaymentDate,
|
||||||
|
|||||||
+80
-66
@@ -91,6 +91,7 @@ import org.apache.fineract.integrationtests.common.system.CodeHelper;
|
|||||||
import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper;
|
import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper;
|
||||||
import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
|
import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||||
@@ -102,37 +103,21 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||||||
@ExtendWith(LoanTestLifecycleExtension.class)
|
@ExtendWith(LoanTestLifecycleExtension.class)
|
||||||
public abstract class BaseLoanIntegrationTest {
|
public abstract class BaseLoanIntegrationTest {
|
||||||
|
|
||||||
|
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
|
||||||
|
|
||||||
static {
|
static {
|
||||||
Utils.initializeRESTAssured();
|
Utils.initializeRESTAssured();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static final String DATETIME_PATTERN = "dd MMMM yyyy";
|
|
||||||
|
|
||||||
protected final ResponseSpecification responseSpec = createResponseSpecification(Matchers.is(200));
|
protected final ResponseSpecification responseSpec = createResponseSpecification(Matchers.is(200));
|
||||||
protected final ResponseSpecification responseSpec204 = createResponseSpecification(Matchers.is(204));
|
protected final ResponseSpecification responseSpec204 = createResponseSpecification(Matchers.is(204));
|
||||||
|
protected final LoanProductHelper loanProductHelper = new LoanProductHelper();
|
||||||
private final String fullAdminAuthKey = getFullAdminAuthKey();
|
private final String fullAdminAuthKey = getFullAdminAuthKey();
|
||||||
|
|
||||||
protected final RequestSpecification requestSpec = createRequestSpecification(fullAdminAuthKey);
|
protected final RequestSpecification requestSpec = createRequestSpecification(fullAdminAuthKey);
|
||||||
private final String nonByPassUserAuthKey = getNonByPassUserAuthKey(requestSpec, responseSpec);
|
private final String nonByPassUserAuthKey = getNonByPassUserAuthKey(requestSpec, responseSpec);
|
||||||
|
|
||||||
protected final AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec);
|
protected final AccountHelper accountHelper = new AccountHelper(requestSpec, responseSpec);
|
||||||
protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
|
||||||
protected final LoanProductHelper loanProductHelper = new LoanProductHelper();
|
|
||||||
protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec);
|
|
||||||
protected ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec);
|
|
||||||
protected SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
|
|
||||||
protected final InlineLoanCOBHelper inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec);
|
|
||||||
|
|
||||||
protected BusinessDateHelper businessDateHelper = new BusinessDateHelper();
|
|
||||||
|
|
||||||
protected final LoanAccountLockHelper loanAccountLockHelper = new LoanAccountLockHelper(requestSpec,
|
|
||||||
createResponseSpecification(Matchers.is(202)));
|
|
||||||
protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
|
||||||
|
|
||||||
// asset
|
// asset
|
||||||
protected final Account loansReceivableAccount = accountHelper.createAssetAccount("loanPortfolio");
|
protected final Account loansReceivableAccount = accountHelper.createAssetAccount("loanPortfolio");
|
||||||
|
|
||||||
protected final Account interestReceivableAccount = accountHelper.createAssetAccount("interestReceivable");
|
protected final Account interestReceivableAccount = accountHelper.createAssetAccount("interestReceivable");
|
||||||
protected final Account feeReceivableAccount = accountHelper.createAssetAccount("feeReceivable");
|
protected final Account feeReceivableAccount = accountHelper.createAssetAccount("feeReceivable");
|
||||||
protected final Account penaltyReceivableAccount = accountHelper.createAssetAccount("penaltyReceivable");
|
protected final Account penaltyReceivableAccount = accountHelper.createAssetAccount("penaltyReceivable");
|
||||||
@@ -146,7 +131,6 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
protected final Account penaltyIncomeAccount = accountHelper.createIncomeAccount("penaltyIncome");
|
protected final Account penaltyIncomeAccount = accountHelper.createIncomeAccount("penaltyIncome");
|
||||||
protected final Account feeChargeOffAccount = accountHelper.createIncomeAccount("feeChargeOff");
|
protected final Account feeChargeOffAccount = accountHelper.createIncomeAccount("feeChargeOff");
|
||||||
protected final Account penaltyChargeOffAccount = accountHelper.createIncomeAccount("penaltyChargeOff");
|
protected final Account penaltyChargeOffAccount = accountHelper.createIncomeAccount("penaltyChargeOff");
|
||||||
|
|
||||||
protected final Account recoveriesAccount = accountHelper.createIncomeAccount("recoveries");
|
protected final Account recoveriesAccount = accountHelper.createIncomeAccount("recoveries");
|
||||||
protected final Account interestIncomeChargeOffAccount = accountHelper.createIncomeAccount("interestIncomeChargeOff");
|
protected final Account interestIncomeChargeOffAccount = accountHelper.createIncomeAccount("interestIncomeChargeOff");
|
||||||
// expense
|
// expense
|
||||||
@@ -154,6 +138,61 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
protected final Account chargeOffFraudExpenseAccount = accountHelper.createExpenseAccount("chargeOffFraud");
|
protected final Account chargeOffFraudExpenseAccount = accountHelper.createExpenseAccount("chargeOffFraud");
|
||||||
protected final Account writtenOffAccount = accountHelper.createExpenseAccount();
|
protected final Account writtenOffAccount = accountHelper.createExpenseAccount();
|
||||||
protected final Account goodwillExpenseAccount = accountHelper.createExpenseAccount();
|
protected final Account goodwillExpenseAccount = accountHelper.createExpenseAccount();
|
||||||
|
protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec);
|
||||||
|
protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec);
|
||||||
|
protected ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec);
|
||||||
|
protected SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
|
||||||
|
protected final InlineLoanCOBHelper inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec);
|
||||||
|
protected final LoanAccountLockHelper loanAccountLockHelper = new LoanAccountLockHelper(requestSpec,
|
||||||
|
createResponseSpecification(Matchers.is(202)));
|
||||||
|
protected BusinessDateHelper businessDateHelper = new BusinessDateHelper();
|
||||||
|
protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
||||||
|
|
||||||
|
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
|
||||||
|
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
|
||||||
|
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
||||||
|
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
||||||
|
assertEquals(dueDate, period.getDueDate());
|
||||||
|
assertEquals(principalDue, period.getPrincipalDue());
|
||||||
|
assertEquals(principalPaid, period.getPrincipalPaid());
|
||||||
|
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
||||||
|
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
||||||
|
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, double principalDue,
|
||||||
|
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
|
||||||
|
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
||||||
|
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
||||||
|
assertEquals(principalDue, period.getPrincipalDue());
|
||||||
|
assertEquals(principalPaid, period.getPrincipalPaid());
|
||||||
|
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
||||||
|
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
||||||
|
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
|
||||||
|
double principalPaid, double principalOutstanding, double feeDue, double feePaid, double feeOutstanding, double penaltyDue,
|
||||||
|
double penaltyPaid, double penaltyOutstanding, double interestDue, double interestPaid, double interestOutstanding,
|
||||||
|
double paidInAdvance, double paidLate) {
|
||||||
|
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
||||||
|
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
||||||
|
assertEquals(dueDate, period.getDueDate());
|
||||||
|
assertEquals(principalDue, period.getPrincipalDue());
|
||||||
|
assertEquals(principalPaid, period.getPrincipalPaid());
|
||||||
|
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
||||||
|
assertEquals(feeDue, period.getFeeChargesDue());
|
||||||
|
assertEquals(feePaid, period.getFeeChargesPaid());
|
||||||
|
assertEquals(feeOutstanding, period.getFeeChargesOutstanding());
|
||||||
|
assertEquals(penaltyDue, period.getPenaltyChargesDue());
|
||||||
|
assertEquals(penaltyPaid, period.getPenaltyChargesPaid());
|
||||||
|
assertEquals(penaltyOutstanding, period.getPenaltyChargesOutstanding());
|
||||||
|
assertEquals(interestDue, period.getInterestDue());
|
||||||
|
assertEquals(interestPaid, period.getInterestPaid());
|
||||||
|
assertEquals(interestOutstanding, period.getInterestOutstanding());
|
||||||
|
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
||||||
|
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
private String getNonByPassUserAuthKey(RequestSpecification requestSpec, ResponseSpecification responseSpec) {
|
private String getNonByPassUserAuthKey(RequestSpecification requestSpec, ResponseSpecification responseSpec) {
|
||||||
// creates the user
|
// creates the user
|
||||||
@@ -287,6 +326,27 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
return advancedPaymentData;
|
return advancedPaymentData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PostLoanProductsRequest create4Period1MonthLongWithoutInterestProduct(String repaymentStrategy) {
|
||||||
|
PostLoanProductsRequest productRequest = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().multiDisburseLoan(false)//
|
||||||
|
.disallowExpectedDisbursements(false)//
|
||||||
|
.allowApprovedDisbursedAmountsOverApplied(false)//
|
||||||
|
.overAppliedCalculationType(null)//
|
||||||
|
.overAppliedNumber(null)//
|
||||||
|
.principal(1000.0)//
|
||||||
|
.numberOfRepayments(4)//
|
||||||
|
.repaymentEvery(1)//
|
||||||
|
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
|
||||||
|
.transactionProcessingStrategyCode(repaymentStrategy)//
|
||||||
|
;
|
||||||
|
if (AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(repaymentStrategy)) {
|
||||||
|
productRequest.loanScheduleType("PROGRESSIVE").loanScheduleProcessingType("HORIZONTAL")
|
||||||
|
.addPaymentAllocationItem(createDefaultPaymentAllocation("NEXT_INSTALLMENT"));
|
||||||
|
} else {
|
||||||
|
productRequest.loanScheduleType("CUMULATIVE").loanScheduleProcessingType(null).paymentAllocation(null);
|
||||||
|
}
|
||||||
|
return productRequest;
|
||||||
|
}
|
||||||
|
|
||||||
protected PostLoanProductsRequest create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
|
protected PostLoanProductsRequest create1InstallmentAmountInMultiplesOf4Period1MonthLongWithInterestAndAmortizationProduct(
|
||||||
int interestType, int amortizationType) {
|
int interestType, int amortizationType) {
|
||||||
return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().multiDisburseLoan(false)//
|
return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().multiDisburseLoan(false)//
|
||||||
@@ -773,52 +833,6 @@ public abstract class BaseLoanIntegrationTest {
|
|||||||
assertEquals(totalOverpaid, loanDetails.getTotalOverpaid());
|
assertEquals(totalOverpaid, loanDetails.getTotalOverpaid());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
|
|
||||||
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
|
|
||||||
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
|
||||||
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
|
||||||
assertEquals(dueDate, period.getDueDate());
|
|
||||||
assertEquals(principalDue, period.getPrincipalDue());
|
|
||||||
assertEquals(principalPaid, period.getPrincipalPaid());
|
|
||||||
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
|
||||||
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
|
||||||
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, double principalDue,
|
|
||||||
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
|
|
||||||
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
|
||||||
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
|
||||||
assertEquals(principalDue, period.getPrincipalDue());
|
|
||||||
assertEquals(principalPaid, period.getPrincipalPaid());
|
|
||||||
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
|
||||||
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
|
||||||
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
|
|
||||||
double principalPaid, double principalOutstanding, double feeDue, double feePaid, double feeOutstanding, double penaltyDue,
|
|
||||||
double penaltyPaid, double penaltyOutstanding, double interestDue, double interestPaid, double interestOutstanding,
|
|
||||||
double paidInAdvance, double paidLate) {
|
|
||||||
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
|
||||||
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
|
||||||
assertEquals(dueDate, period.getDueDate());
|
|
||||||
assertEquals(principalDue, period.getPrincipalDue());
|
|
||||||
assertEquals(principalPaid, period.getPrincipalPaid());
|
|
||||||
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
|
||||||
assertEquals(feeDue, period.getFeeChargesDue());
|
|
||||||
assertEquals(feePaid, period.getFeeChargesPaid());
|
|
||||||
assertEquals(feeOutstanding, period.getFeeChargesOutstanding());
|
|
||||||
assertEquals(penaltyDue, period.getPenaltyChargesDue());
|
|
||||||
assertEquals(penaltyPaid, period.getPenaltyChargesPaid());
|
|
||||||
assertEquals(penaltyOutstanding, period.getPenaltyChargesOutstanding());
|
|
||||||
assertEquals(interestDue, period.getInterestDue());
|
|
||||||
assertEquals(interestPaid, period.getInterestPaid());
|
|
||||||
assertEquals(interestOutstanding, period.getInterestOutstanding());
|
|
||||||
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
|
||||||
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void checkMaturityDates(long loanId, LocalDate expectedMaturityDate, LocalDate actualMaturityDate) {
|
protected void checkMaturityDates(long loanId, LocalDate expectedMaturityDate, LocalDate actualMaturityDate) {
|
||||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
|
||||||
|
|||||||
+286
@@ -0,0 +1,286 @@
|
|||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import static org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoansRequest;
|
||||||
|
import org.apache.fineract.client.models.PostLoansResponse;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||||
|
import org.junit.jupiter.api.Named;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
public class LoanDueCalculationTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
|
private static Stream<Arguments> processingStrategy() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(Named.of("originalStrategy",
|
||||||
|
DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY)), //
|
||||||
|
Arguments.of(Named.of("advancedStrategy", AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) //
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repayment dates are calculated from the provided date (2024-02-29). As repayment starting date was provided, it
|
||||||
|
// overrules `repayment start date type` configuration
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("processingStrategy")
|
||||||
|
public void dueDateBasedOnFirstRepaymentDate(String repaymentProcessor) {
|
||||||
|
runAt("2 February 2024", () -> {
|
||||||
|
// Client and Loan account creation
|
||||||
|
final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-01-31", 1000.0, 4,
|
||||||
|
(postLoansRequest) -> {
|
||||||
|
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
|
||||||
|
.loanTermFrequency(4).loanTermFrequencyType(2).dateFormat("yyyy-MM-dd")
|
||||||
|
.repaymentsStartingFromDate(LocalDate.of(2024, 2, 29));
|
||||||
|
});
|
||||||
|
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest);
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "29 February 2024"), //
|
||||||
|
installment(250.0, false, "29 March 2024"), //
|
||||||
|
installment(250.0, false, "29 April 2024"), //
|
||||||
|
installment(250.0, false, "29 May 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024"));
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "29 February 2024"), //
|
||||||
|
installment(250.0, false, "29 March 2024"), //
|
||||||
|
installment(250.0, false, "29 April 2024"), //
|
||||||
|
installment(250.0, false, "29 May 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "31 January 2024");
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "29 February 2024"), //
|
||||||
|
installment(250.0, false, "29 March 2024"), //
|
||||||
|
installment(250.0, false, "29 April 2024"), //
|
||||||
|
installment(250.0, false, "29 May 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repayment dates are calculated based on `repayment start date type` configuration(=Expected disbursement date).
|
||||||
|
// Expected disbursement date `2024-01-30`,
|
||||||
|
// which is used to generate repayment due date when loan got submitted and approved, however the loan got disbursed
|
||||||
|
// on `2024-01-31`,
|
||||||
|
// the repayment schedule reflects the "new date" after it got disbursed
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("processingStrategy")
|
||||||
|
public void dueDateBasedOnExpectedDisbursementDate(String repaymentProcessor) {
|
||||||
|
runAt("31 March 2024", () -> {
|
||||||
|
// Client and Loan account creation
|
||||||
|
final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor)
|
||||||
|
.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE.getValue());
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-01-30", 1000.0, 4,
|
||||||
|
(postLoansRequest) -> {
|
||||||
|
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
|
||||||
|
.loanTermFrequency(4).loanTermFrequencyType(2).dateFormat("yyyy-MM-dd");
|
||||||
|
});
|
||||||
|
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest);
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "30 January 2024"), //
|
||||||
|
installment(250.0, false, "29 February 2024"), //
|
||||||
|
installment(250.0, false, "30 March 2024"), //
|
||||||
|
installment(250.0, false, "30 April 2024"), //
|
||||||
|
installment(250.0, false, "30 May 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024"));
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "30 January 2024"), //
|
||||||
|
installment(250.0, false, "29 February 2024"), //
|
||||||
|
installment(250.0, false, "30 March 2024"), //
|
||||||
|
installment(250.0, false, "30 April 2024"), //
|
||||||
|
installment(250.0, false, "30 May 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "31 March 2024");
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 March 2024"), //
|
||||||
|
installment(250.0, false, "30 April 2024"), //
|
||||||
|
installment(250.0, false, "31 May 2024"), //
|
||||||
|
installment(250.0, false, "30 June 2024"), //
|
||||||
|
installment(250.0, false, "31 July 2024")) //
|
||||||
|
;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repayment dates are calculated based on `repayment start date type` configuration(=Submitted on date). Submitted
|
||||||
|
// on date is `2024-01-31`,
|
||||||
|
// and even the expected disbursement date is `2024-02-01`, the generated repayment schedule honors the submitted on
|
||||||
|
// date
|
||||||
|
// when it got disbursed on `2024-02-03`, the repayment schedule due dates got no changed.
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("processingStrategy")
|
||||||
|
public void dueDateBasedOnSubmittedOnDate(String repaymentProcessor) {
|
||||||
|
runAt("03 February 2024", () -> {
|
||||||
|
// Client and Loan account creation
|
||||||
|
final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor)
|
||||||
|
.repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE.getValue());
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-02-01", 1000.0, 4,
|
||||||
|
(postLoansRequest) -> {
|
||||||
|
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
|
||||||
|
.loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd");
|
||||||
|
});
|
||||||
|
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest);
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "29 February 2024"), //
|
||||||
|
installment(250.0, false, "31 March 2024"), //
|
||||||
|
installment(250.0, false, "30 April 2024"), //
|
||||||
|
installment(250.0, false, "31 May 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024"));
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "29 February 2024"), //
|
||||||
|
installment(250.0, false, "31 March 2024"), //
|
||||||
|
installment(250.0, false, "30 April 2024"), //
|
||||||
|
installment(250.0, false, "31 May 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "03 February 2024");
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "01 February 2024"), //
|
||||||
|
installment(250.0, false, "29 February 2024"), //
|
||||||
|
installment(250.0, false, "31 March 2024"), //
|
||||||
|
installment(250.0, false, "30 April 2024"), //
|
||||||
|
installment(250.0, false, "31 May 2024")) //
|
||||||
|
;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repayment dates are calculated based on `repayment start date type` configuration(=Submitted on date). Submitted
|
||||||
|
// on date is `2024-01-31 the expected disbursement date is `2024-02-26`, the minimum days between disbursement and
|
||||||
|
// first repayment is 10 days
|
||||||
|
// so the repayment schedule got amended accordingly
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("processingStrategy")
|
||||||
|
public void dueDateBasedOnSubmittedOnDateButThereShallBeMinimumDaysBetweenDisbursementAndFirstRepayment(String repaymentProcessor) {
|
||||||
|
runAt("31 January 2024", () -> {
|
||||||
|
// Client and Loan account creation
|
||||||
|
final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor)
|
||||||
|
.repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE.getValue())
|
||||||
|
.minimumDaysBetweenDisbursalAndFirstRepayment(10);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-02-26", 1000.0, 4,
|
||||||
|
(postLoansRequest) -> {
|
||||||
|
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
|
||||||
|
.loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd");
|
||||||
|
});
|
||||||
|
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest);
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "07 March 2024"), //
|
||||||
|
installment(250.0, false, "07 April 2024"), //
|
||||||
|
installment(250.0, false, "07 May 2024"), //
|
||||||
|
installment(250.0, false, "07 June 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024"));
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "07 March 2024"), //
|
||||||
|
installment(250.0, false, "07 April 2024"), //
|
||||||
|
installment(250.0, false, "07 May 2024"), //
|
||||||
|
installment(250.0, false, "07 June 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "31 January 2024");
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "07 March 2024"), //
|
||||||
|
installment(250.0, false, "07 April 2024"), //
|
||||||
|
installment(250.0, false, "07 May 2024"), //
|
||||||
|
installment(250.0, false, "07 June 2024")) //
|
||||||
|
;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repayment dates are calculated based on `repayment start date type` configuration(=Disbursement date). Submitted
|
||||||
|
// on date is `2024-01-31 the expected disbursement date is `2024-02-26`, the minimum days between disbursement and
|
||||||
|
// first repayment is 36 days
|
||||||
|
// so the repayment schedule got amended accordingly
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("processingStrategy")
|
||||||
|
public void dueDateBasedOnExpectedDisbursalDateButThereShallBeMinimumDaysBetweenDisbursementAndFirstRepayment(
|
||||||
|
String repaymentProcessor) {
|
||||||
|
runAt("31 January 2024", () -> {
|
||||||
|
// Client and Loan account creation
|
||||||
|
final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
PostLoanProductsRequest loanProductsRequest = create4Period1MonthLongWithoutInterestProduct(repaymentProcessor)
|
||||||
|
.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE.getValue())
|
||||||
|
.minimumDaysBetweenDisbursalAndFirstRepayment(36);
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(loanProductsRequest);
|
||||||
|
|
||||||
|
PostLoansRequest loanRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "2024-01-31", 1000.0, 4,
|
||||||
|
(postLoansRequest) -> {
|
||||||
|
postLoansRequest.transactionProcessingStrategyCode(repaymentProcessor).repaymentEvery(1).repaymentFrequencyType(2)
|
||||||
|
.loanTermFrequency(4).loanTermFrequencyType(2).submittedOnDate("2024-01-31").dateFormat("yyyy-MM-dd");
|
||||||
|
});
|
||||||
|
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(loanRequest);
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "07 March 2024"), //
|
||||||
|
installment(250.0, false, "07 April 2024"), //
|
||||||
|
installment(250.0, false, "07 May 2024"), //
|
||||||
|
installment(250.0, false, "07 June 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(1000.0, "31 January 2024"));
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "07 March 2024"), //
|
||||||
|
installment(250.0, false, "07 April 2024"), //
|
||||||
|
installment(250.0, false, "07 May 2024"), //
|
||||||
|
installment(250.0, false, "07 June 2024")) //
|
||||||
|
;
|
||||||
|
|
||||||
|
disburseLoan(postLoansResponse.getLoanId(), BigDecimal.valueOf(1000.00), "31 January 2024");
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(postLoansResponse.getLoanId(), installment(1000.0, null, "31 January 2024"), //
|
||||||
|
installment(250.0, false, "07 March 2024"), //
|
||||||
|
installment(250.0, false, "07 April 2024"), //
|
||||||
|
installment(250.0, false, "07 May 2024"), //
|
||||||
|
installment(250.0, false, "07 June 2024")) //
|
||||||
|
;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user