This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new 3f4bbf3e1 FINERACT-2104: Accrual Activity Posting for Loans
3f4bbf3e1 is described below
commit 3f4bbf3e14088fc1feffb6662d1cc9785233cc55
Author: Soma Sörös <[email protected]>
AuthorDate: Tue Jul 9 07:56:36 2024 +0200
FINERACT-2104: Accrual Activity Posting for Loans
---
...ransactionAccrualActivityPostBusinessEvent.java | 35 ++++
...TransactionAccrualActivityPreBusinessEvent.java | 36 ++++
.../loanaccount/data/LoanTransactionEnumData.java | 2 +
.../portfolio/loanaccount/domain/Loan.java | 8 +-
.../loanaccount/domain/LoanTransaction.java | 4 +
.../loanaccount/domain/LoanTransactionType.java | 6 +
.../loanschedule/domain/LoanApplicationTerms.java | 16 +-
.../service/LoanWritePlatformService.java | 4 +
.../loanproduct/LoanProductConstants.java | 3 +
.../api/LoanProductsApiResourceSwagger.java | 6 +
.../loanproduct/data/LoanProductData.java | 18 +-
.../portfolio/loanproduct/domain/LoanProduct.java | 18 +-
.../domain/LoanProductRelatedDetail.java | 12 +-
.../loanproduct/service/LoanEnumerations.java | 2 +
.../loan/AccrualActivityPostingBusinessStep.java | 82 ++++++++
.../service/LoanScheduleAssembler.java | 3 +-
.../service/LoanTransactionAssembler.java | 15 ++
.../LoanWritePlatformServiceJpaRepositoryImpl.java | 21 ++
.../serialization/LoanProductDataValidator.java | 36 +++-
.../LoanProductReadPlatformServiceImpl.java | 5 +-
.../db/changelog/tenant/changelog-tenant.xml | 1 +
.../0142_add_accrual_activity_transaction.xml | 63 ++++++
...nalEventConfigurationValidationServiceTest.java | 6 +-
.../domain/DefaultScheduledDateGeneratorTest.java | 4 +-
.../integrationtests/BaseLoanIntegrationTest.java | 6 +-
.../LoanTransactionAccrualActivityPostingTest.java | 228 +++++++++++++++++++++
.../common/ExternalEventConfigurationHelper.java | 10 +
27 files changed, 620 insertions(+), 30 deletions(-)
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<Long> {
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<Long> {
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<Long> {
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<Long
return getTypeOf().isReAmortize() && isNotReversed();
}
+ public boolean isAccrualActivity() {
+ return getTypeOf().isAccrualActivity();
+ }
+
public boolean isIdentifiedBy(final Long identifier) {
return getId().equals(identifier);
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index f78c1d67a..fada088d8 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -64,6 +64,7 @@ public enum LoanTransactionType {
REAGE(29, "loanTransactionType.reAge"), //
REAMORTIZE(30, "loanTransactionType.reAmortize"), //
INTEREST_PAYMENT_WAIVER(31, "loanTransactionType.interestPaymentWaiver"),
//
+ ACCRUAL_ACTIVITY(32, "loanTransactionType.accrualActivity"), //
;
private final Integer value;
@@ -111,6 +112,7 @@ public enum LoanTransactionType {
case 29 -> 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<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings>
paymentChannelToFundSourceMappings;
public
List<GetLoanProductsProductIdResponse.GetLoanFeeToIncomeAccountMappings>
feeToIncomeAccountMappings;
public List<ChargeToGLAccountMapper> 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<PaymentTypeToGLAccountMapper>
paymentChannelToFundSourceMappings;
private Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
private Collection<ChargeToGLAccountMapper> 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<String, Object> accountingMappings,
@@ -761,7 +766,8 @@ public class LoanProductData implements Serializable {
final BigDecimal disbursedAmountPercentageForDownPayment, final
boolean enableAutoRepaymentForDownPayment,
final Collection<AdvancedPaymentData> paymentAllocation, final
Collection<CreditAllocationData> 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<ChargeData> 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<ChargeData> nullIfEmpty(final Collection<ChargeData>
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<Long> {
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<Long> {
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<Long> {
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<Long> {
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<Long> {
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<LoanRepaymentScheduleInstallment> 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<LoanTransaction> 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 @@
<include file="parts/0139_add_disburse_without_auto_payment_command.xml"
relativeToChangelogFile="true" />
<include file="parts/0140_trial_balance_with_asset_transfer_update.xml"
relativeToChangelogFile="true" />
<include
file="parts/0141_add_interest_payment_waiver_transaction_type.xml"
relativeToChangelogFile="true" />
+ <include file="parts/0142_add_accrual_activity_transaction.xml"
relativeToChangelogFile="true" />
</databaseChangeLog>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
+ <changeSet author="fineract" id="1">
+ <addColumn tableName="m_product_loan">
+ <column defaultValueBoolean="false" type="boolean"
name="enable_accrual_activity_posting">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+ <changeSet author="fineract" id="2">
+ <addColumn tableName="m_loan">
+ <column defaultValueBoolean="false" type="boolean"
name="enable_accrual_activity_posting">
+ <constraints nullable="false"/>
+ </column>
+ </addColumn>
+ </changeSet>
+ <changeSet id="3" author="fineract">
+ <insert tableName="m_external_event_configuration">
+ <column name="type"
value="LoanTransactionAccrualActivityPostBusinessEvent"/>
+ <column name="enabled" valueBoolean="false"/>
+ </insert>
+ <insert tableName="m_external_event_configuration">
+ <column name="type"
value="LoanTransactionAccrualActivityPreBusinessEvent"/>
+ <column name="enabled" valueBoolean="false"/>
+ </insert>
+ <insert tableName="r_enum_value">
+ <column name="enum_name" value="transaction_type_enum"/>
+ <column name="enum_id" valueNumeric="32"/>
+ <column name="enum_message_property" value="Accrual Activity"/>
+ <column name="enum_value" value="Accrual Activity"/>
+ <column name="enum_type" valueBoolean="false"/>
+ </insert>
+ </changeSet>
+ <changeSet id="4" author="fineract">
+ <insert tableName="m_batch_business_steps">
+ <column name="job_name" value="LOAN_CLOSE_OF_BUSINESS"/>
+ <column name="step_name" value="ACCRUAL_ACTIVITY_POSTING"/>
+ <column name="step_order" value="8"/>
+ </insert>
+ </changeSet>
+</databaseChangeLog>
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<FineractPlatformTenant> 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<FineractPlatformTenant> 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<PreGeneratedLoanSchedulePeriod> 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<Long> 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<String, Object> loanTransactionAccrualActivityPostBusinessEvent =
new HashMap<>();
+ loanTransactionAccrualActivityPostBusinessEvent.put("type",
"LoanTransactionAccrualActivityPostBusinessEvent");
+ loanTransactionAccrualActivityPostBusinessEvent.put("enabled", false);
+ defaults.add(loanTransactionAccrualActivityPostBusinessEvent);
+
+ Map<String, Object> loanTransactionAccrualActivityPreBusinessEvent =
new HashMap<>();
+ loanTransactionAccrualActivityPreBusinessEvent.put("type",
"LoanTransactionAccrualActivityPreBusinessEvent");
+ loanTransactionAccrualActivityPreBusinessEvent.put("enabled", false);
+ defaults.add(loanTransactionAccrualActivityPreBusinessEvent);
+
return defaults;
}