FINERACT-1761-Repayment-due-event-configuration-at-product-level

This commit is contained in:
Ruchi Dhamankar
2023-04-27 08:34:37 +02:00
committed by Arnold Gálovics
parent 5fe708a90f
commit 57a1cb2f8c
16 changed files with 393 additions and 11 deletions
@@ -42,6 +42,11 @@ public class CheckLoanRepaymentDueBusinessStep implements LoanCOBBusinessStep {
public Loan execute(Loan loan) {
log.debug("start processing loan repayment due business step loan for loan with id [{}]", loan.getId());
Long numberOfDaysBeforeDueDateToRaiseEvent = configurationDomainService.retrieveRepaymentDueDays();
if (loan.getLoanProduct().getDueDaysForRepaymentEvent() != null) {
if (loan.getLoanProduct().getDueDaysForRepaymentEvent() > 0) {
numberOfDaysBeforeDueDateToRaiseEvent = loan.getLoanProduct().getDueDaysForRepaymentEvent().longValue();
}
}
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment repaymentSchedule : loanRepaymentScheduleInstallments) {
@@ -42,6 +42,11 @@ public class CheckLoanRepaymentOverdueBusinessStep implements LoanCOBBusinessSte
public Loan execute(Loan loan) {
log.debug("start processing loan repayment overdue business step for loan with Id [{}]", loan.getId());
Long numberOfDaysAfterDueDateToRaiseEvent = configurationDomainService.retrieveRepaymentOverdueDays();
if (loan.getLoanProduct().getOverDueDaysForRepaymentEvent() != null) {
if (loan.getLoanProduct().getOverDueDaysForRepaymentEvent() > 0) {
numberOfDaysAfterDueDateToRaiseEvent = loan.getLoanProduct().getOverDueDaysForRepaymentEvent().longValue();
}
}
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
final List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment repaymentSchedule : loanRepaymentScheduleInstallments) {
@@ -143,4 +143,8 @@ public interface LoanProductConstants {
String OVER_APPLIED_NUMBER = "overAppliedNumber";
String DELINQUENCY_BUCKET_PARAM_NAME = "delinquencyBucketId";
// repayment events related
String DUE_DAYS_FOR_REPAYMENT_EVENT = "dueDaysForRepaymentEvent";
String OVER_DUE_DAYS_FOR_REPAYMENT_EVENT = "overDueDaysForRepaymentEvent";
}
@@ -106,7 +106,8 @@ public class LoanProductsApiResource {
"isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate",
"defaultDifferentialLendingRate", "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed",
LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME,
LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName));
LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT,
LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT));
private static final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions"));
@@ -141,7 +142,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\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\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"
@@ -149,6 +149,10 @@ final class LoanProductsApiResourceSwagger {
public Boolean holdGuaranteeFunds;
@Schema(example = "1")
public Long delinquencyBucketId;
@Schema(example = "3")
public Integer dueDaysForRepaymentEvent;
@Schema(example = "3")
public Integer overDueDaysForRepaymentEvent;
// Interest Recalculation
@Schema(example = "false")
@@ -1191,6 +1195,10 @@ final class LoanProductsApiResourceSwagger {
public GetDelinquencyBucketsResponse delinquencyBucket;
@Schema(example = "true")
public Boolean disallowExpectedDisbursements;
@Schema(example = "3")
public Integer dueDaysForRepaymentEvent;
@Schema(example = "3")
public Integer overDueDaysForRepaymentEvent;
}
@Schema(description = "PutLoanProductsProductIdRequest")
@@ -1308,6 +1316,10 @@ final class LoanProductsApiResourceSwagger {
public Boolean holdGuaranteeFunds;
@Schema(example = "1")
public Long delinquencyBucketId;
@Schema(example = "3")
public Integer dueDaysForRepaymentEvent;
@Schema(example = "3")
public Integer overDueDaysForRepaymentEvent;
// Interest Recalculation
@Schema(example = "false")
@@ -190,6 +190,9 @@ public class LoanProductData implements Serializable {
private final Collection<DelinquencyBucketData> delinquencyBucketOptions;
private final DelinquencyBucketData delinquencyBucket;
private final Integer dueDaysForRepaymentEvent;
private final Integer overDueDaysForRepaymentEvent;
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -276,6 +279,8 @@ public class LoanProductData implements Serializable {
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
final Integer dueDaysForRepaymentEvent = null;
final Integer overDueDaysForRepaymentEvent = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -293,7 +298,8 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent);
}
@@ -381,6 +387,8 @@ public class LoanProductData implements Serializable {
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
final Integer dueDaysForRepaymentEvent = null;
final Integer overDueDaysForRepaymentEvent = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -398,7 +406,8 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent);
}
@@ -493,6 +502,8 @@ public class LoanProductData implements Serializable {
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
final Integer dueDaysForRepaymentEvent = null;
final Integer overDueDaysForRepaymentEvent = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -510,7 +521,8 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent);
}
@@ -599,6 +611,8 @@ public class LoanProductData implements Serializable {
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
final Integer dueDaysForRepaymentEvent = null;
final Integer overDueDaysForRepaymentEvent = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -616,7 +630,8 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent);
}
@@ -662,7 +677,8 @@ public class LoanProductData implements Serializable {
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization,
Collection<RateData> rateOptions, Collection<RateData> rates, final boolean isRatesEnabled,
final BigDecimal fixedPrincipalPercentagePerInstallment, final Collection<DelinquencyBucketData> delinquencyBucketOptions,
final DelinquencyBucketData delinquencyBucket) {
final DelinquencyBucketData delinquencyBucket, final Integer dueDaysForRepaymentEvent,
final Integer overDueDaysForRepaymentEvent) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -778,6 +794,8 @@ public class LoanProductData implements Serializable {
this.isEqualAmortization = isEqualAmortization;
this.delinquencyBucketOptions = delinquencyBucketOptions;
this.delinquencyBucket = delinquencyBucket;
this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent;
this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -927,6 +945,8 @@ public class LoanProductData implements Serializable {
this.isRatesEnabled = isRatesEnabled;
this.delinquencyBucketOptions = delinquencyBucketOptions;
this.delinquencyBucket = productData.delinquencyBucket;
this.dueDaysForRepaymentEvent = productData.dueDaysForRepaymentEvent;
this.overDueDaysForRepaymentEvent = productData.overDueDaysForRepaymentEvent;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -202,6 +202,12 @@ public class LoanProduct extends AbstractPersistableCustom {
@JoinColumn(name = "delinquency_bucket_id")
private DelinquencyBucket delinquencyBucket;
@Column(name = "due_days_for_repayment_event")
private Integer dueDaysForRepaymentEvent;
@Column(name = "overdue_days_for_repayment_event")
private Integer overDueDaysForRepaymentEvent;
public static LoanProduct assembleFromJson(final Fund fund, final String loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
final List<Rate> productRates) {
@@ -373,6 +379,10 @@ public class LoanProduct extends AbstractPersistableCustom {
final Integer overAppliedNumber = command.integerValueOfParameterNamed(LoanProductConstants.OVER_APPLIED_NUMBER);
final Integer dueDaysForRepaymentEvent = command.integerValueOfParameterNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT);
final Integer overDueDaysForRepaymentEvent = command
.integerValueOfParameterNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT);
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery,
@@ -388,7 +398,8 @@ public class LoanProduct extends AbstractPersistableCustom {
defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed,
minimumGapBetweenInstallments, maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup,
isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment, disallowExpectedDisbursements,
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber);
allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent,
overDueDaysForRepaymentEvent);
}
@@ -599,7 +610,7 @@ public class LoanProduct extends AbstractPersistableCustom {
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization,
final List<Rate> rates, final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean disallowExpectedDisbursements,
final boolean allowApprovedDisbursedAmountsOverApplied, final String overAppliedCalculationType,
final Integer overAppliedNumber) {
final Integer overAppliedNumber, final Integer dueDaysForRepaymentEvent, final Integer overDueDaysForRepaymentEvent) {
this.fund = fund;
this.transactionProcessingStrategyCode = transactionProcessingStrategyCode;
this.name = name.trim();
@@ -681,6 +692,10 @@ public class LoanProduct extends AbstractPersistableCustom {
if (rates != null) {
this.rates = rates;
}
this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent;
this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent;
validateLoanProductPreSave();
}
@@ -1184,6 +1199,21 @@ public class LoanProduct extends AbstractPersistableCustom {
this.overAppliedNumber = newValue;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, this.dueDaysForRepaymentEvent)) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT);
actualChanges.put(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, newValue);
actualChanges.put("locale", localeAsInput);
this.dueDaysForRepaymentEvent = newValue;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT,
this.overDueDaysForRepaymentEvent)) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT);
actualChanges.put(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, newValue);
actualChanges.put("locale", localeAsInput);
this.overDueDaysForRepaymentEvent = newValue;
}
return actualChanges;
}
@@ -1571,4 +1601,12 @@ public class LoanProduct extends AbstractPersistableCustom {
this.delinquencyBucket = delinquencyBucket;
}
public Integer getDueDaysForRepaymentEvent() {
return this.dueDaysForRepaymentEvent;
}
public Integer getOverDueDaysForRepaymentEvent() {
return this.overDueDaysForRepaymentEvent;
}
}
@@ -158,7 +158,8 @@ public final class LoanProductDataValidator {
LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME,
LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS,
LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE,
LoanProductConstants.OVER_APPLIED_NUMBER, LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME));
LoanProductConstants.OVER_APPLIED_NUMBER, LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -733,6 +734,16 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LoanProductConstants.CAN_USE_FOR_TOPUP).value(canUseForTopup).validateForBooleanValue();
}
final Integer dueDaysForRepaymentEvent = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, element);
baseDataValidator.reset().parameter(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT).value(dueDaysForRepaymentEvent)
.integerZeroOrGreater();
final Integer overDueDaysForRepaymentEvent = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, element);
baseDataValidator.reset().parameter(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT).value(overDueDaysForRepaymentEvent)
.integerZeroOrGreater();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1600,6 +1611,16 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(LoanProductConstants.CAN_USE_FOR_TOPUP).value(canUseForTopup).validateForBooleanValue();
}
final Integer dueDaysForRepaymentEvent = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT, element);
baseDataValidator.reset().parameter(LoanProductConstants.DUE_DAYS_FOR_REPAYMENT_EVENT).value(dueDaysForRepaymentEvent)
.integerZeroOrGreater();
final Integer overDueDaysForRepaymentEvent = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT, element);
baseDataValidator.reset().parameter(LoanProductConstants.OVER_DUE_DAYS_FOR_REPAYMENT_EVENT).value(overDueDaysForRepaymentEvent)
.integerZeroOrGreater();
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -216,6 +216,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,"
+ "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, "
@@ -345,6 +346,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final boolean useBorrowerCycle = rs.getBoolean("useBorrowerCycle");
final LocalDate startDate = JdbcSupport.getLocalDate(rs, "startDate");
final LocalDate closeDate = JdbcSupport.getLocalDate(rs, "closeDate");
final Integer dueDaysForRepaymentEvent = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "dueDaysForRepaymentEvent");
final Integer overDueDaysForRepaymentEvent = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "overDueDaysForRepaymentEvent");
String status = "";
if (closeDate != null && closeDate.isBefore(DateUtils.getBusinessLocalDate())) {
status = "loanProduct.inActive";
@@ -499,7 +502,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates,
isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket,
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent);
}
}
@@ -123,4 +123,5 @@
<include file="parts/0101_update_transaction_summary_table_report.xml" relativeToChangelogFile="true" />
<include file="parts/0102_add_external_event_for_loan_reschedule.xml" relativeToChangelogFile="true" />
<include file="parts/0103_modify_parameter_json_column_custom_job_parameters.xml" relativeToChangelogFile="true" />
<include file="parts/0104_loan_product_add_repayment_overdue_days_config.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="1">
<addColumn tableName="m_product_loan">
<column name="due_days_for_repayment_event" type="INT">
<constraints nullable="true"/>
</column>
</addColumn>
<addColumn tableName="m_product_loan">
<column name="overdue_days_for_repayment_event" type="INT">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>
@@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.repayment.L
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -75,10 +76,13 @@ public class CheckLoanRepaymentDueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentDueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().plusDays(1);
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
@@ -97,10 +101,13 @@ public class CheckLoanRepaymentDueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentDueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDateAfter5Days = DateUtils.getBusinessLocalDate().plusDays(5);
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays
.asList(new LoanRepaymentScheduleInstallment(loanForProcessing, 1, LocalDate.now(ZoneId.systemDefault()),
loanInstallmentRepaymentDueDateAfter5Days, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0)));
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
@@ -110,4 +117,32 @@ public class CheckLoanRepaymentDueBusinessStepTest {
assertEquals(processedLoan, loanForProcessing);
}
@Test
public void givenLoanWithInstallmentDueAfterConfiguredDaysInLoanProductWhenStepExecutionThenBusinessEventIsRaised() {
ArgumentCaptor<LoanRepaymentDueBusinessEvent> loanRepaymentDueEvent = ArgumentCaptor.forClass(LoanRepaymentDueBusinessEvent.class);
// given
// Global config settings
when(configurationDomainService.retrieveRepaymentDueDays()).thenReturn(2L);
LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().plusDays(1);
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
// Loan Product setting overrides global settings
when(loanProduct.getDueDaysForRepaymentEvent()).thenReturn(1);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
Loan processedLoan = underTest.execute(loanForProcessing);
// then
verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(loanRepaymentDueEvent.capture());
LoanRepaymentScheduleInstallment loanPayloadForEvent = loanRepaymentDueEvent.getValue().get();
assertEquals(repaymentInstallment, loanPayloadForEvent);
assertEquals(processedLoan, loanForProcessing);
}
}
@@ -42,6 +42,7 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.repayment.L
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -76,10 +77,13 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentOverdueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().minusDays(1);
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
@@ -97,10 +101,13 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentOverdueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDateBefore5Days = DateUtils.getBusinessLocalDate().minusDays(5);
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays
.asList(new LoanRepaymentScheduleInstallment(loanForProcessing, 1, LocalDate.now(ZoneId.systemDefault()),
loanInstallmentRepaymentDueDateBefore5Days, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0)));
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
Loan processedLoan = underTest.execute(loanForProcessing);
@@ -116,6 +123,7 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
when(configurationDomainService.retrieveRepaymentOverdueDays()).thenReturn(1L);
LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().minusDays(1);
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
LoanRepaymentScheduleInstallment repaymentInstallmentPaidOff = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
@@ -123,6 +131,8 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
repaymentInstallmentPaidOff.updateObligationMet(true);
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallmentPaidOff);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(null);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
@@ -132,4 +142,32 @@ public class CheckLoanRepaymentOverdueBusinessStepTest {
assertEquals(processedLoan, loanForProcessing);
}
@Test
public void givenLoanWithInstallmentOverdueAfterConfiguredDaysInLoanProductWhenStepExecutionThenBusinessEventIsRaised() {
ArgumentCaptor<LoanRepaymentOverdueBusinessEvent> loanRepaymentDueBusinessEventArgumentCaptor = ArgumentCaptor
.forClass(LoanRepaymentOverdueBusinessEvent.class);
// given
// global configuration
when(configurationDomainService.retrieveRepaymentOverdueDays()).thenReturn(2L);
LocalDate loanInstallmentRepaymentDueDate = DateUtils.getBusinessLocalDate().minusDays(1);
Loan loanForProcessing = Mockito.mock(Loan.class);
LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
LoanRepaymentScheduleInstallment repaymentInstallment = new LoanRepaymentScheduleInstallment(loanForProcessing, 1,
LocalDate.now(ZoneId.systemDefault()), loanInstallmentRepaymentDueDate, BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0),
BigDecimal.valueOf(0.0), BigDecimal.valueOf(0.0), false, new HashSet<>(), BigDecimal.valueOf(0.0));
List<LoanRepaymentScheduleInstallment> loanRepaymentScheduleInstallments = Arrays.asList(repaymentInstallment);
when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
// product configuration overrides global configuration
when(loanProduct.getOverDueDaysForRepaymentEvent()).thenReturn(1);
when(loanForProcessing.getRepaymentScheduleInstallments()).thenReturn(loanRepaymentScheduleInstallments);
// when
Loan processedLoan = underTest.execute(loanForProcessing);
// then
verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(loanRepaymentDueBusinessEventArgumentCaptor.capture());
LoanRepaymentScheduleInstallment loanPayloadForEvent = loanRepaymentDueBusinessEventArgumentCaptor.getValue().get();
assertEquals(repaymentInstallment, loanPayloadForEvent);
assertEquals(processedLoan, loanForProcessing);
}
}
@@ -0,0 +1,135 @@
/**
* 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.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.HashMap;
import java.util.UUID;
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class LoanProductWithRepaymentDueEventConfigurationTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private ClientHelper clientHelper;
private LoanTransactionHelper loanTransactionHelper;
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
}
@Test
public void loanProductCreationWithDueDaysConfigurationForRepaymentEventTest() {
// Loan ExternalId
String loanExternalIdStr = UUID.randomUUID().toString();
// Delinquency Bucket
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
delinquencyBucketId);
// event days configuration
Integer dueDaysForRepaymentEvent = 1;
Integer overDueDaysForRepaymentEvent = 2;
// Client and Loan account creation
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
Integer loanProductId = createLoanProductWithDueDaysForRepaymentEvent(loanTransactionHelper, delinquencyBucketId,
dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent);
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId);
assertNotNull(getLoanProductsProductResponse);
assertNotNull(getLoanProductsProductResponse.getDueDaysForRepaymentEvent());
assertNotNull(getLoanProductsProductResponse.getOverDueDaysForRepaymentEvent());
assertEquals(getLoanProductsProductResponse.getDueDaysForRepaymentEvent(), dueDaysForRepaymentEvent);
assertEquals(getLoanProductsProductResponse.getOverDueDaysForRepaymentEvent(), overDueDaysForRepaymentEvent);
}
@Test
public void loanProductUpdateWithDueDaysConfigurationForRepaymentEventTest() {
// Loan ExternalId
String loanExternalIdStr = UUID.randomUUID().toString();
// Delinquency Bucket
final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec);
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
delinquencyBucketId);
// Client and Loan account creation
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
delinquencyBucketId);
assertNotNull(getLoanProductsProductResponse);
// Modify Loan Product
PutLoanProductsProductIdResponse loanProductModifyResponse = updateLoanProduct(loanTransactionHelper,
getLoanProductsProductResponse.getId());
assertNotNull(loanProductModifyResponse);
}
private PutLoanProductsProductIdResponse updateLoanProduct(LoanTransactionHelper loanTransactionHelper, Long id) {
// event days configuration
Integer dueDaysForRepaymentEvent = 1;
Integer overDueDaysForRepaymentEvent = 2;
final PutLoanProductsProductIdRequest requestModifyLoan = new PutLoanProductsProductIdRequest()
.dueDaysForRepaymentEvent(dueDaysForRepaymentEvent).overDueDaysForRepaymentEvent(overDueDaysForRepaymentEvent).locale("en");
return loanTransactionHelper.updateLoanProduct(id, requestModifyLoan);
}
private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
final Integer delinquencyBucketId) {
final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucketId);
final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
return loanTransactionHelper.getLoanProduct(loanProductId);
}
private Integer createLoanProductWithDueDaysForRepaymentEvent(final LoanTransactionHelper loanTransactionHelper,
final Integer delinquencyBucketId, Integer dueDaysForRepaymentEvent, Integer overDueDaysForRepaymentEvent) {
final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().withDueDaysForRepaymentEvent(dueDaysForRepaymentEvent)
.withOverDueDaysForRepaymentEvent(overDueDaysForRepaymentEvent).build(null, delinquencyBucketId);
final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
return loanProductId;
}
}
@@ -138,6 +138,8 @@ public class LoanProductTestBuilder {
private String installmentAmountInMultiplesOf;
private boolean canDefineInstallmentAmount;
private Integer delinquencyBucketId;
private Integer dueDaysForRepaymentEvent = null;
private Integer overDueDaysForRepaymentEvent = null;
public String build(final String chargeId) {
final HashMap<String, Object> map = build(chargeId, null);
@@ -267,6 +269,13 @@ public class LoanProductTestBuilder {
map.put("penaltyToIncomeAccountMappings", this.penaltyToIncomeAccountMappings);
}
if (this.dueDaysForRepaymentEvent != null) {
map.put("dueDaysForRepaymentEvent", this.dueDaysForRepaymentEvent);
}
if (this.overDueDaysForRepaymentEvent != null) {
map.put("overDueDaysForRepaymentEvent", this.overDueDaysForRepaymentEvent);
}
return map;
}
@@ -656,4 +665,15 @@ public class LoanProductTestBuilder {
this.feeAndPenaltyAssetAccount = account;
return this;
}
public LoanProductTestBuilder withDueDaysForRepaymentEvent(final Integer dueDaysForRepaymentEvent) {
this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent;
return this;
}
public LoanProductTestBuilder withOverDueDaysForRepaymentEvent(final Integer overDueDaysForRepaymentEvent) {
this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent;
return this;
}
}
@@ -70,6 +70,8 @@ import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
import org.apache.fineract.client.models.PutChargeTransactionChangesRequest;
import org.apache.fineract.client.models.PutChargeTransactionChangesResponse;
import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
import org.apache.fineract.client.models.PutLoanProductsProductIdResponse;
import org.apache.fineract.client.models.PutLoansLoanIdChargesChargeIdRequest;
import org.apache.fineract.client.models.PutLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.PutLoansLoanIdRequest;
@@ -1844,4 +1846,8 @@ public class LoanTransactionHelper extends IntegrationTest {
final String get = Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_URL, null);
return new Gson().fromJson(get, new TypeToken<ArrayList<Integer>>() {}.getType());
}
public PutLoanProductsProductIdResponse updateLoanProduct(Long id, PutLoanProductsProductIdRequest requestModifyLoan) {
return ok(fineract().loanProducts.updateLoanProduct(id, requestModifyLoan));
}
}