FINERACT-1958-Loan-Product-repayment-start-date-configuration

This commit is contained in:
Ruchi Dhamankar
2023-08-22 12:28:36 +02:00
committed by Adam Saghy
parent b1cde8a809
commit 3c6fc1e47b
20 changed files with 794 additions and 49 deletions
@@ -57,6 +57,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGenerator {
@@ -91,14 +92,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions();
LoanScheduleParams scheduleParams;
LoanScheduleParams scheduleParams = null;
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
? loanApplicationTerms.getExpectedDisbursementDate()
: loanApplicationTerms.getSubmittedOnDate();
if (loanScheduleParams == null) {
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms));
periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms));
} else if (!loanScheduleParams.isPartialUpdate()) {
scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement),
loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms),
loanScheduleParams);
periodStartDate, getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams);
} else {
scheduleParams = loanScheduleParams;
}
@@ -124,8 +127,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
boolean isFirstRepayment = true;
LocalDate firstRepaymentdate = this.scheduledDateGenerator
.generateNextRepaymentDate(loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment);
LocalDate lastRepaymentDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
? loanApplicationTerms.getExpectedDisbursementDate()
: loanApplicationTerms.getSubmittedOnDate();
LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms,
isFirstRepayment);
final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate,
loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms);
@@ -2207,7 +2213,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (loanApplicationTerms.isInterestRecalculationEnabled()) {
lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
}
LocalDate actualRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
LocalDate actualRepaymentDate = RepaymentStartDateType.DISBURSEMENT_DATE
.equals(loanApplicationTerms.getRepaymentStartDateType()) ? loanApplicationTerms.getExpectedDisbursementDate()
: loanApplicationTerms.getSubmittedOnDate();
boolean isFirstRepayment = true;
// cumulative fields
@@ -2222,7 +2230,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// Actual period Number plus interest only repayments
int instalmentNumber = 1;
LocalDate lastInstallmentDate = actualRepaymentDate;
LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate();
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
? loanApplicationTerms.getExpectedDisbursementDate()
: loanApplicationTerms.getSubmittedOnDate();
// Set fixed Amortization Amounts(either EMI or Principal )
updateAmortization(mc, loanApplicationTerms, periodNumber, outstandingBalance);
@@ -32,6 +32,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,7 +45,11 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate();
RepaymentStartDateType repaymentStartDateType = loanApplicationTerms.getRepaymentStartDateType();
LocalDate lastRepaymentDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
? loanApplicationTerms.getExpectedDisbursementDate()
: loanApplicationTerms.getSubmittedOnDate();
boolean isFirstRepayment = true;
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
@@ -125,8 +130,6 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
* @param adjustedDateDetailsDTO
* @param loanApplicationTerms
* @param holidayDetailDTO
* @param nextRepaymentPeriodDueDate
* @param rescheduleType
* @param isFirstRepayment
* @return
*/
@@ -112,6 +112,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductVariableInstallmentConfig;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.beans.factory.annotation.Autowired;
@@ -234,7 +235,11 @@ public class LoanScheduleAssembler {
final Money principalMoney = Money.of(currency, principal);
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element);
LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element);
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
final RepaymentStartDateType repaymentStartDateType = loanProduct.getRepaymentStartDateType();
LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate;
final Boolean synchDisbursement = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
@@ -261,7 +266,8 @@ public class LoanScheduleAssembler {
&& !nthDay.equals(NthDayType.INVALID.getValue())) {
LocalDate calendarStartDate = repaymentsStartingFromDate;
if (calendarStartDate == null) {
calendarStartDate = expectedDisbursementDate;
calendarStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType) ? expectedDisbursementDate
: submittedOnDate;
}
calendar = createLoanCalendar(calendarStartDate, repaymentEvery, CalendarFrequencyType.MONTHLY, dayOfWeek, nthDay);
}
@@ -272,7 +278,8 @@ public class LoanScheduleAssembler {
*/
if (calculatedRepaymentsStartingFromDate == null) {
calculatedRepaymentsStartingFromDate = deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate,
repaymentPeriodFrequencyType, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar);
repaymentPeriodFrequencyType, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar, submittedOnDate,
repaymentStartDateType);
}
/*
@@ -305,8 +312,10 @@ public class LoanScheduleAssembler {
}
}
validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate, calculatedRepaymentsStartingFromDate,
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment());
if (RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)) {
validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate, calculatedRepaymentsStartingFromDate,
loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment());
}
// grace details
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
@@ -471,7 +480,7 @@ public class LoanScheduleAssembler {
loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipMeetingOnFirstDay, detailDTO,
allowCompoundingOnEod, isEqualAmortization, isInterestToBeRecoveredFirstWhenGreaterThanEMI,
fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled,
disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled);
disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate);
}
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
@@ -1070,19 +1079,20 @@ public class LoanScheduleAssembler {
private LocalDate deriveFirstRepaymentDate(final AccountType loanType, final Integer repaymentEvery,
final LocalDate expectedDisbursementDate, final PeriodFrequencyType repaymentPeriodFrequencyType,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar) {
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar, final LocalDate submittedOnDate,
final RepaymentStartDateType repaymentStartDateType) {
LocalDate derivedFirstRepayment = null;
final LocalDate dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = expectedDisbursementDate
.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
final LocalDate dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = RepaymentStartDateType.DISBURSEMENT_DATE.equals(
repaymentStartDateType) ? expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment) : submittedOnDate;
if (calendar != null) {
final LocalDate refernceDateForCalculatingFirstRepaymentDate = expectedDisbursementDate;
derivedFirstRepayment = deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate,
refernceDateForCalculatingFirstRepaymentDate, repaymentPeriodFrequencyType,
minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate, repaymentStartDateType);
} /*** Individual or group account, or JLG not linked to a meeting ***/
else {
@@ -1092,14 +1102,26 @@ public class LoanScheduleAssembler {
// (disbursement date + minimum between disbursal and first
// repayment )
if (repaymentPeriodFrequencyType.isDaily()) {
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusDays(repaymentEvery);
dateBasedOnRepaymentFrequency = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
? expectedDisbursementDate.plusDays(repaymentEvery)
: submittedOnDate.plusDays(repaymentEvery);
} else if (repaymentPeriodFrequencyType.isWeekly()) {
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusWeeks(repaymentEvery);
dateBasedOnRepaymentFrequency = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
? expectedDisbursementDate.plusWeeks(repaymentEvery)
: submittedOnDate.plusWeeks(repaymentEvery);
} else if (repaymentPeriodFrequencyType.isMonthly()) {
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusMonths(repaymentEvery);
dateBasedOnRepaymentFrequency = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
? expectedDisbursementDate.plusMonths(repaymentEvery)
: submittedOnDate.plusMonths(repaymentEvery);
} /** yearly loan **/
else {
dateBasedOnRepaymentFrequency = expectedDisbursementDate.plusYears(repaymentEvery);
dateBasedOnRepaymentFrequency = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
? expectedDisbursementDate.plusYears(repaymentEvery)
: submittedOnDate.plusYears(repaymentEvery);
}
derivedFirstRepayment = dateBasedOnRepaymentFrequency.isAfter(dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment)
? dateBasedOnRepaymentFrequency
@@ -1111,16 +1133,20 @@ public class LoanScheduleAssembler {
private LocalDate deriveFirstRepaymentDateForLoans(final Integer repaymentEvery, final LocalDate expectedDisbursementDate,
final LocalDate refernceDateForCalculatingFirstRepaymentDate, final PeriodFrequencyType repaymentPeriodFrequencyType,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar) {
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar, final LocalDate submittedOnDate,
final RepaymentStartDateType repaymentStartDateType) {
boolean isMeetingSkipOnFirstDayOfMonth = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
int numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar,
refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency, isMeetingSkipOnFirstDayOfMonth, numberOfDays);
final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
final LocalDate minimumFirstRepaymentDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
? expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment)
: submittedOnDate;
return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment
: deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, derivedFirstRepayment,
repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar);
repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar, submittedOnDate,
repaymentStartDateType);
}
private void validateMinimumDaysBetweenDisbursalAndFirstRepayment(final LocalDate disbursalDate, final LocalDate firstRepaymentDate,
@@ -112,7 +112,8 @@ public class LoanProductsApiResource {
LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME,
LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT,
LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.ENABLE_DOWN_PAYMENT,
LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT));
LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT,
LoanProductConstants.REPAYMENT_START_DATE_TYPE));
private static final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions"));
@@ -147,7 +148,7 @@ public class LoanProductsApiResource {
@Operation(summary = "Create a Loan Product", description = "Depending of the Accounting Rule (accountingRule) selected, additional fields with details of the appropriate Ledger Account identifiers would need to be passed in.\n"
+ "\n" + "Refer MifosX Accounting Specs Draft for more details regarding the significance of the selected accounting rule\n\n"
+ "Mandatory Fields: name, shortName, currencyCode, digitsAfterDecimal, inMultiplesOf, principal, numberOfRepayments, repaymentEvery, repaymentFrequencyType, interestRatePerPeriod, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, transactionProcessingStrategyCode, accountingRule, isInterestRecalculationEnabled, daysInYearType, daysInMonthType\n\n"
+ "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, includeInBorrowerCycle, useBorrowerCycle,principalVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, allowAttributeOverrides, allowPartialPeriodInterestCalcualtion,dueDaysForRepaymentEvent,overDueDaysForRepaymentEvent,enableDownPayment,disbursedAmountPercentageDownPayment,enableAutoRepaymentForDownPayment\n\n"
+ "Optional Fields: inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, graceOnArrearsAgeing, charges, paymentChannelToFundSourceMappings, feeToIncomeAccountMappings, penaltyToIncomeAccountMappings, includeInBorrowerCycle, useBorrowerCycle,principalVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, allowAttributeOverrides, allowPartialPeriodInterestCalcualtion,dueDaysForRepaymentEvent,overDueDaysForRepaymentEvent,enableDownPayment,disbursedAmountPercentageDownPayment,enableAutoRepaymentForDownPayment,repaymentStartDateType\n\n"
+ "Additional Mandatory Fields for Cash(2) based accounting: fundSourceAccountId, loanPortfolioAccountId, interestOnLoanAccountId, incomeFromFeeAccountId, incomeFromPenaltyAccountId, writeOffAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId\n\n"
+ "Additional Mandatory Fields for periodic (3) and upfront (4)accrual accounting: fundSourceAccountId, loanPortfolioAccountId, interestOnLoanAccountId, incomeFromFeeAccountId, incomeFromPenaltyAccountId, writeOffAccountId, receivableInterestAccountId, receivableFeeAccountId, receivablePenaltyAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId\n\n"
+ "Additional Mandatory Fields if interest recalculation is enabled(true): interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType\n\n"
@@ -405,6 +406,7 @@ public class LoanProductsApiResource {
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService
.retrievePreCloseInterestCalculationStrategyOptions();
final List<FloatingRateData> floatingRateOptions = this.floatingRateReadPlatformService.retrieveLookupActive();
final List<EnumOptionData> repaymentStartDateTypeOptions = dropdownReadPlatformService.retrieveRepaymentStartDateTypeOptions();
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions,
interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
@@ -412,7 +414,7 @@ public class LoanProductsApiResource {
loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions,
interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions,
preCloseInterestCalculationStrategyOptions, floatingRateOptions, interestRecalculationNthDayTypeOptions,
interestRecalculationDayOfWeekTypeOptions, isRatesEnabled, delinquencyBucketOptions);
interestRecalculationDayOfWeekTypeOptions, isRatesEnabled, delinquencyBucketOptions, repaymentStartDateTypeOptions);
}
}
@@ -162,6 +162,8 @@ final class LoanProductsApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
@Schema(example = "1")
public Integer repaymentStartDateType;
// Interest Recalculation
@Schema(example = "false")
@@ -419,6 +421,18 @@ final class LoanProductsApiResourceSwagger {
public String description;
}
static final class GetLoanProductsRepaymentStartDateType {
private GetLoanProductsRepaymentStartDateType() {}
@Schema(example = "1")
public Long id;
@Schema(example = "repaymentStartDateType.disbursementDate")
public String code;
@Schema(example = "Disbursement Date")
public String description;
}
static final class GetLoanProductsInterestRecalculationData {
private GetLoanProductsInterestRecalculationData() {}
@@ -562,6 +576,7 @@ final class LoanProductsApiResourceSwagger {
public GetLoanProductsResponse.GetLoanProductsAccountingRule accountingRule;
@Schema(example = "0")
public Integer principalThresholdForLastInstalment;
public GetLoanProductsResponse.GetLoanProductsRepaymentStartDateType repaymentStartDateType;
}
@Schema(description = "GetLoanProductsTemplateResponse")
@@ -1015,6 +1030,7 @@ final class LoanProductsApiResourceSwagger {
public Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsInterestRecalculationCompoundingType> interestRecalculationCompoundingTypeOptions;
public Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsRescheduleStrategyType> rescheduleStrategyTypeOptions;
public Set<GetLoanProductsResponse.GetLoanProductsInterestRecalculationData.GetLoanProductsInterestRecalculationCompoundingFrequencyType> interestRecalculationFrequencyTypeOptions;
public Set<GetLoanProductsResponse.GetLoanProductsRepaymentStartDateType> repaymentStartDateTypeOptions;
}
@Schema(description = "GetLoanProductsProductIdResponse")
@@ -1034,6 +1050,18 @@ final class LoanProductsApiResourceSwagger {
public String description;
}
static final class GetLoanProductsRepaymentStartDateType {
private GetLoanProductsRepaymentStartDateType() {}
@Schema(example = "1")
public Long id;
@Schema(example = "repaymentStartDateType.disbursementDate")
public String code;
@Schema(example = "Disbursement Date")
public String description;
}
static final class GetLoanProductsPrincipalVariationsForBorrowerCycle {
private GetLoanProductsPrincipalVariationsForBorrowerCycle() {}
@@ -1218,6 +1246,7 @@ final class LoanProductsApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
public GetLoanProductsRepaymentStartDateType repaymentStartDateType;
}
@Schema(description = "PutLoanProductsProductIdRequest")
@@ -1346,6 +1375,8 @@ final class LoanProductsApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
@Schema(example = "1")
public Integer repaymentStartDateType;
// Interest Recalculation
@Schema(example = "false")
@@ -48,6 +48,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.rate.data.RateData;
@@ -123,6 +124,7 @@ public class LoanProductData implements Serializable {
private final Integer minimumDaysBetweenDisbursalAndFirstRepayment;
private final boolean canDefineInstallmentAmount;
private final Integer installmentAmountInMultiplesOf;
private final EnumOptionData repaymentStartDateType;
// charges
private final Collection<ChargeData> charges;
@@ -168,6 +170,7 @@ public class LoanProductData implements Serializable {
private final List<EnumOptionData> interestRecalculationFrequencyTypeOptions;
private final List<FloatingRateData> floatingRateOptions;
private final List<EnumOptionData> repaymentStartDateTypeOptions;
private final Boolean multiDisburseLoan;
private final Integer maxTrancheCount;
@@ -290,6 +293,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null;
final Collection<AdvancedPaymentData> paymentAllocation = null;
final boolean enableAutoRepaymentForDownPayment = false;
final EnumOptionData repaymentStartDateType = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -309,7 +313,7 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation);
paymentAllocation, repaymentStartDateType);
}
@@ -403,6 +407,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null;
final EnumOptionData repaymentStartDateType = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -422,7 +427,7 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation);
paymentAllocation, repaymentStartDateType);
}
@@ -523,6 +528,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null;
final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE);
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -542,7 +548,7 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation);
paymentAllocation, repaymentStartDateType);
}
@@ -637,6 +643,7 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageDownPayment = null;
final boolean enableAutoRepaymentForDownPayment = false;
final Collection<AdvancedPaymentData> paymentAllocation = null;
final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE);
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -656,7 +663,7 @@ public class LoanProductData implements Serializable {
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment,
paymentAllocation);
paymentAllocation, repaymentStartDateType);
}
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
@@ -704,7 +711,7 @@ public class LoanProductData implements Serializable {
final DelinquencyBucketData delinquencyBucket, final Integer dueDaysForRepaymentEvent,
final Integer overDueDaysForRepaymentEvent, final boolean enableDownPayment,
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment,
final Collection<AdvancedPaymentData> paymentAllocation) {
final Collection<AdvancedPaymentData> paymentAllocation, final EnumOptionData repaymentStartDateType) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -826,6 +833,8 @@ public class LoanProductData implements Serializable {
this.disbursedAmountPercentageForDownPayment = disbursedAmountPercentageForDownPayment;
this.paymentAllocation = paymentAllocation;
this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment;
this.repaymentStartDateType = repaymentStartDateType;
this.repaymentStartDateTypeOptions = null;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -842,7 +851,7 @@ public class LoanProductData implements Serializable {
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions, final List<FloatingRateData> floatingRateOptions,
final List<EnumOptionData> interestRecalculationNthDayTypeOptions,
final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions, final boolean isRatesEnabled,
final Collection<DelinquencyBucketData> delinquencyBucketOptions) {
final Collection<DelinquencyBucketData> delinquencyBucketOptions, final List<EnumOptionData> repaymentStartDateTypeOptions) {
this.id = productData.id;
this.name = productData.name;
this.shortName = productData.shortName;
@@ -981,6 +990,8 @@ public class LoanProductData implements Serializable {
this.disbursedAmountPercentageForDownPayment = productData.disbursedAmountPercentageForDownPayment;
this.enableAutoRepaymentForDownPayment = productData.enableAutoRepaymentForDownPayment;
this.paymentAllocation = productData.paymentAllocation;
this.repaymentStartDateType = productData.repaymentStartDateType;
this.repaymentStartDateTypeOptions = repaymentStartDateTypeOptions;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -162,7 +162,7 @@ public final class LoanProductDataValidator {
LoanProductConstants.OVER_APPLIED_NUMBER, LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT,
LoanProductConstants.ENABLE_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT));
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.REPAYMENT_START_DATE_TYPE));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -755,6 +755,13 @@ public final class LoanProductDataValidator {
validateAutoRepaymentForDownPayment(enableDownPayment, baseDataValidator, element);
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.REPAYMENT_START_DATE_TYPE, element)) {
final Integer repaymentStartDateType = this.fromApiJsonHelper
.extractIntegerNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE, element, Locale.getDefault());
baseDataValidator.reset().parameter(LoanProductConstants.REPAYMENT_START_DATE_TYPE).value(repaymentStartDateType).notNull()
.isOneOfTheseValues(1, 2);
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1679,6 +1686,15 @@ public final class LoanProductDataValidator {
validateDownPaymentPercentage(enableDownPayment, baseDataValidator, element);
validateAutoRepaymentForDownPayment(enableDownPayment, baseDataValidator, element);
}
Integer repaymentStartDateType = loanProduct.getRepaymentStartDateType().getValue();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.REPAYMENT_START_DATE_TYPE, element)) {
repaymentStartDateType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE, element,
Locale.getDefault());
}
baseDataValidator.reset().parameter(LoanProductConstants.REPAYMENT_START_DATE_TYPE).value(repaymentStartDateType).notNull()
.isOneOfTheseValues(1, 2);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -57,4 +57,5 @@ public interface LoanDropdownReadPlatformService {
List<EnumOptionData> retrievePreCloseInterestCalculationStrategyOptions();
List<EnumOptionData> retrieveRepaymentStartDateTypeOptions();
}
@@ -32,6 +32,7 @@ import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations
import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyDayOfWeekType;
import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyNthDayType;
import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyType;
import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentStartDateType;
import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType;
import java.util.Arrays;
@@ -52,6 +53,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCa
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.springframework.stereotype.Service;
@Service
@@ -177,4 +179,10 @@ public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlat
return Arrays.asList(preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE),
preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE));
}
@Override
public List<EnumOptionData> retrieveRepaymentStartDateTypeOptions() {
return Arrays.asList(repaymentStartDateType(RepaymentStartDateType.DISBURSEMENT_DATE),
repaymentStartDateType(RepaymentStartDateType.SUBMITTED_ON_DATE));
}
}
@@ -229,7 +229,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lp.disallow_expected_disbursements as disallowExpectedDisbursements, lp.allow_approved_disbursed_amounts_over_applied as allowApprovedDisbursedAmountsOverApplied, lp.over_applied_calculation_type as overAppliedCalculationType, over_applied_number as overAppliedNumber, "
+ "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, "
+ "lp.can_define_fixed_emi_amount as canDefineInstallmentAmount, lp.instalment_amount_in_multiples_of as installmentAmountInMultiplesOf, "
+ "lp.due_days_for_repayment_event as dueDaysForRepaymentEvent, lp.overdue_days_for_repayment_event as overDueDaysForRepaymentEvent, lp.enable_down_payment as enableDownPayment, lp.disbursed_amount_percentage_for_down_payment as disbursedAmountPercentageForDownPayment, lp.enable_auto_repayment_for_down_payment as enableAutoRepaymentForDownPayment, "
+ "lp.due_days_for_repayment_event as dueDaysForRepaymentEvent, lp.overdue_days_for_repayment_event as overDueDaysForRepaymentEvent, lp.enable_down_payment as enableDownPayment, lp.disbursed_amount_percentage_for_down_payment as disbursedAmountPercentageForDownPayment, lp.enable_auto_repayment_for_down_payment as enableAutoRepaymentForDownPayment, lp.repayment_start_date_type_enum as repaymentStartDateType, "
+ "lpr.pre_close_interest_calculation_strategy as preCloseInterestCalculationStrategy, "
+ "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, "
+ "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, "
@@ -364,6 +364,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final boolean enableDownPayment = rs.getBoolean("enableDownPayment");
final BigDecimal disbursedAmountPercentageForDownPayment = rs.getBigDecimal("disbursedAmountPercentageForDownPayment");
final boolean enableAutoRepaymentForDownPayment = rs.getBoolean("enableAutoRepaymentForDownPayment");
final Integer repaymentStartDateTypeId = JdbcSupport.getInteger(rs, "repaymentStartDateType");
final EnumOptionData repaymentStartDateType = LoanEnumerations.repaymentStartDateType(repaymentStartDateTypeId);
String status = "";
if (closeDate != null && closeDate.isBefore(DateUtils.getBusinessLocalDate())) {
@@ -521,7 +523,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates,
isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket,
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, advancedPaymentData);
enableAutoRepaymentForDownPayment, advancedPaymentData, repaymentStartDateType);
}
}