diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc index 048dee05732..e7cfb310f5d 100644 --- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc +++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDataV1.avsc @@ -824,6 +824,14 @@ "boolean" ] }, + { + "default": null, + "name": "repaymentStartDateType", + "type": [ + "null", + "org.apache.fineract.avro.generic.v1.EnumOptionDataV1" + ] + }, { "default": null, "name": "interestRecognitionOnDisbursementDate", diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java index 0b1b6f60900..3bb67aab206 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java @@ -865,6 +865,13 @@ public static String wrongfixedLength(Integer actual, Integer expected) { expectedToStr); } + public static String wrongRepaymentStartDateType(final Integer actual, final Integer expected) { + final String actualToStr = actual.toString(); + final String expectedToStr = expected.toString(); + return String.format("Wrong value in LoanDetails/repaymentStartDateType. %nActual value is: %s %nExpected Value is: %s", + actualToStr, expectedToStr); + } + public static String downpaymentDisabledOnProductErrorCodeMsg() { return "The Loan can not override the downpayment properties because in the Loan Product the downpayment is disabled"; } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index a2594c5fc24..347758af79d 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -3470,6 +3470,20 @@ public void checkLoanDetailsFieldAndValueInt(int fieldValue) throws NoSuchMethod assertThat(fixedLengthactual).as(ErrorMessageHelper.wrongfixedLength(fixedLengthactual, fieldValue)).isEqualTo(fieldValue); } + @Then("LoanDetails has repaymentStartDateType field with value: {string}") + public void checkLoanDetailsRepaymentStartDateTypeField(final String expectedType) { + final PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + final long loanId = loanResponse.getLoanId(); + + final GetLoansLoanIdResponse loanDetails = ok( + () -> fineractClient.loans().retrieveLoan(loanId, Map.of("staffInSelectedOfficeOnly", "false"))); + assert loanDetails.getRepaymentStartDateType() != null; + assert loanDetails.getRepaymentStartDateType().getId() != null; + final Integer actualValue = loanDetails.getRepaymentStartDateType().getId().intValue(); + final Integer expectedValue = RepaymentStartDateType.valueOf(expectedType).getValue(); + assertThat(actualValue).as(ErrorMessageHelper.wrongRepaymentStartDateType(actualValue, expectedValue)).isEqualTo(expectedValue); + } + @Then("Loan has availableDisbursementAmountWithOverApplied field with value: {double}") public void checkLoanDetailsAvailableDisbursementAmountWithOverAppliedField(final double fieldValue) { final PostLoansResponse loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment-Part4.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment-Part4.feature index 0d32cd304fc..9bb988de783 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment-Part4.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment-Part4.feature @@ -315,6 +315,7 @@ Feature: LoanRepayment - Part4 When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | expected disbursement date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | Repayment start date type | | LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR | 31 January 2024 | 10 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | SUBMITTED_ON_DATE | + Then LoanDetails has repaymentStartDateType field with value: "SUBMITTED_ON_DATE" Then Loan Repayment schedule has 4 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 10 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | @@ -356,6 +357,7 @@ Feature: LoanRepayment - Part4 When Admin creates a fully customized loan with the following data: | LoanProduct | submitted on date | expected disbursement date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | Repayment start date type | | LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION_REPAYMENT_START_SUBMITTED | 31 January 2024 | 10 February 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | DISBURSEMENT_DATE | + Then LoanDetails has repaymentStartDateType field with value: "DISBURSEMENT_DATE" Then Loan Repayment schedule has 5 periods, with the following data for periods: | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 10 February 2024 | | 1000.0 | | | 0.0 | | 0.0 | | | | 0.0 | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index f1f026c2a23..25a2e916d55 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -277,6 +277,7 @@ public class LoanAccountData { private Boolean enableDownPayment; private BigDecimal disbursedAmountPercentageForDownPayment; private Boolean enableAutoRepaymentForDownPayment; + private EnumOptionData repaymentStartDateType; private Boolean interestRecognitionOnDisbursementDate; private EnumOptionData loanScheduleType; @@ -444,6 +445,7 @@ public LoanAccountData withProductData(final LoanProductData product, final Inte .setFixedPrincipalPercentagePerInstallment(product.getFixedPrincipalPercentagePerInstallment()) .setDelinquent(CollectionData.template()).setDisallowExpectedDisbursements(product.getDisallowExpectedDisbursements()) .setLoanScheduleType(product.getLoanScheduleType()).setLoanScheduleProcessingType(product.getLoanScheduleProcessingType()) + .setRepaymentStartDateType(product.getRepaymentStartDateType()) .setInterestRecognitionOnDisbursementDate(product.isInterestRecognitionOnDisbursementDate()) .setDaysInYearCustomStrategyOptions(product.getDaysInYearCustomStrategyOptions()) .setDaysInYearCustomStrategy(product.getDaysInYearCustomStrategy()); @@ -482,14 +484,15 @@ public static LoanAccountData basicLoanDetails(final Long id, final String accou final DelinquencyRangeData delinquencyRange, final boolean disallowExpectedDisbursements, final boolean fraud, LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, - final boolean enableInstallmentLevelDelinquency, final EnumOptionData loanScheduleType, - final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, final StringEnumOptionData chargeOffBehaviour, - final boolean isInterestRecognitionOnDisbursementDate, final boolean allowFullTermForTranche, - final StringEnumOptionData daysInYearCustomStrategy, final boolean enableIncomeCapitalization, - final StringEnumOptionData capitalizedIncomeCalculationType, final StringEnumOptionData capitalizedIncomeStrategy, - StringEnumOptionData capitalizedIncomeType, final boolean enableBuyDownFee, - final StringEnumOptionData buyDownFeeCalculationType, final StringEnumOptionData buyDownFeeStrategy, - final StringEnumOptionData buyDownFeeIncomeType, final boolean merchantBuyDownFee) { + final EnumOptionData repaymentStartDateType, final boolean enableInstallmentLevelDelinquency, + final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, + final StringEnumOptionData chargeOffBehaviour, final boolean isInterestRecognitionOnDisbursementDate, + final boolean allowFullTermForTranche, final StringEnumOptionData daysInYearCustomStrategy, + final boolean enableIncomeCapitalization, final StringEnumOptionData capitalizedIncomeCalculationType, + final StringEnumOptionData capitalizedIncomeStrategy, StringEnumOptionData capitalizedIncomeType, + final boolean enableBuyDownFee, final StringEnumOptionData buyDownFeeCalculationType, + final StringEnumOptionData buyDownFeeStrategy, final StringEnumOptionData buyDownFeeIncomeType, + final boolean merchantBuyDownFee) { final CollectionData delinquent = CollectionData.template(); @@ -531,7 +534,7 @@ public static LoanAccountData basicLoanDetails(final Long id, final String accou .setDelinquencyRange(delinquencyRange).setDisallowExpectedDisbursements(disallowExpectedDisbursements).setFraud(fraud) .setLastClosedBusinessDate(lastClosedBusinessDate).setOverpaidOnDate(overpaidOnDate).setChargedOff(chargedOff) .setEnableDownPayment(enableDownPayment).setDisbursedAmountPercentageForDownPayment(disbursedAmountPercentageForDownPayment) - .setEnableAutoRepaymentForDownPayment(enableAutoRepaymentForDownPayment) + .setEnableAutoRepaymentForDownPayment(enableAutoRepaymentForDownPayment).setRepaymentStartDateType(repaymentStartDateType) .setEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency).setLoanScheduleType(loanScheduleType) .setLoanScheduleProcessingType(loanScheduleProcessingType).setFixedLength(fixedLength) .setChargeOffBehaviour(chargeOffBehaviour).setInterestRecognitionOnDisbursementDate(isInterestRecognitionOnDisbursementDate) diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 5e6c28917d9..8a90e639222 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -79,7 +79,6 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes; -import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks; import org.apache.fineract.useradministration.domain.AppUser; @@ -432,9 +431,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { @Column(name = "allow_full_term_for_tranche", nullable = false) private boolean allowFullTermForTranche = false; - @Column(name = "repayment_start_date_type_enum") - private RepaymentStartDateType repaymentStartDateType; - public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final AccountType loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy, @@ -570,7 +566,7 @@ private Loan(final String accountNo, final Client client, final Group group, fin this.expectedFirstRepaymentOnDate = loanApplicationTerms.getRepaymentStartFromDate(); this.interestChargedFromDate = loanApplicationTerms.getInterestChargedFromDate(); this.submittedOnDate = submittedOnDate != null ? submittedOnDate : DateUtils.getBusinessLocalDate(); - this.repaymentStartDateType = loanApplicationTerms.getRepaymentStartDateType(); + this.loanRepaymentScheduleDetail.setRepaymentStartDateType(loanApplicationTerms.getRepaymentStartDateType()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); @@ -1848,8 +1844,4 @@ public boolean hasContractTerminationTransaction() { public long getTermsCount() { return getRepaymentScheduleInstallments().stream().filter(i -> !i.isDownPayment() && !i.isAdditional()).count(); } - - public RepaymentStartDateType getRepaymentStartDateType() { - return this.repaymentStartDateType != null ? this.repaymentStartDateType : this.loanProduct.getRepaymentStartDateType(); - } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 8b6a9b46933..e212da38f0d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -1740,7 +1740,7 @@ public LoanProductRelatedDetail toLoanProductRelatedDetail() { this.chargeOffBehaviour, this.interestRecognitionOnDisbursementDate, this.daysInYearCustomStrategy, this.enableIncomeCapitalization, this.capitalizedIncomeCalculationType, this.capitalizedIncomeStrategy, this.capitalizedIncomeType, this.installmentAmountInMultiplesOf, this.enableBuyDownFee, this.buyDownFeeCalculationType, - this.buyDownFeeStrategy, this.buyDownFeeIncomeType, this.merchantBuyDownFee); + this.buyDownFeeStrategy, this.buyDownFeeIncomeType, this.merchantBuyDownFee, this.repaymentStartDateType); } public ILoanConfigurationDetails toLoanConfigurationDetails() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java index ba82e8adbed..c8291968962 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTermVariationsMapper.java @@ -84,7 +84,7 @@ public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGenerato RecalculationFrequencyType compoundingFrequencyType = null; LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; CalendarHistoryDataWrapper calendarHistoryDataWrapper; - RepaymentStartDateType repaymentStartDateType = loan.getRepaymentStartDateType(); + RepaymentStartDateType repaymentStartDateType = loan.getLoanProductRelatedDetail().getRepaymentStartDateType(); boolean allowCompoundingOnEod = false; if (loan.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) { restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation(); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java index 9f79209630b..b2eab9cac5b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java @@ -228,9 +228,6 @@ public class LoanProduct extends AbstractPersistableCustom { @Column(name = "overdue_days_for_repayment_event") private Integer overDueDaysForRepaymentEvent; - @Column(name = "repayment_start_date_type_enum", nullable = false) - private RepaymentStartDateType repaymentStartDateType; - public void updateLoanProductInRelatedClasses() { if (this.isInterestRecalculationEnabled()) { this.productInterestRecalculationDetails.updateProduct(this); @@ -341,7 +338,7 @@ public LoanProduct(final Fund fund, final String transactionProcessingStrategyCo supportedInterestRefundTypes, chargeOffBehaviour, isInterestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, installmentAmountInMultiplesOf, enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, - merchantBuyDownFee); + merchantBuyDownFee, repaymentStartDateType); this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments, @@ -389,8 +386,6 @@ public LoanProduct(final Fund fund, final String transactionProcessingStrategyCo this.dueDaysForRepaymentEvent = dueDaysForRepaymentEvent; this.overDueDaysForRepaymentEvent = overDueDaysForRepaymentEvent; - this.repaymentStartDateType = repaymentStartDateType; - this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; validateLoanProductPreSave(); } @@ -756,10 +751,6 @@ public boolean isEqualAmortization() { return loanProductRelatedDetail.isEqualAmortization(); } - public RepaymentStartDateType getRepaymentStartDateType() { - return this.repaymentStartDateType == null ? RepaymentStartDateType.INVALID : this.repaymentStartDateType; - } - public void updateEnableInstallmentLevelDelinquency(boolean enableInstallmentLevelDelinquency) { this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 5dc77092720..111219b8df0 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -210,6 +210,9 @@ public class LoanProductRelatedDetail { @Column(name = "installment_amount_in_multiples_of") private Integer installmentAmountInMultiplesOf; + @Column(name = "repayment_start_date_type_enum") + private RepaymentStartDateType repaymentStartDateType; + public static LoanProductRelatedDetail createFrom(final CurrencyData currencyData, final BigDecimal principal, final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, @@ -229,7 +232,8 @@ public static LoanProductRelatedDetail createFrom(final CurrencyData currencyDat final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType, final Integer installmentAmountInMultiplesOf, final boolean enableBuyDownFee, final LoanBuyDownFeeCalculationType buyDownFeeCalculationType, final LoanBuyDownFeeStrategy buyDownFeeStrategy, - final LoanBuyDownFeeIncomeType buyDownFeeIncomeType, final boolean merchantBuyDownFee) { + final LoanBuyDownFeeIncomeType buyDownFeeIncomeType, final boolean merchantBuyDownFee, + final RepaymentStartDateType repaymentStartDateType) { final MonetaryCurrency currency = MonetaryCurrency.fromCurrencyData(currencyData); return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, @@ -241,7 +245,8 @@ public static LoanProductRelatedDetail createFrom(final CurrencyData currencyDat loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes, chargeOffBehaviour, interestRecognitionOnDisbursementDate, daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, installmentAmountInMultiplesOf, - enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee); + enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee, + repaymentStartDateType); } protected LoanProductRelatedDetail() { @@ -267,7 +272,8 @@ public LoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecima final LoanCapitalizedIncomeStrategy capitalizedIncomeStrategy, final LoanCapitalizedIncomeType capitalizedIncomeType, final Integer installmentAmountInMultiplesOf, final boolean enableBuyDownFee, final LoanBuyDownFeeCalculationType buyDownFeeCalculationType, final LoanBuyDownFeeStrategy buyDownFeeStrategy, - final LoanBuyDownFeeIncomeType buyDownFeeIncomeType, final boolean merchantBuyDownFee) { + final LoanBuyDownFeeIncomeType buyDownFeeIncomeType, final boolean merchantBuyDownFee, + final RepaymentStartDateType repaymentStartDateType) { this.currency = currency; this.principal = defaultPrincipal; this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod; @@ -315,6 +321,7 @@ public LoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecima this.buyDownFeeStrategy = buyDownFeeStrategy; this.buyDownFeeIncomeType = buyDownFeeIncomeType; this.merchantBuyDownFee = merchantBuyDownFee; + this.repaymentStartDateType = repaymentStartDateType; } private Integer defaultToNullIfZero(final Integer value) { @@ -371,6 +378,10 @@ public DaysInYearType fetchDaysInYearType() { return DaysInYearType.fromInt(this.daysInYearType); } + public RepaymentStartDateType getRepaymentStartDateType() { + return this.repaymentStartDateType == null ? RepaymentStartDateType.INVALID : this.repaymentStartDateType; + } + public void updateForFloatingInterestRates() { this.nominalInterestRatePerPeriod = null; this.interestPeriodFrequencyType = PeriodFrequencyType.INVALID; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index 4d235532475..08ccaa9f6b9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -1261,6 +1261,8 @@ private GetLoansLoanIdOriginatorData() {} public BigDecimal disbursedAmountPercentageForDownPayment; @Schema(example = "false") public Boolean enableAutoRepaymentForDownPayment; + @Schema(description = "Seed date for first repayment period: disbursement date vs submitted on date", example = "1") + public EnumOptionData repaymentStartDateType; @Schema(example = "CUMULATIVE") public EnumOptionData loanScheduleType; @Schema(example = "HORIZONTAL") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 6d0c67611e2..66751dbeb3c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -271,7 +271,7 @@ private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element); final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element); - RepaymentStartDateType repaymentStartDateType = loanProduct.getRepaymentStartDateType(); + RepaymentStartDateType repaymentStartDateType = loanProduct.getLoanProductRelatedDetail().getRepaymentStartDateType(); if (this.fromApiJsonHelper.parameterExists("repaymentStartDateType", element)) { RepaymentStartDateType paramValue = RepaymentStartDateType .fromInt(this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.REPAYMENT_START_DATE_TYPE, element)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java index 339d7d4725a..b5c15e775d7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java @@ -1515,7 +1515,7 @@ public void validateForModify(final JsonCommand command, final Loan loan) { } } - Integer repaymentStartDateType = loan.getRepaymentStartDateType().getValue(); + Integer repaymentStartDateType = loan.getLoanProductRelatedDetail().getRepaymentStartDateType().getValue(); if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.REPAYMENT_START_DATE_TYPE, element)) { repaymentStartDateType = this.fromApiJsonHelper.extractIntegerNamed(LoanApiConstants.REPAYMENT_START_DATE_TYPE, element, Locale.getDefault()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java index 3133bf0077b..f09d5ec2b4c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductRelatedDetailUpdateUtil.java @@ -40,6 +40,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; +import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.springframework.stereotype.Service; @Service @@ -366,6 +367,14 @@ public Map updateLoanRepaymentSchedule(final LoanProductRelatedD loanRepaymentScheduleDetail.setMerchantBuyDownFee(newValue); } + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE, + loanRepaymentScheduleDetail.getRepaymentStartDateType() == null ? null + : loanRepaymentScheduleDetail.getRepaymentStartDateType().getValue())) { + final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE); + actualChanges.put(LoanProductConstants.REPAYMENT_START_DATE_TYPE, newValue); + loanRepaymentScheduleDetail.setRepaymentStartDateType(RepaymentStartDateType.fromInt(newValue)); + } + return actualChanges; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductUpdateUtil.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductUpdateUtil.java index ac28a3ea2fb..f6a9db917ef 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductUpdateUtil.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanProductUpdateUtil.java @@ -38,7 +38,6 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductBorrowerCycleVariations; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductParamType; -import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.springframework.stereotype.Service; @Service @@ -504,13 +503,6 @@ public Map update(final LoanProduct loanProduct, final JsonComma loanProduct.getLoanProductRelatedDetail().setEnableAutoRepaymentForDownPayment(newValue); } - if (command.isChangeInIntegerParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE, - loanProduct.getRepaymentStartDateType().getValue())) { - final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE); - actualChanges.put(LoanProductConstants.REPAYMENT_START_DATE_TYPE, newValue); - loanProduct.setRepaymentStartDateType(RepaymentStartDateType.fromInt(newValue)); - } - if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, loanProduct.isEnableInstallmentLevelDelinquency())) { final boolean newValue = command diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index b889ad638b7..42cafd0ca91 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -145,6 +145,7 @@ import org.apache.fineract.portfolio.loanproduct.data.LoanProductData; import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; +import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType; import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; @@ -864,7 +865,7 @@ public String loanSchema() { + " topup.topup_amount as topupAmount, l.last_closed_business_date as lastClosedBusinessDate,l.overpaidon_date as overpaidOnDate, " + " l.is_charged_off as isChargedOff, l.charge_off_reason_cv_id as chargeOffReasonId, codec.code_value as chargeOffReason, l.charged_off_on_date as chargedOffOnDate, l.enable_down_payment as enableDownPayment, l.disbursed_amount_percentage_for_down_payment as disbursedAmountPercentageForDownPayment, l.enable_auto_repayment_for_down_payment as enableAutoRepaymentForDownPayment," + " cobu.username as chargedOffByUsername, cobu.firstname as chargedOffByFirstname, cobu.lastname as chargedOffByLastname, l.loan_schedule_type as loanScheduleType, l.loan_schedule_processing_type as loanScheduleProcessingType, " - + " l.charge_off_behaviour as chargeOffBehaviour, l.interest_recognition_on_disbursement_date as interestRecognitionOnDisbursementDate, l.allow_full_term_for_tranche as allowFullTermForTranche " // + + " l.charge_off_behaviour as chargeOffBehaviour, l.interest_recognition_on_disbursement_date as interestRecognitionOnDisbursementDate, l.allow_full_term_for_tranche as allowFullTermForTranche, l.repayment_start_date_type_enum as repaymentStartDateType " // + " from m_loan l" // + " join m_product_loan lp on lp.id = l.product_id" // + " left join m_loan_recalculation_details lir on lir.loan_id = l.id join m_currency rc on rc." @@ -1229,6 +1230,8 @@ public LoanAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") fi final boolean enableDownPayment = rs.getBoolean("enableDownPayment"); final BigDecimal disbursedAmountPercentageForDownPayment = rs.getBigDecimal("disbursedAmountPercentageForDownPayment"); final boolean enableAutoRepaymentForDownPayment = rs.getBoolean("enableAutoRepaymentForDownPayment"); + final EnumOptionData repaymentStartDateType = LoanEnumerations + .repaymentStartDateType(RepaymentStartDateType.fromInt(JdbcSupport.getInteger(rs, "repaymentStartDateType"))); final boolean enableInstallmentLevelDelinquency = rs.getBoolean("enableInstallmentLevelDelinquency"); final String loanScheduleTypeStr = rs.getString("loanScheduleType"); final LoanScheduleType loanScheduleType = LoanScheduleType.valueOf(loanScheduleTypeStr); @@ -1272,11 +1275,12 @@ public LoanAccountData mapRow(final ResultSet rs, @SuppressWarnings("unused") fi canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud, lastClosedBusinessDate, overpaidOnDate, isChargedOff, enableDownPayment, disbursedAmountPercentageForDownPayment, - enableAutoRepaymentForDownPayment, enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), - loanScheduleProcessingType.asEnumOptionData(), fixedLength, chargeOffBehaviour.getValueAsStringEnumOptionData(), - interestRecognitionOnDisbursementDate, allowFullTermForTranche, daysInYearCustomStrategy, enableIncomeCapitalization, - capitalizedIncomeCalculationType, capitalizedIncomeStrategy, capitalizedIncomeType, enableBuyDownFee, - buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, merchantBuyDownFee); + enableAutoRepaymentForDownPayment, repaymentStartDateType, enableInstallmentLevelDelinquency, + loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData(), fixedLength, + chargeOffBehaviour.getValueAsStringEnumOptionData(), interestRecognitionOnDisbursementDate, allowFullTermForTranche, + daysInYearCustomStrategy, enableIncomeCapitalization, capitalizedIncomeCalculationType, capitalizedIncomeStrategy, + capitalizedIncomeType, enableBuyDownFee, buyDownFeeCalculationType, buyDownFeeStrategy, buyDownFeeIncomeType, + merchantBuyDownFee); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 0f06e034316..7f6465a02ab 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -1969,7 +1969,7 @@ public void validateForUpdate(final JsonCommand command, final LoanProduct loanP validateAutoRepaymentForDownPayment(enableDownPayment, baseDataValidator, element); } - Integer repaymentStartDateType = loanProduct.getRepaymentStartDateType().getValue(); + Integer repaymentStartDateType = loanProduct.getLoanProductRelatedDetail().getRepaymentStartDateType().getValue(); if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.REPAYMENT_START_DATE_TYPE, element)) { repaymentStartDateType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.REPAYMENT_START_DATE_TYPE, element, Locale.getDefault()); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index 44b674fcb6e..b751b11ed8e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -55,7 +55,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdTransactions; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse; import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse; -import org.apache.fineract.client.models.LoanProduct; +import org.apache.fineract.client.models.LoanProductRelatedDetail; import org.apache.fineract.client.models.PaymentAllocationOrder; import org.apache.fineract.client.models.PostClientsResponse; import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest; @@ -5171,7 +5171,7 @@ public void uc147c() { .numberOfRepayments(6)// .repaymentEvery(1)// .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())// - .repaymentStartDateType(LoanProduct.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// + .repaymentStartDateType(LoanProductRelatedDetail.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// .enableDownPayment(false)// .allowPartialPeriodInterestCalculation(false)// .enableAutoRepaymentForDownPayment(null)// @@ -5288,7 +5288,7 @@ public void uc147d() { .numberOfRepayments(6)// .repaymentEvery(1)// .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())// - .repaymentStartDateType(LoanProduct.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// + .repaymentStartDateType(LoanProductRelatedDetail.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// .enableDownPayment(false)// .allowPartialPeriodInterestCalculation(null)// .enableAutoRepaymentForDownPayment(null)// @@ -6052,7 +6052,7 @@ public void uc156() { .repaymentFrequencyType(2L)// .chargeOffBehaviour("ZERO_INTEREST")// .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())// - .repaymentStartDateType(LoanProduct.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// + .repaymentStartDateType(LoanProductRelatedDetail.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// .enableDownPayment(false)// .enableAccrualActivityPosting(true)// .allowPartialPeriodInterestCalculation(null)// @@ -6194,7 +6194,7 @@ public void uc157() { .repaymentFrequencyType(2L)// .chargeOffBehaviour("ZERO_INTEREST")// .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())// - .repaymentStartDateType(LoanProduct.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// + .repaymentStartDateType(LoanProductRelatedDetail.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// .enableDownPayment(false)// .enableAccrualActivityPosting(true)// .allowPartialPeriodInterestCalculation(null)//