diff --git a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionAccrualActivityPostBusinessEvent.java b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionAccrualActivityPostBusinessEvent.java new file mode 100644 index 000000000..732ce7477 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionAccrualActivityPostBusinessEvent.java @@ -0,0 +1,35 @@ +/** + * 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.infrastructure.event.business.domain.loan.transaction; + +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; + +public class LoanTransactionAccrualActivityPostBusinessEvent extends LoanTransactionBusinessEvent { + + private static final String TYPE = "LoanTransactionAccrualActivityPostBusinessEvent"; + + public LoanTransactionAccrualActivityPostBusinessEvent(LoanTransaction value) { + super(value); + } + + @Override + public String getType() { + return TYPE; + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionAccrualActivityPreBusinessEvent.java b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionAccrualActivityPreBusinessEvent.java new file mode 100644 index 000000000..3108396c5 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionAccrualActivityPreBusinessEvent.java @@ -0,0 +1,36 @@ +/** + * 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.infrastructure.event.business.domain.loan.transaction; + +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; + +public class LoanTransactionAccrualActivityPreBusinessEvent extends LoanBusinessEvent { + + private static final String TYPE = "LoanTransactionAccrualActivityPreBusinessEvent"; + + public LoanTransactionAccrualActivityPreBusinessEvent(Loan value) { + super(value); + } + + @Override + public String getType() { + return TYPE; + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java index 9d5c292b7..78497b620 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java @@ -59,6 +59,7 @@ public class LoanTransactionEnumData { private final boolean downPayment; private final boolean reAge; private final boolean reAmortize; + private final boolean accrualActivity; public LoanTransactionEnumData(final Long id, final String code, final String value) { this.id = id; @@ -90,6 +91,7 @@ public class LoanTransactionEnumData { this.chargeoff = Long.valueOf(27).equals(this.id); this.downPayment = Long.valueOf(28).equals(this.id); this.interestPaymentWaiver = Long.valueOf(31).equals(this.id); + this.accrualActivity = Long.valueOf(32).equals(this.id); this.reAge = Long.valueOf(LoanTransactionType.REAGE.getValue()).equals(this.id); this.reAmortize = Long.valueOf(LoanTransactionType.REAMORTIZE.getValue()).equals(this.id); } 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 cfb680585..657893cff 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 @@ -605,6 +605,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { this.loanInterestRecalculationDetails.updateLoan(this); } this.enableInstallmentLevelDelinquency = enableInstallmentLevelDelinquency; + this.getLoanProductRelatedDetail() + .setEnableAccrualActivityPosting(loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()); } public Integer getNumberOfRepayments() { @@ -4951,7 +4953,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { calendarHistoryDataWrapper, scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), holidayDetailDTO, allowCompoundingOnEod, scheduleGeneratorDTO.isFirstRepaymentDateAllowedOnHoliday(), scheduleGeneratorDTO.isInterestToBeRecoveredFirstWhenGreaterThanEMI(), this.fixedPrincipalPercentagePerInstallment, - scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, getSubmittedOnDate()); + scheduleGeneratorDTO.isPrincipalCompoundingDisabledForOverdueLoans(), repaymentStartDateType, getSubmittedOnDate(), + loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()); } public BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate, @@ -5199,7 +5202,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst, holidayDetailDTO, allowCompoundingOnEod, false, - false, this.fixedPrincipalPercentagePerInstallment, false, repaymentStartDateType, getSubmittedOnDate()); + false, this.fixedPrincipalPercentagePerInstallment, false, repaymentStartDateType, getSubmittedOnDate(), + loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()); } /** diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 973c8245f..b2f20684a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -711,6 +711,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom LoanTransactionType.REAGE; case 30 -> LoanTransactionType.REAMORTIZE; case 31 -> LoanTransactionType.INTEREST_PAYMENT_WAIVER; + case 32 -> LoanTransactionType.ACCRUAL_ACTIVITY; default -> LoanTransactionType.INVALID; }; } @@ -215,4 +217,8 @@ public enum LoanTransactionType { public boolean isDownPayment() { return this.equals(LoanTransactionType.DOWN_PAYMENT); } + + public boolean isAccrualActivity() { + return this.equals(LoanTransactionType.ACCRUAL_ACTIVITY); + } } 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 41f8a95d2..54cc4e570 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 @@ -224,6 +224,7 @@ public final class LoanApplicationTerms { private Money disbursedPrincipal; private final LoanScheduleType loanScheduleType; private final LoanScheduleProcessingType loanScheduleProcessingType; + private final boolean enableAccrualActivityPosting; public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, @@ -251,7 +252,8 @@ public final class LoanApplicationTerms { final Boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final Boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, + final boolean enableAccrualActivityPosting) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; @@ -270,7 +272,7 @@ public final class LoanApplicationTerms { isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, enableDownPayment, disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType, - fixedLength); + fixedLength, enableAccrualActivityPosting); } @@ -291,7 +293,8 @@ public final class LoanApplicationTerms { final boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod, final boolean isFirstRepaymentDateAllowedOnHoliday, final boolean isInterestToBeRecoveredFirstWhenGreaterThanEMI, final BigDecimal fixedPrincipalPercentagePerInstallment, final boolean isPrincipalCompoundingDisabledForOverdueLoans, - final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate) { + final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, + final boolean enableAccrualActivityPosting) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -343,7 +346,7 @@ public final class LoanApplicationTerms { isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, loanScheduleType, loanScheduleProcessingType, - fixedLength); + fixedLength, enableAccrualActivityPosting); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -372,7 +375,7 @@ public final class LoanApplicationTerms { final boolean isPrincipalCompoundingDisabledForOverdueLoans, final boolean isDownPaymentEnabled, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean isAutoRepaymentForDownPaymentEnabled, final RepaymentStartDateType repaymentStartDateType, final LocalDate submittedOnDate, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, boolean enableAccrualActivityPosting) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; @@ -426,6 +429,7 @@ public final class LoanApplicationTerms { this.numberOfDays = numberOfDays; this.loanCalendar = loanCalendar; + this.enableAccrualActivityPosting = enableAccrualActivityPosting; this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount); this.variationsDataWrapper = new LoanTermVariationsDataWrapper(loanTermVariations); this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments(); @@ -1333,7 +1337,7 @@ public final class LoanApplicationTerms { this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.daysInYearType.getValue(), this.interestRecalculationEnabled, this.isEqualAmortization, this.isDownPaymentEnabled, this.disbursedAmountPercentageForDownPayment, this.isAutoRepaymentForDownPaymentEnabled, this.loanScheduleType, - this.loanScheduleProcessingType, this.fixedLength); + this.loanScheduleProcessingType, this.fixedLength, this.enableAccrualActivityPosting); } public Integer getLoanTermFrequency() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java index bba061edc..1958b72cb 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -30,6 +30,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.springframework.transaction.annotation.Transactional; @@ -52,6 +53,9 @@ public interface LoanWritePlatformService { CommandProcessingResult makeLoanRepaymentWithChargeRefundChargeType(LoanTransactionType repaymentTransactionType, Long loanId, JsonCommand command, boolean isRecoveryRepayment, String chargeRefundChargeType); + @Transactional + Loan makeAccrualActivityTransaction(Loan loan, LoanRepaymentScheduleInstallment installment, LocalDate currentDate); + @Transactional CommandProcessingResult makeInterestPaymentWaiver(JsonCommand command); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java index d18c5da69..29b6cbcf9 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java @@ -163,4 +163,7 @@ public interface LoanProductConstants { String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy"; String FIXED_LENGTH = "fixedLength"; + + String ENABLE_ACCRUAL_ACTIVITY_POSTING = "enableAccrualActivityPosting"; + } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java index c96e00148..0682e866b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java @@ -169,6 +169,8 @@ final class LoanProductsApiResourceSwagger { public Boolean enableAutoRepaymentForDownPayment; @Schema(example = "1") public Integer repaymentStartDateType; + @Schema(example = "false") + public Boolean enableAccrualActivityPosting; // Interest Recalculation @Schema(example = "false") @@ -1277,6 +1279,8 @@ final class LoanProductsApiResourceSwagger { public EnumOptionData loanScheduleType; @Schema(example = "HORIZONTAL") public EnumOptionData loanScheduleProcessingType; + @Schema(example = "false") + public Boolean enableAccrualActivityPosting; } @Schema(description = "PutLoanProductsProductIdRequest") @@ -1487,6 +1491,8 @@ final class LoanProductsApiResourceSwagger { public List paymentChannelToFundSourceMappings; public List feeToIncomeAccountMappings; public List penaltyToIncomeAccountMappings; + @Schema(example = "false") + public Boolean enableAccrualActivityPosting; // Multi Disburse @Schema(example = "true") diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java index 7403e82e4..f455b26da 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java @@ -148,6 +148,7 @@ public class LoanProductData implements Serializable { private Collection paymentChannelToFundSourceMappings; private Collection feeToIncomeAccountMappings; private Collection penaltyToIncomeAccountMappings; + private final boolean enableAccrualActivityPosting; // rates private final boolean isRatesEnabled; @@ -323,6 +324,7 @@ public class LoanProductData implements Serializable { final EnumOptionData loanScheduleProcessingType = null; final EnumOptionData loanScheduleTypeOptions = null; final EnumOptionData loanScheduleProcessingTypeOptions = null; + final boolean enableAccrualActivityPosting = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -343,7 +345,7 @@ public class LoanProductData implements Serializable { fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, - loanScheduleProcessingType, fixedLength); + loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting); } @@ -443,6 +445,7 @@ public class LoanProductData implements Serializable { final boolean enableInstallmentLevelDelinquency = false; final EnumOptionData loanScheduleType = null; final EnumOptionData loanScheduleProcessingType = null; + final boolean enableAccrualActivityPosting = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -463,7 +466,7 @@ public class LoanProductData implements Serializable { fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, - loanScheduleProcessingType, fixedLength); + loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting); } @@ -570,6 +573,7 @@ public class LoanProductData implements Serializable { final boolean enableInstallmentLevelDelinquency = false; final EnumOptionData loanScheduleType = LoanScheduleType.CUMULATIVE.asEnumOptionData(); final EnumOptionData loanScheduleProcessingType = LoanScheduleProcessingType.HORIZONTAL.asEnumOptionData(); + final boolean enableAccrualActivityPosting = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -590,7 +594,7 @@ public class LoanProductData implements Serializable { fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocation, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, - loanScheduleProcessingType, fixedLength); + loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting); } @@ -691,6 +695,7 @@ public class LoanProductData implements Serializable { final boolean enableInstallmentLevelDelinquency = false; final EnumOptionData loanScheduleType = null; final EnumOptionData loanScheduleProcessingType = null; + final boolean enableAccrualActivityPosting = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -711,7 +716,7 @@ public class LoanProductData implements Serializable { fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, paymentAllocation, creditAllocationData, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, - loanScheduleProcessingType, fixedLength); + loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting); } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings, @@ -761,7 +766,8 @@ public class LoanProductData implements Serializable { final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final Collection paymentAllocation, final Collection creditAllocation, final EnumOptionData repaymentStartDateType, final boolean enableInstallmentLevelDelinquency, - final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType, final Integer fixedLength) { + final EnumOptionData loanScheduleType, final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, + final boolean enableAccrualActivityPosting) { this.id = id; this.name = name; this.shortName = shortName; @@ -897,6 +903,7 @@ public class LoanProductData implements Serializable { this.loanScheduleProcessingType = loanScheduleProcessingType; this.loanScheduleTypeOptions = null; this.loanScheduleProcessingTypeOptions = null; + this.enableAccrualActivityPosting = enableAccrualActivityPosting; } public LoanProductData(final LoanProductData productData, final Collection chargeOptions, @@ -1072,6 +1079,7 @@ public class LoanProductData implements Serializable { this.loanScheduleProcessingType = productData.getLoanScheduleProcessingType(); this.loanScheduleProcessingTypeOptions = loanScheduleProcessingTypeOptions; this.loanScheduleTypeOptions = loanScheduleTypeOptions; + this.enableAccrualActivityPosting = productData.enableAccrualActivityPosting; } private Collection nullIfEmpty(final Collection charges) { 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 dd3f7d99d..4ccadd68c 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 @@ -439,6 +439,9 @@ public class LoanProduct extends AbstractPersistableCustom { final Integer fixedLength = command.integerValueOfParameterNamed(LoanProductConstants.FIXED_LENGTH); + final boolean enableAccrualActivityPosting = command + .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING); + return new LoanProduct(fund, loanTransactionProcessingStrategy, loanProductPaymentAllocationRules, loanProductCreditAllocationRules, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, @@ -457,7 +460,8 @@ public class LoanProduct extends AbstractPersistableCustom { isEqualAmortization, productRates, fixedPrincipalPercentagePerInstallment, disallowExpectedDisbursements, allowApprovedDisbursedAmountsOverApplied, overAppliedCalculationType, overAppliedNumber, dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageDownPayment, enableAutoRepaymentForDownPayment, - repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType, fixedLength); + repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType, loanScheduleProcessingType, fixedLength, + enableAccrualActivityPosting); } @@ -674,7 +678,8 @@ public class LoanProduct extends AbstractPersistableCustom { final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final RepaymentStartDateType repaymentStartDateType, final boolean enableInstallmentLevelDelinquency, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, + final boolean enableAccrualActivityPosting) { this.fund = fund; this.transactionProcessingStrategyCode = transactionProcessingStrategyCode; @@ -723,7 +728,7 @@ public class LoanProduct extends AbstractPersistableCustom { recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(), isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, - enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength); + enableAutoRepaymentForDownPayment, loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting); this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal, defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments, @@ -908,6 +913,13 @@ public class LoanProduct extends AbstractPersistableCustom { this.isLinkedToFloatingInterestRate = newValue; } + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, + this.getLoanProductRelatedDetail().isEnableAccrualActivityPosting())) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING); + actualChanges.put(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, newValue); + this.getLoanProductRelatedDetail().setEnableAccrualActivityPosting(newValue); + } + if (this.isLinkedToFloatingInterestRate) { actualChanges.putAll(loanProductFloatingRates().update(command, floatingRate)); this.loanProductRelatedDetail.updateForFloatingInterestRates(); 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 c7cea895a..a9096d074 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 @@ -150,6 +150,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Enumerated(EnumType.STRING) private LoanScheduleProcessingType loanScheduleProcessingType; + @Column(name = "enable_accrual_activity_posting", nullable = false) + private boolean enableAccrualActivityPosting = false; + public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal, final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, @@ -161,7 +164,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, + final boolean enableAccrualActivityPosting) { return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, @@ -169,7 +173,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, isEqualAmortization, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment, - loanScheduleType, loanScheduleProcessingType, fixedLength); + loanScheduleType, loanScheduleProcessingType, fixedLength, enableAccrualActivityPosting); } protected LoanProductRelatedDetail() { @@ -187,7 +191,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final Integer daysInYearType, final boolean isInterestRecalculationEnabled, final boolean isEqualAmortization, final boolean enableDownPayment, final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment, final LoanScheduleType loanScheduleType, - final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength) { + final LoanScheduleProcessingType loanScheduleProcessingType, final Integer fixedLength, + final boolean enableAccrualActivityPosting) { this.currency = currency; this.principal = defaultPrincipal; this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod; @@ -220,6 +225,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.enableAutoRepaymentForDownPayment = enableAutoRepaymentForDownPayment; this.loanScheduleType = loanScheduleType; this.loanScheduleProcessingType = loanScheduleProcessingType; + this.enableAccrualActivityPosting = enableAccrualActivityPosting; } private Integer defaultToNullIfZero(final Integer value) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java index 4c4110988..4901cccbb 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java @@ -319,6 +319,8 @@ public final class LoanEnumerations { "Re-age"); case REAMORTIZE -> new LoanTransactionEnumData(LoanTransactionType.REAMORTIZE.getValue().longValue(), LoanTransactionType.REAMORTIZE.getCode(), "Re-amortize"); + case ACCRUAL_ACTIVITY -> new LoanTransactionEnumData(LoanTransactionType.ACCRUAL_ACTIVITY.getValue().longValue(), + LoanTransactionType.ACCRUAL_ACTIVITY.getCode(), "Accrual Activity"); }; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AccrualActivityPostingBusinessStep.java b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AccrualActivityPostingBusinessStep.java new file mode 100644 index 000000000..5e149bb9c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/AccrualActivityPostingBusinessStep.java @@ -0,0 +1,82 @@ +/** + * 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.cob.loan; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AccrualActivityPostingBusinessStep implements LoanCOBBusinessStep { + + private final BusinessEventNotifierService businessEventNotifierService; + private final LoanAccountDomainService loanAccountDomainService; + private final ExternalIdFactory externalIdFactory; + private final LoanWritePlatformService loanWritePlatformService; + + @Override + public Loan execute(Loan loan) { + log.debug("start processing loan accrual activity posting on installment due date with id [{}]", loan.getId()); + + // check if loan capable for posting + if (loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) { + final LocalDate currentDate = DateUtils.getBusinessLocalDate(); + // check if loan has installment due on business day + Optional first = loan.getRepaymentScheduleInstallments().stream() + .filter(loanRepaymentScheduleInstallment -> loanRepaymentScheduleInstallment.getDueDate().isEqual(currentDate)) + .findFirst(); + if (first.isPresent()) { + final LoanRepaymentScheduleInstallment installment = first.get(); + // check if there is no not-replayed-accrual-activity related to business date + List loanTransactions = loan.getLoanTransactions(loanTransaction -> loanTransaction.isNotReversed() + && loanTransaction.isAccrualActivity() && loanTransaction.getTransactionDate().isEqual(currentDate)); + if (loanTransactions.isEmpty()) { + loan = loanWritePlatformService.makeAccrualActivityTransaction(loan, installment, currentDate); + } + } + } + + log.debug("end processing loan accrual activity posting on installment due date with id [{}]", loan.getId()); + return loan; + } + + @Override + public String getEnumStyledName() { + return "ACCRUAL_ACTIVITY_POSTING"; + } + + @Override + public String getHumanReadableName() { + return "Accrual Activity Posting on Installment Due Date"; + } + +} 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 4b7173df5..8c75f6475 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 @@ -511,7 +511,8 @@ public class LoanScheduleAssembler { allowCompoundingOnEod, isEqualAmortization, isInterestToBeRecoveredFirstWhenGreaterThanEMI, fixedPrincipalPercentagePerInstallment, isPrincipalCompoundingDisabledForOverdueLoans, isDownPaymentEnabled, disbursedAmountPercentageForDownPayment, isAutoRepaymentForDownPaymentEnabled, repaymentStartDateType, submittedOnDate, - loanScheduleType, loanScheduleProcessingType, fixedLength); + loanScheduleType, loanScheduleProcessingType, fixedLength, + loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()); } private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionAssembler.java index 6674a69d1..0e641a7df 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionAssembler.java @@ -26,9 +26,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; @@ -65,4 +67,17 @@ public class LoanTransactionAssembler { return LoanTransaction.repaymentType(repaymentTransactionType, loan.getOffice(), repaymentAmount, paymentDetail, transactionDate, txnExternalId, null); } + + public LoanTransaction assembleAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment, + final LocalDate transactionDate) { + MonetaryCurrency currency = loan.getCurrency(); + ExternalId externalId = externalIdFactory.create(); + + BigDecimal interestPortion = installment.getInterestCharged(currency).getAmount(); + BigDecimal feeChargesPortion = installment.getFeeChargesCharged(currency).getAmount(); + BigDecimal penaltyChargesPortion = installment.getPenaltyChargesCharged(currency).getAmount(); + BigDecimal transactionAmount = interestPortion.add(feeChargesPortion).add(penaltyChargesPortion); + return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.ACCRUAL_ACTIVITY.getValue(), transactionDate, + transactionAmount, null, interestPortion, feeChargesPortion, penaltyChargesPortion, null, false, null, externalId); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 2eaa893db..6e3282713 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -84,6 +84,8 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent; @@ -1131,6 +1133,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return changedTransactionDetail; } + @Transactional + @Override + public Loan makeAccrualActivityTransaction(Loan loan, final LoanRepaymentScheduleInstallment installment, + final LocalDate transactionDate) { + businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionAccrualActivityPreBusinessEvent(loan)); + + LoanTransaction newAccrualActivityTransaction = loanTransactionAssembler.assembleAccrualActivityTransaction(loan, installment, + transactionDate); + + loanAccountDomainService.saveLoanTransactionWithDataIntegrityViolationChecks(newAccrualActivityTransaction); + + loan.addLoanTransaction(newAccrualActivityTransaction); + + businessEventNotifierService + .notifyPostBusinessEvent(new LoanTransactionAccrualActivityPostBusinessEvent(newAccrualActivityTransaction)); + + return loan; + } + @Transactional @Override public CommandProcessingResult makeInterestPaymentWaiver(final JsonCommand command) { 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 e35499b2b..7be800757 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 @@ -106,6 +106,8 @@ public final class LoanProductDataValidator { public static final String MAX_DIFFERENTIAL_LENDING_RATE = "maxDifferentialLendingRate"; public static final String IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED = "isFloatingInterestRateCalculationAllowed"; public static final String ACCOUNTING_RULE = "accountingRule"; + public static final String ENABLE_ACCRUAL_ACTIVITY_POSTING = "enableAccrualActivityPosting"; + /** * The parameters supported for this command. */ @@ -172,7 +174,8 @@ public final class LoanProductDataValidator { LoanProductConstants.ENABLE_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT, LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.REPAYMENT_START_DATE_TYPE, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, LoanProductConstants.LOAN_SCHEDULE_TYPE, - LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH)); + LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH, + LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING)); private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName, LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName, @@ -734,6 +737,18 @@ public final class LoanProductDataValidator { .value(receivablePenaltyAccountId).notNull().integerGreaterThanZero(); } + if (!AccountingValidations.isAccrualBasedAccounting(accountingRuleType) + && this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, element)) { + final Boolean enableAccrualActivityPosting = this.fromApiJsonHelper + .extractBooleanNamed(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, element); + baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING).value(enableAccrualActivityPosting) + .ignoreIfNull().validateForBooleanValue(); + if (!AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) { + baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING).isOneOfTheseValues(Boolean.TRUE) + .failWithCode("should be false for non accrual accounting"); + } + } + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME, element)) { final Boolean useBorrowerCycle = this.fromApiJsonHelper .extractBooleanNamed(LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME, element); @@ -1613,6 +1628,25 @@ public final class LoanProductDataValidator { final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed(ACCOUNTING_RULE, element, Locale.getDefault()); baseDataValidator.reset().parameter(ACCOUNTING_RULE).value(accountingRuleType).ignoreIfNull().inMinMaxRange(1, 4); + boolean actualValue = loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting(); + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, element)) { + final Boolean enableAccrualActivityPosting = this.fromApiJsonHelper + .extractBooleanNamed(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, element); + baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING).value(enableAccrualActivityPosting) + .ignoreIfNull().validateForBooleanValue(); + actualValue = enableAccrualActivityPosting; + } + if (actualValue) { + Integer ruleType = accountingRuleType; + if (ruleType == null) { + ruleType = loanProduct.getAccountingRule(); + } + if (!AccountingValidations.isAccrualBasedAccounting(ruleType)) { + baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING) + .failWithCode("should be false for non accrual accounting"); + } + } + final Long fundAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.FUND_SOURCE.getValue(), element); baseDataValidator.reset().parameter(LoanProductAccountingParams.FUND_SOURCE.getValue()).value(fundAccountId).ignoreIfNull() .integerGreaterThanZero(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 31b776163..d9d8c63a1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -234,7 +234,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.min_nominal_interest_rate_per_period as minInterestRatePerPeriod, lp.max_nominal_interest_rate_per_period as maxInterestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, " + "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,lp.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion, " + "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, " - + "lp.fixed_length as fixedLength, " + + "lp.fixed_length as fixedLength, " + "lp.enable_accrual_activity_posting as enableAccrualActivityPosting, " + "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,lp.grace_on_arrears_ageing as graceOnArrearsAgeing,lp.overdue_days_for_npa as overdueDaysForNPA, " + "lp.min_days_between_disbursal_and_first_repayment As minimumDaysBetweenDisbursalAndFirstRepayment, " + "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, " @@ -525,6 +525,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final LoanScheduleType loanScheduleType = LoanScheduleType.valueOf(loanScheduleTypeStr); final String loanScheduleProcessingTypeStr = rs.getString("loanScheduleProcessingType"); final LoanScheduleProcessingType loanScheduleProcessingType = LoanScheduleProcessingType.valueOf(loanScheduleProcessingTypeStr); + final boolean enableAccrualActivityPosting = rs.getBoolean("enableAccrualActivityPosting"); return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -547,7 +548,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo dueDaysForRepaymentEvent, overDueDaysForRepaymentEvent, enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment, advancedPaymentData, creditAllocationData, repaymentStartDateType, enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData(), - fixedLength); + fixedLength, enableAccrualActivityPosting); } } diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index be97efde9..b2272f3b2 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -161,4 +161,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0142_add_accrual_activity_transaction.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0142_add_accrual_activity_transaction.xml new file mode 100644 index 000000000..dcdc7b0a6 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0142_add_accrual_activity_transaction.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java index c2f12a18e..d666c1d94 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java @@ -102,7 +102,8 @@ public class ExternalEventConfigurationValidationServiceTest { "LoanReAgeTransactionBusinessEvent", "LoanUndoReAgeTransactionBusinessEvent", "LoanReAmortizeTransactionBusinessEvent", "LoanUndoReAmortizeTransactionBusinessEvent", "LoanReAgeBusinessEvent", "LoanUndoReAgeBusinessEvent", "LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent", - "LoanTransactionInterestPaymentWaiverPostBusinessEvent"); + "LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent", + "LoanTransactionAccrualActivityPreBusinessEvent"); List tenants = Arrays .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); @@ -186,7 +187,8 @@ public class ExternalEventConfigurationValidationServiceTest { "LoanReAgeTransactionBusinessEvent", "LoanUndoReAgeTransactionBusinessEvent", "LoanReAmortizeTransactionBusinessEvent", "LoanUndoReAmortizeTransactionBusinessEvent", "LoanReAgeBusinessEvent", "LoanUndoReAgeBusinessEvent", "LoanReAmortizeBusinessEvent", "LoanUndoReAmortizeBusinessEvent", "LoanTransactionInterestPaymentWaiverPreBusinessEvent", - "LoanTransactionInterestPaymentWaiverPostBusinessEvent"); + "LoanTransactionInterestPaymentWaiverPostBusinessEvent", "LoanTransactionAccrualActivityPostBusinessEvent", + "LoanTransactionAccrualActivityPreBusinessEvent"); List tenants = Arrays .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java index 666c1b6e8..c87f77407 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGeneratorTest.java @@ -95,7 +95,7 @@ public class DefaultScheduledDateGeneratorTest { Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, EMPTY_LIST, BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false, null, false, DISBURSEMENT_DATE, - submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null); + submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false); // when List result = underTest.generateRepaymentPeriods(expectedDisbursementDate, loanApplicationTerms, @@ -166,7 +166,7 @@ public class DefaultScheduledDateGeneratorTest { null, null, null, null, null, Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, EMPTY_LIST, BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false, null, false, - DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null); + DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false); } private HolidayDetailDTO createHolidayDTO() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index 25aca2974..359be4c55 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -136,9 +136,9 @@ public abstract class BaseLoanIntegrationTest { // expense protected final Account chargeOffExpenseAccount = accountHelper.createExpenseAccount("chargeOff"); protected final Account chargeOffFraudExpenseAccount = accountHelper.createExpenseAccount("chargeOffFraud"); - protected final Account writtenOffAccount = accountHelper.createExpenseAccount(); - protected final Account goodwillExpenseAccount = accountHelper.createExpenseAccount(); - protected final Account goodwillIncomeAccount = accountHelper.createIncomeAccount(); + protected final Account writtenOffAccount = accountHelper.createExpenseAccount("writtenOffAccount"); + protected final Account goodwillExpenseAccount = accountHelper.createExpenseAccount("goodwillExpenseAccount"); + protected final Account goodwillIncomeAccount = accountHelper.createIncomeAccount("goodwillIncomeAccount"); protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec); protected ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java new file mode 100644 index 000000000..1d833e8e3 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java @@ -0,0 +1,228 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder.DEFAULT_STRATEGY; +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.math.BigDecimal; +import java.util.Calendar; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.apache.fineract.client.models.PostChargesRequest; +import org.apache.fineract.client.models.PostChargesResponse; +import org.apache.fineract.client.models.PostClientsResponse; +import org.apache.fineract.client.models.PostLoanProductsRequest; +import org.apache.fineract.client.models.PostLoansLoanIdChargesRequest; +import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse; +import org.apache.fineract.client.models.PostLoansLoanIdRequest; +import org.apache.fineract.client.models.PostLoansRequest; +import org.apache.fineract.integrationtests.common.BusinessStepHelper; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.charges.ChargesHelper; +import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper; +import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; +import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; +import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ExtendWith(LoanTestLifecycleExtension.class) +public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrationTest { + + private static final Logger LOG = LoggerFactory.getLogger(LoanTransactionAccrualActivityPostingTest.class); + private static final String DATETIME_PATTERN = "dd MMMM yyyy"; + private static ResponseSpecification responseSpec; + private static RequestSpecification requestSpec; + private static LoanTransactionHelper loanTransactionHelper; + private static PostClientsResponse client; + private static ChargesHelper chargesHelper; + private static InlineLoanCOBHelper inlineLoanCOBHelper; + private static BusinessStepHelper businessStepHelper; + + @BeforeAll + public static void setup() { + Utils.initializeRESTAssured(); + requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + requestSpec.header("Fineract-Platform-TenantId", "default"); + responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); + ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec); + chargesHelper = new ChargesHelper(); + client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()); + inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec); + businessStepHelper = new BusinessStepHelper(); + // setup COB Business Steps to prevent test failing due other integration test configurations + businessStepHelper.updateSteps("LOAN_CLOSE_OF_BUSINESS", "APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION", + "CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", "UPDATE_LOAN_ARREARS_AGING", "ADD_PERIODIC_ACCRUAL_ENTRIES", + "EXTERNAL_ASSET_OWNER_TRANSFER", "ACCRUAL_ACTIVITY_POSTING"); + } + + // Create Loan with Interest and enabled Accrual Activity Posting + // Approve and disburse loan + // charge penalty with due date as 1st installment + // charge fee with due date as 1st installment + // charge penalty with due date as 3rd installment + // charge fee with due date as 2nd installment + @Test + public void test() { + final String disbursementDay = "01 January 2023"; + final String repaymentPeriod1DueDate = "01 February 2023"; + final String repaymentPeriod1CloseDate = "02 February 2023"; + final String repaymentPeriod1OneDayBeforeCloseDate = "01 February 2023"; + final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023"; + final String repaymentPeriod2DueDate = "01 March 2023"; + final String repaymentPeriod3DueDate = "01 April 2023"; + final String creationBusinessDay = "15 May 2023"; + AtomicReference loanId = new AtomicReference<>(); + runAt(creationBusinessDay, () -> { + + Long localLoanProductId = createLoanProductAccountingAccrualPeriodicWithInterest(); + loanId.set(applyForLoanApplicationWithInterest(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000), + disbursementDay)); + + LOG.info("Test Loan Product Id {1} http://localhost:4200/#/products/loan-products/{}/general", localLoanProductId); + LOG.info("Test Client Id {1} http://localhost:4200/#/clients/{}", client.getClientId()); + LOG.info("Test Loan Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), loanId); + + loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)) + .dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en")); + + loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay) + .dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + + chargePenalty(loanId.get(), 30.0, repaymentPeriod1DueDate); + chargePenalty(loanId.get(), 50.0, repaymentPeriod2DueDate); + chargeFee(loanId.get(), 40.0, repaymentPeriod1DueDate); + chargeFee(loanId.get(), 60.0, repaymentPeriod3DueDate); + + }); + runAt(repaymentPeriod1OneDayBeforeCloseDate, () -> { + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + verifyTransactions(loanId.get(), // + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + transaction(19.35, "Accrual", "31 January 2023", 0, 0, 19.35, 0, 0, 0.0, 0.0)); + }); + runAt(repaymentPeriod1CloseDate, () -> { + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + verifyTransactions(loanId.get(), // + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + transaction(19.35, "Accrual", "31 January 2023", 0, 0, 19.35, 0, 0, 0.0, 0.0), + transaction(70.65, "Accrual", "01 February 2023", 0, 0, 0.65, 40, 30, 0.0, 0.0), + transaction(90.0, "Accrual Activity", "01 February 2023", 0, 0, 20.0, 40.0, 30.0, 0.0, 0.0)); + + }); + runAt(repaymentPeriod1OneDayAfterCloseDate, () -> { + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get())); + verifyTransactions(loanId.get(), // + transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + transaction(19.35, "Accrual", "31 January 2023", 0, 0, 19.35, 0, 0, 0.0, 0.0), + transaction(70.65, "Accrual", "01 February 2023", 0, 0, 0.65, 40, 30, 0.0, 0.0), + transaction(90.0, "Accrual Activity", "01 February 2023", 1000, 0, 20.0, 40.0, 30.0, 0.0, 0.0), + transaction(0.71, "Accrual", "02 February 2023", 0, 0, 0.71, 0, 0, 0.0, 0.0)); + + }); + } + + private void chargeFee(Long loanId, Double amount, String dueDate) { + PostChargesResponse feeCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(false).amount(9.0) + .chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) + .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") + .name(Utils.randomStringGenerator("FEE_" + Calendar.getInstance().getTimeInMillis(), 5)).chargeAppliesTo(1).locale("en") + .active(true)); + PostLoansLoanIdChargesResponse feeLoanChargeResult = loanTransactionHelper.addChargesForLoan(loanId, + new PostLoansLoanIdChargesRequest().chargeId(feeCharge.getResourceId()).dateFormat(DATETIME_PATTERN).locale("en") + .amount(amount).dueDate(dueDate)); + assertNotNull(feeLoanChargeResult); + assertNotNull(feeLoanChargeResult.getResourceId()); + } + + private void chargePenalty(Long loanId, Double amount, String dueDate) { + PostChargesResponse penaltyCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(true).amount(10.0) + .chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue()) + .chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD") + .name(Utils.randomStringGenerator("PENALTY_" + Calendar.getInstance().getTimeInMillis(), 5)).chargeAppliesTo(1).locale("en") + .active(true)); + PostLoansLoanIdChargesResponse penaltyLoanChargeResult = loanTransactionHelper.addChargesForLoan(loanId, + new PostLoansLoanIdChargesRequest().chargeId(penaltyCharge.getResourceId()).dateFormat(DATETIME_PATTERN).locale("en") + .amount(amount).dueDate(dueDate)); + assertNotNull(penaltyLoanChargeResult); + assertNotNull(penaltyLoanChargeResult.getResourceId()); + } + + private Long createLoanProductAccountingAccrualPeriodicWithInterest() { + LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); + String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6); + String shortName = Utils.uniqueRandomStringGenerator("", 4); + return loanTransactionHelper.createLoanProduct(new PostLoanProductsRequest().name(name).shortName(shortName) + .description("Test loan description").currencyCode("USD").digitsAfterDecimal(2).daysInYearType(1).daysInMonthType(1) + .interestRecalculationCompoundingMethod(0).recalculationRestFrequencyType(1).rescheduleStrategyMethod(1) + .recalculationRestFrequencyInterval(0).isInterestRecalculationEnabled(false).interestRateFrequencyType(2).locale("en_GB") + .numberOfRepayments(4).repaymentFrequencyType(2L).interestRatePerPeriod(2.0).repaymentEvery(1).minPrincipal(100.0) + .principal(1000.0).maxPrincipal(10000000.0).amortizationType(1).interestType(1).interestCalculationPeriodType(1) + .dateFormat("dd MMMM yyyy").transactionProcessingStrategyCode(DEFAULT_STRATEGY).accountingRule(3) + .enableAccrualActivityPosting(true).fundSourceAccountId(fundSource.getAccountID().longValue())// + .loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())// + .transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())// + .interestOnLoanAccountId(interestIncomeAccount.getAccountID().longValue())// + .incomeFromFeeAccountId(feeIncomeAccount.getAccountID().longValue())// + .incomeFromPenaltyAccountId(feeIncomeAccount.getAccountID().longValue())// + .incomeFromRecoveryAccountId(recoveriesAccount.getAccountID().longValue())// + .writeOffAccountId(writtenOffAccount.getAccountID().longValue())// + .overpaymentLiabilityAccountId(overpaymentAccount.getAccountID().longValue())// + .receivableInterestAccountId(interestReceivableAccount.getAccountID().longValue())// + .receivableFeeAccountId(feeReceivableAccount.getAccountID().longValue())// + .receivablePenaltyAccountId(penaltyReceivableAccount.getAccountID().longValue())// + .goodwillCreditAccountId(goodwillExpenseAccount.getAccountID().longValue())// + .incomeFromGoodwillCreditInterestAccountId(goodwillIncomeAccount.getAccountID().longValue())// + .incomeFromGoodwillCreditFeesAccountId(goodwillIncomeAccount.getAccountID().longValue())// + .incomeFromGoodwillCreditPenaltyAccountId(goodwillIncomeAccount.getAccountID().longValue())// + .incomeFromChargeOffInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())// + .incomeFromChargeOffFeesAccountId(feeChargeOffAccount.getAccountID().longValue())// + .chargeOffExpenseAccountId(chargeOffExpenseAccount.getAccountID().longValue())// + .chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())// + .incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())// + ).getResourceId(); + } + + private static Long applyForLoanApplicationWithInterest(final Long clientID, final Long loanProductID, BigDecimal principal, + String applicationDisbursementDate) { + LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final PostLoansRequest loanRequest = new PostLoansRequest() // + .loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2) + .interestRatePerPeriod(BigDecimal.valueOf(2)).repaymentEvery(1).principal(principal).amortizationType(1).interestType(1) + .interestCalculationPeriodType(1).dateFormat("dd MMMM yyyy").transactionProcessingStrategyCode(DEFAULT_STRATEGY) + .loanType("individual").expectedDisbursementDate(applicationDisbursementDate).submittedOnDate(applicationDisbursementDate) + .clientId(clientID).productId(loanProductID); + return loanTransactionHelper.applyLoan(loanRequest).getLoanId(); + } + +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java index 4147b07b8..7d3406c68 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java @@ -550,6 +550,16 @@ public class ExternalEventConfigurationHelper { loanTransactionInterestPaymentWaiverPreBusinessEvent.put("enabled", false); defaults.add(loanTransactionInterestPaymentWaiverPreBusinessEvent); + Map loanTransactionAccrualActivityPostBusinessEvent = new HashMap<>(); + loanTransactionAccrualActivityPostBusinessEvent.put("type", "LoanTransactionAccrualActivityPostBusinessEvent"); + loanTransactionAccrualActivityPostBusinessEvent.put("enabled", false); + defaults.add(loanTransactionAccrualActivityPostBusinessEvent); + + Map loanTransactionAccrualActivityPreBusinessEvent = new HashMap<>(); + loanTransactionAccrualActivityPreBusinessEvent.put("type", "LoanTransactionAccrualActivityPreBusinessEvent"); + loanTransactionAccrualActivityPreBusinessEvent.put("enabled", false); + defaults.add(loanTransactionAccrualActivityPreBusinessEvent); + return defaults; }