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;
 
     }

Reply via email to