FINERACT-2312: Accruals added for savings accounts (#4885)

diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
index f579fa2..3d767a7 100644
--- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
+++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java
@@ -58,7 +58,7 @@
     PURGE_EXTERNAL_EVENTS("Purge External Events"), //
     PURGE_PROCESSED_COMMANDS("Purge Processed Commands"), //
     ACCRUAL_ACTIVITY_POSTING("Accrual Activity Posting"), //
-    ;
+    ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Savings"); //
 
     private final String name;
 
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
index b0ac6ce..605a185 100644
--- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java
@@ -104,6 +104,7 @@
     public static final String activeParamName = "active";
     public static final String nameParamName = "name";
     public static final String shortNameParamName = "shortName";
+    public static final String interestReceivableAccount = "interestReceivableAccountId";
     public static final String descriptionParamName = "description";
     public static final String currencyCodeParamName = "currencyCode";
     public static final String digitsAfterDecimalParamName = "digitsAfterDecimal";
diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java
index f0bce2b..a9decdc 100644
--- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java
+++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/domain/interest/PostingPeriod.java
@@ -64,6 +64,10 @@
 
     private Integer financialYearBeginningMonth;
 
+    public void setOverdraftInterestRateAsFraction(BigDecimal overdraftInterestRateAsFraction) {
+        this.overdraftInterestRateAsFraction = overdraftInterestRateAsFraction;
+    }
+
     public static PostingPeriod createFrom(final LocalDateInterval periodInterval, final Money periodStartingBalance,
             final List<SavingsAccountTransactionDetailsForPostingPeriod> orderedListOfTransactions, final MonetaryCurrency currency,
             final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
@@ -545,4 +549,10 @@
         return this.financialYearBeginningMonth;
     }
 
+    // public List<CompoundingPeriod> getCompoundingPeriods() {return compoundingPeriods;}
+
+    public Money getClosingBalance() {
+        return closingBalance;
+    }
+
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java
index 4aa1b93..e0b7370 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java
@@ -28,6 +28,7 @@
 import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
 import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
 import org.apache.fineract.accounting.journalentry.data.SavingsTransactionDTO;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
 import org.apache.fineract.organisation.office.domain.Office;
 import org.springframework.stereotype.Component;
 
@@ -182,9 +183,21 @@
             else if (savingsTransactionDTO.getTransactionType().isAccrual()) {
                 // Post journal entry for Accrual Recognition
                 if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) {
-                    this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode,
-                            AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(),
-                            savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal);
+                    if (MathUtil.isGreaterThanZero(overdraftAmount)) {
+                        this.helper.createAccrualBasedDebitJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                AccrualAccountsForSavings.INTEREST_ON_SAVINGS.getValue(), savingsProductId, paymentTypeId, savingsId,
+                                transactionId, transactionDate, amount, isReversal);
+                        this.helper.createAccrualBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), savingsProductId, paymentTypeId, savingsId,
+                                transactionId, transactionDate, amount, isReversal);
+                    } else {
+                        this.helper.createAccrualBasedDebitJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                AccrualAccountsForSavings.INTEREST_RECEIVABLE.getValue(), savingsProductId, paymentTypeId, savingsId,
+                                transactionId, transactionDate, amount, isReversal);
+                        this.helper.createAccrualBasedCreditJournalEntriesAndReversalsForSavings(office, currencyCode,
+                                AccrualAccountsForSavings.INCOME_FROM_INTEREST.getValue(), savingsProductId, paymentTypeId, savingsId,
+                                transactionId, transactionDate, amount, isReversal);
+                    }
                 }
             }
 
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsConfig.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsConfig.java
new file mode 100644
index 0000000..b9b61cc
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsConfig.java
@@ -0,0 +1,60 @@
+/**
+ * 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.portfolio.savings.jobs.addaccrualtransactionforsavings;
+
+import org.apache.fineract.infrastructure.jobs.service.JobName;
+import org.apache.fineract.portfolio.savings.service.SavingsAccrualWritePlatformService;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.builder.JobBuilder;
+import org.springframework.batch.core.launch.support.RunIdIncrementer;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.step.builder.StepBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+
+@Configuration
+public class AddAccrualTransactionForSavingsConfig {
+
+    @Autowired
+    private JobRepository jobRepository;
+    @Autowired
+    private PlatformTransactionManager transactionManager;
+    @Autowired
+    private SavingsAccrualWritePlatformService savingsAccrualWritePlatformService;
+
+    @Bean
+    protected Step addAccrualTransactionForSavingsStep() {
+        return new StepBuilder(JobName.ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS.name(), jobRepository)
+                .tasklet(addAccrualTransactionForSavingsTasklet(), transactionManager).build();
+    }
+
+    @Bean
+    public Job addAccrualTransactionForSavingsJob() {
+        return new JobBuilder(JobName.ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_SAVINGS_WITH_INCOME_POSTED_AS_TRANSACTIONS.name(), jobRepository)
+                .start(addAccrualTransactionForSavingsStep()).incrementer(new RunIdIncrementer()).build();
+    }
+
+    @Bean
+    public AddAccrualTransactionForSavingsTasklet addAccrualTransactionForSavingsTasklet() {
+        return new AddAccrualTransactionForSavingsTasklet(savingsAccrualWritePlatformService);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsTasklet.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsTasklet.java
new file mode 100644
index 0000000..5638221
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/jobs/addaccrualtransactionforsavings/AddAccrualTransactionForSavingsTasklet.java
@@ -0,0 +1,50 @@
+/**
+ * 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.portfolio.savings.jobs.addaccrualtransactionforsavings;
+
+import java.time.LocalDate;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.exception.MultiException;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.portfolio.savings.service.SavingsAccrualWritePlatformService;
+import org.springframework.batch.core.StepContribution;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.core.step.tasklet.Tasklet;
+import org.springframework.batch.repeat.RepeatStatus;
+
+@RequiredArgsConstructor
+public class AddAccrualTransactionForSavingsTasklet implements Tasklet {
+
+    private final SavingsAccrualWritePlatformService savingsAccrualWritePlatformService;
+
+    @Override
+    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
+        try {
+            addPeriodicAccruals(DateUtils.getBusinessLocalDate());
+        } catch (MultiException e) {
+            throw new JobExecutionException(e);
+        }
+        return RepeatStatus.FINISHED;
+    }
+
+    private void addPeriodicAccruals(final LocalDate tilldate) throws MultiException {
+        savingsAccrualWritePlatformService.addAccrualEntries(tilldate);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index f84962a..a5de903 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -64,7 +64,9 @@
 import org.apache.fineract.portfolio.savings.data.SavingsAccountSummaryData;
 import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
 import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionEnumData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccrualData;
 import org.apache.fineract.portfolio.savings.data.SavingsProductData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargesPaidByData;
 import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
@@ -1386,4 +1388,13 @@
     public Long retrieveAccountIdByExternalId(final ExternalId externalId) {
         return savingsAccountRepositoryWrapper.findIdByExternalId(externalId);
     }
+
+    @Override
+    public List<SavingsAccrualData> retrievePeriodicAccrualData(LocalDate tillDate, SavingsAccount savings) {
+        Long savingsId = (savings != null) ? savings.getId() : null;
+        Integer status = SavingsAccountStatusType.ACTIVE.getValue();
+        Integer accountingRule = AccountingRuleType.ACCRUAL_PERIODIC.getValue();
+
+        return this.savingsAccountRepositoryWrapper.findAccrualData(tillDate, savingsId, status, accountingRule);
+    }
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformService.java
new file mode 100644
index 0000000..ed6f4c4
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformService.java
@@ -0,0 +1,28 @@
+/**
+ * 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.portfolio.savings.service;
+
+import java.time.LocalDate;
+import org.apache.fineract.infrastructure.core.exception.MultiException;
+
+public interface SavingsAccrualWritePlatformService {
+
+    void addAccrualEntries(LocalDate tillDate) throws MultiException;
+
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java
new file mode 100644
index 0000000..6fc136b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java
@@ -0,0 +1,182 @@
+/**
+ * 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.portfolio.savings.service;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
+import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
+import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
+import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccrualData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
+import org.apache.fineract.portfolio.savings.domain.SavingsHelper;
+import org.apache.fineract.portfolio.savings.domain.interest.CompoundInterestValues;
+import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
+import org.apache.fineract.portfolio.savings.domain.interest.SavingsAccountTransactionDetailsForPostingPeriod;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SavingsAccrualWritePlatformServiceImpl implements SavingsAccrualWritePlatformService {
+
+    private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
+    private final SavingsAccountAssembler savingsAccountAssembler;
+    private final SavingsAccountRepositoryWrapper savingsAccountRepository;
+    private final SavingsHelper savingsHelper;
+    private final ConfigurationDomainService configurationDomainService;
+    private final SavingsAccountDomainService savingsAccountDomainService;
+
+    @Transactional
+    @Override
+    public void addAccrualEntries(LocalDate tillDate) throws JobExecutionException {
+        final List<SavingsAccrualData> savingsAccrualData = savingsAccountReadPlatformService.retrievePeriodicAccrualData(tillDate, null);
+        final Integer financialYearBeginningMonth = configurationDomainService.retrieveFinancialYearBeginningMonth();
+        final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
+                .isSavingsInterestPostingAtCurrentPeriodEnd();
+        final MathContext mc = MoneyHelper.getMathContext();
+
+        List<Throwable> errors = new ArrayList<>();
+        for (SavingsAccrualData savingsAccrual : savingsAccrualData) {
+            try {
+                if (savingsAccrual.getDepositType().isSavingsDeposit() && savingsAccrual.getIsAllowOverdraft()) {
+                    if (!savingsAccrual.getIsTypeInterestReceivable()) {
+                        continue;
+                    }
+                }
+                SavingsAccount savingsAccount = savingsAccountAssembler.assembleFrom(savingsAccrual.getId(), false);
+                LocalDate fromDate = savingsAccrual.getAccruedTill();
+                if (fromDate == null) {
+                    fromDate = savingsAccount.getActivationDate();
+                }
+                log.debug("Processing savings account {} from date {} till date {}", savingsAccrual.getAccountNo(), fromDate, tillDate);
+                addAccrualTransactions(savingsAccount, fromDate, tillDate, financialYearBeginningMonth,
+                        isSavingsInterestPostingAtCurrentPeriodEnd, mc, null);
+            } catch (Exception e) {
+                log.error("Failed to add accrual transaction for savings {} : {}", savingsAccrual.getAccountNo(), e.getMessage());
+                errors.add(e.getCause());
+            }
+        }
+        if (!errors.isEmpty()) {
+            throw new JobExecutionException(errors);
+        }
+    }
+
+    private void addAccrualTransactions(SavingsAccount savingsAccount, final LocalDate fromDate, final LocalDate tillDate,
+            final Integer financialYearBeginningMonth, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final MathContext mc,
+            final Function<LocalDate, String> refNoProvider) {
+        final Set<Long> existingTransactionIds = new HashSet<>();
+        final Set<Long> existingReversedTransactionIds = new HashSet<>();
+
+        existingTransactionIds.addAll(savingsAccount.findExistingTransactionIds());
+        existingReversedTransactionIds.addAll(savingsAccount.findExistingReversedTransactionIds());
+
+        List<LocalDate> postedAsOnTransactionDates = savingsAccount.getManualPostingDates();
+        final SavingsPostingInterestPeriodType postingPeriodType = SavingsPostingInterestPeriodType
+                .fromInt(savingsAccount.getInterestCalculationType());
+
+        final SavingsCompoundingInterestPeriodType compoundingPeriodType = SavingsCompoundingInterestPeriodType
+                .fromInt(savingsAccount.getInterestPostingPeriodType());
+
+        final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType
+                .fromInt(savingsAccount.getInterestCalculationDaysInYearType());
+
+        final List<LocalDateInterval> postingPeriodIntervals = this.savingsHelper.determineInterestPostingPeriods(fromDate, tillDate,
+                postingPeriodType, financialYearBeginningMonth, postedAsOnTransactionDates);
+
+        final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
+        final MonetaryCurrency currency = savingsAccount.getCurrency();
+        Money periodStartingBalance = Money.zero(currency);
+
+        final SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType
+                .fromInt(savingsAccount.getInterestCalculationType());
+        final BigDecimal interestRateAsFraction = savingsAccount.getEffectiveInterestRateAsFractionAccrual(mc, tillDate);
+        final Collection<Long> interestPostTransactions = this.savingsHelper.fetchPostInterestTransactionIds(savingsAccount.getId());
+        boolean isInterestTransfer = false;
+        final Money minBalanceForInterestCalculation = Money.of(currency, savingsAccount.getMinBalanceForInterestCalculation());
+        List<SavingsAccountTransactionDetailsForPostingPeriod> savingsAccountTransactionDetailsForPostingPeriodList = savingsAccount
+                .toSavingsAccountTransactionDetailsForPostingPeriodList();
+        for (final LocalDateInterval periodInterval : postingPeriodIntervals) {
+            if (DateUtils.isDateInTheFuture(periodInterval.endDate())) {
+                continue;
+            }
+            final boolean isUserPosting = postedAsOnTransactionDates.contains(periodInterval.endDate());
+
+            final PostingPeriod postingPeriod = PostingPeriod.createFrom(periodInterval, periodStartingBalance,
+                    savingsAccountTransactionDetailsForPostingPeriodList, currency, compoundingPeriodType, interestCalculationType,
+                    interestRateAsFraction, daysInYearType.getValue(), tillDate, interestPostTransactions, isInterestTransfer,
+                    minBalanceForInterestCalculation, isSavingsInterestPostingAtCurrentPeriodEnd, isUserPosting,
+                    financialYearBeginningMonth);
+
+            postingPeriod.setOverdraftInterestRateAsFraction(
+                    savingsAccount.getNominalAnnualInterestRateOverdraft().divide(BigDecimal.valueOf(100), mc));
+            periodStartingBalance = postingPeriod.closingBalance();
+
+            allPostingPeriods.add(postingPeriod);
+        }
+        BigDecimal compoundedInterest = BigDecimal.ZERO;
+        BigDecimal unCompoundedInterest = BigDecimal.ZERO;
+        final CompoundInterestValues compoundInterestValues = new CompoundInterestValues(compoundedInterest, unCompoundedInterest);
+
+        final List<LocalDate> accrualTransactionDates = savingsAccount.retrieveOrderedAccrualTransactions().stream()
+                .map(transaction -> transaction.getTransactionDate()).toList();
+        LocalDate accruedTillDate = fromDate;
+
+        for (PostingPeriod period : allPostingPeriods) {
+            period.calculateInterest(compoundInterestValues);
+            final LocalDate endDate = period.getPeriodInterval().endDate();
+            if (!accrualTransactionDates.contains(period.getPeriodInterval().endDate())
+                    && !MathUtil.isZero(period.closingBalance().getAmount())) {
+                String refNo = (refNoProvider != null) ? refNoProvider.apply(endDate) : null;
+                SavingsAccountTransaction savingsAccountTransaction = SavingsAccountTransaction.accrual(savingsAccount,
+                        savingsAccount.office(), period.getPeriodInterval().endDate(), period.getInterestEarned().abs(), false, refNo);
+                savingsAccountTransaction.setRunningBalance(period.getClosingBalance());
+                savingsAccountTransaction.setOverdraftAmount(period.getInterestEarned());
+                savingsAccount.addTransaction(savingsAccountTransaction);
+            }
+        }
+
+        savingsAccount.setAccruedTillDate(accruedTillDate);
+        savingsAccountRepository.saveAndFlush(savingsAccount);
+        savingsAccountDomainService.postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds, false);
+    }
+
+}
diff --git a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
index f44f384..f5c4e60 100644
--- a/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
+++ b/fineract-provider/src/main/resources/db/changelog/db.changelog-master.xml
@@ -34,6 +34,7 @@
     <!-- Add new module to the end of this modules list (to keep the existing auto-increment identifiers) -->
     <include file="db/changelog/tenant/module/loan/module-changelog-master.xml" context="tenant_db AND !initial_switch"/>
     <include file="db/changelog/tenant/module/investor/module-changelog-master.xml" context="tenant_db AND !initial_switch"/>
+    <include file="db/changelog/tenant/module/savings/parts/module-changelog-master.xml" context="tenant_db AND !initial_switch"/>
     <includeAll path="db/custom-changelog" errorIfMissingOrEmpty="false" context="tenant_db AND !initial_switch AND custom_changelog"/>
     <include file="/db/changelog/tenant/module/progressiveloan/module-changelog-master.xml" context="tenant_db AND !initial_switch"/>
     <!-- Scripts to run after the modules were initialized  -->
diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccrualData.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccrualData.java
new file mode 100644
index 0000000..1ff69ef
--- /dev/null
+++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccrualData.java
@@ -0,0 +1,45 @@
+/**
+ * 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.portfolio.savings.data;
+
+import java.time.LocalDate;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.data.EnumOptionData;
+import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
+
+@Data
+@RequiredArgsConstructor
+public class SavingsAccrualData {
+
+    private final Long id;
+    private final String accountNo;
+    private final LocalDate accruedTill;
+    private final Boolean isTypeInterestReceivable;
+    private final Boolean isAllowOverdraft;
+    private final Integer depositType;
+
+    public DepositAccountType getDepositType() {
+        final EnumOptionData depositType = SavingsEnumerations.depositType(this.depositType);
+        DepositAccountType depositAccountType = DepositAccountType.fromInt(depositType.getId().intValue());
+        return depositAccountType;
+    }
+
+}
diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
index 5db6e32..e2f226e 100644
--- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
+++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java
@@ -71,6 +71,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
 import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
@@ -337,6 +338,9 @@
     @JoinColumn(name = "tax_group_id")
     private TaxGroup taxGroup;
 
+    @Column(name = "accrued_till_date")
+    private LocalDate accruedTillDate;
+
     @Column(name = "total_savings_amount_on_hold", scale = 6, precision = 19, nullable = true)
     private BigDecimal savingsOnHoldAmount;
     @OneToMany(cascade = CascadeType.ALL, mappedBy = "account", orphanRemoval = true, fetch = FetchType.LAZY)
@@ -926,6 +930,10 @@
         return this.nominalAnnualInterestRateOverdraft.divide(BigDecimal.valueOf(100L), mc);
     }
 
+    public BigDecimal getEffectiveInterestRateAsFractionAccrual(final MathContext mc, final LocalDate upToInterestCalculationDate) {
+        return this.nominalAnnualInterestRate.divide(BigDecimal.valueOf(100L), mc);
+    }
+
     @SuppressWarnings("unused")
     protected BigDecimal getEffectiveInterestRateAsFraction(final MathContext mc, final LocalDate upToInterestCalculationDate) {
         return this.nominalAnnualInterestRate.divide(BigDecimal.valueOf(100L), mc);
@@ -939,6 +947,11 @@
         return isAllowOverdraft() && !MathUtil.isEmpty(getOverdraftLimit()) && !MathUtil.isEmpty(nominalAnnualInterestRateOverdraft);
     }
 
+    public List<SavingsAccountTransaction> retrieveOrderedAccrualTransactions() {
+        return retrieveListOfTransactions().stream().filter(SavingsAccountTransaction::isAccrual)
+                .sorted(new SavingsAccountTransactionComparator()).collect(Collectors.toList());
+    }
+
     protected List<SavingsAccountTransaction> retreiveOrderedNonInterestPostingTransactions() {
         final List<SavingsAccountTransaction> listOfTransactionsSorted = retrieveListOfTransactions();
 
@@ -3841,10 +3854,21 @@
         return this.withHoldTax;
     }
 
+    public void setAccruedTillDate(LocalDate accruedTillDate) {
+        this.accruedTillDate = accruedTillDate;
+    }
+
     public List<SavingsAccountTransactionDetailsForPostingPeriod> toSavingsAccountTransactionDetailsForPostingPeriodList(
             List<SavingsAccountTransaction> transactions) {
         return transactions.stream()
                 .map(transaction -> transaction.toSavingsAccountTransactionDetailsForPostingPeriod(this.currency, this.allowOverdraft))
                 .toList();
     }
+
+    public List<SavingsAccountTransactionDetailsForPostingPeriod> toSavingsAccountTransactionDetailsForPostingPeriodList() {
+        return retreiveOrderedNonInterestPostingTransactions().stream()
+                .map(transaction -> transaction.toSavingsAccountTransactionDetailsForPostingPeriod(this.currency, this.allowOverdraft))
+                .toList();
+    }
+
 }
diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
index aaece88..5a41d3a 100644
--- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
+++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java
@@ -19,8 +19,10 @@
 package org.apache.fineract.portfolio.savings.domain;
 
 import jakarta.persistence.LockModeType;
+import java.time.LocalDate;
 import java.util.List;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import org.apache.fineract.portfolio.savings.data.SavingsAccrualData;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.jpa.repository.JpaRepository;
@@ -70,4 +72,26 @@
 
     @Query("SELECT sa.id FROM SavingsAccount sa WHERE sa.externalId = :externalId")
     Long findIdByExternalId(@Param("externalId") ExternalId externalId);
+
+    @Query("""
+            SELECT new org.apache.fineract.portfolio.savings.data.SavingsAccrualData(
+                savings.id,
+                savings.accountNumber,
+                savings.accruedTillDate,
+                CASE WHEN apm.financialAccountType = 18 THEN TRUE ELSE FALSE END,
+                msp.allowOverdraft,
+                savings.depositType
+            )
+            FROM SavingsAccount savings
+            LEFT JOIN SavingsProduct msp ON msp = savings.product
+            LEFT JOIN ProductToGLAccountMapping apm ON apm.productId = msp.id and (apm.financialAccountType = 18 or apm.financialAccountType IS NULL)
+            WHERE savings.status = :status
+              AND (savings.nominalAnnualInterestRate IS NOT NULL AND savings.nominalAnnualInterestRate > 0)
+              AND msp.accountingRule = :accountingRule
+              AND ( savings.closedOnDate <= :tillDate OR savings.closedOnDate IS NULL)
+              AND ( savings.accruedTillDate <= :tillDate OR savings.accruedTillDate IS NULL )
+            ORDER BY savings.id
+            """)
+    List<SavingsAccrualData> findAccrualData(@Param("tillDate") LocalDate tillDate, @Param("savingsId") Long savingsId,
+            @Param("status") Integer status, @Param("accountingRule") Integer accountingRule);
 }
diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
index 3a40633..562a454 100644
--- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
+++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepositoryWrapper.java
@@ -22,6 +22,7 @@
 import java.util.List;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.portfolio.savings.DepositAccountType;
+import org.apache.fineract.portfolio.savings.data.SavingsAccrualData;
 import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
@@ -179,4 +180,10 @@
     public Long findIdByExternalId(final ExternalId externalId) {
         return this.repository.findIdByExternalId(externalId);
     }
+
+    @Transactional(readOnly = true)
+    public List<SavingsAccrualData> findAccrualData(final LocalDate tillDate, final Long savingsId, final Integer status,
+            final Integer accountingRule) {
+        return this.repository.findAccrualData(tillDate, savingsId, status, accountingRule);
+    }
 }
diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
index 96aef54..51d1175 100644
--- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
+++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java
@@ -198,6 +198,14 @@
                 date, amount, isReversed, isManualTransaction, lienTransaction, refNo);
     }
 
+    public static SavingsAccountTransaction accrual(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
+            final Money amount, final boolean isManualTransaction, final String refNo) {
+        final boolean isReversed = false;
+        final Boolean lienTransaction = false;
+        return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ACCRUAL.getValue(), date, amount,
+                isReversed, isManualTransaction, lienTransaction, refNo);
+    }
+
     public static SavingsAccountTransaction interestPosting(final SavingsAccount savingsAccount, final Office office, final LocalDate date,
             final Money amount, final boolean isManualTransaction) {
         final boolean isReversed = false;
@@ -415,7 +423,7 @@
         return Money.of(currency, this.overdraftAmount);
     }
 
-    void setOverdraftAmount(Money overdraftAmount) {
+    public void setOverdraftAmount(Money overdraftAmount) {
         this.overdraftAmount = overdraftAmount == null ? null : overdraftAmount.getAmount();
     }
 
@@ -511,6 +519,10 @@
         return this.isDeposit() || this.isWithdrawal() || this.isChargeTransaction() || this.isDividendPayout() || this.isInterestPosting();
     }
 
+    public boolean isAccrual() {
+        return getTransactionType().isAccrual();
+    }
+
     public boolean isInterestPostingAndNotReversed() {
         return getTransactionType().isInterestPosting() && isNotReversed();
     }
diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
index dc36728..cf408c5 100644
--- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
+++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformService.java
@@ -27,6 +27,8 @@
 import org.apache.fineract.portfolio.savings.DepositAccountType;
 import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
 import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
+import org.apache.fineract.portfolio.savings.data.SavingsAccrualData;
+import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
 
 public interface SavingsAccountReadPlatformService {
 
@@ -69,4 +71,6 @@
     List<SavingsAccountTransactionData> retrieveAllTransactionData(List<String> refNo);
 
     Long retrieveAccountIdByExternalId(ExternalId externalId);
+
+    List<SavingsAccrualData> retrievePeriodicAccrualData(LocalDate tillDate, SavingsAccount savings);
 }
diff --git a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml
index 8746633..e18006b 100644
--- a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml
+++ b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/module-changelog-master.xml
@@ -23,4 +23,7 @@
   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">
   <!-- Sequence is starting from 2000 to make it easier to move existing liquibase changesets here -->
+  <include file="parts/2001_add_savings_accrual_job.xml" relativeToChangelogFile="true" />
+  <include file="parts/2002_add_savings_accrual_permission.xml" relativeToChangelogFile="true" />
+  <include file="parts/2003_add_accrued_till_date_to_savings_account.xml" relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2001_add_savings_accrual_job.xml b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2001_add_savings_accrual_job.xml
new file mode 100644
index 0000000..cc18f1f
--- /dev/null
+++ b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2001_add_savings_accrual_job.xml
@@ -0,0 +1,46 @@
+<?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.3.xsd">
+    <changeSet author="fineract" id="1">
+        <insert tableName="job">
+            <column name="name" value="Add Accrual Transactions For Savings"/>
+            <column name="display_name" value="Add Accrual Transactions For Savings"/>
+            <column name="cron_expression" value="0 1 0 1/1 * ? *"/>
+            <column name="create_time" valueDate="${current_datetime}"/>
+            <column name="task_priority" valueNumeric="5"/>
+            <column name="group_name"/>
+            <column name="previous_run_start_time"/>
+            <column name="job_key" value="Add Accrual Transactions For Savings _ DEFAULT"/>
+            <column name="initializing_errorlog"/>
+            <column name="is_active" valueBoolean="false"/>
+            <column name="currently_running" valueBoolean="false"/>
+            <column name="updates_allowed" valueBoolean="true"/>
+            <column name="scheduler_group" valueNumeric="0"/>
+            <column name="is_misfired" valueBoolean="false"/>
+            <column name="node_id" valueNumeric="1"/>
+            <column name="is_mismatched_job" valueBoolean="true"/>
+            <column name="short_name" value="ADD_ATFS"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2002_add_savings_accrual_permission.xml b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2002_add_savings_accrual_permission.xml
new file mode 100644
index 0000000..57f5157
--- /dev/null
+++ b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2002_add_savings_accrual_permission.xml
@@ -0,0 +1,34 @@
+<?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.3.xsd">
+    <changeSet author="fineract" id="1">
+        <insert tableName="m_permission">
+            <column name="grouping" value="accounting"/>
+            <column name="code" value="EXECUTEFORSAVINGS"/>
+            <column name="entity_name" value="PERIODICACCRUALACCOUNTINGFORSAVINGS"/>
+            <column name="action_name" value="EXECUTE"/>
+            <column name="can_maker_checker" valueBoolean="false"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git a/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2003_add_accrued_till_date_to_savings_account.xml b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2003_add_accrued_till_date_to_savings_account.xml
new file mode 100644
index 0000000..1bdd4dd
--- /dev/null
+++ b/fineract-savings/src/main/resources/db/changelog/tenant/module/savings/parts/parts/2003_add_accrued_till_date_to_savings_account.xml
@@ -0,0 +1,31 @@
+<?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.3.xsd">
+    <changeSet author="fineract" id="1">
+        <addColumn tableName="m_savings_account">
+            <column name="accrued_till_date" type="DATE" defaultValueComputed="NULL" />
+        </addColumn>
+    </changeSet>
+
+</databaseChangeLog>
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccrualAccountingIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccrualAccountingIntegrationTest.java
new file mode 100644
index 0000000..abb7491
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccrualAccountingIntegrationTest.java
@@ -0,0 +1,251 @@
+/**
+ * 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 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.math.MathContext;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SavingsAccrualAccountingIntegrationTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SavingsAccrualAccountingIntegrationTest.class);
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsAccountHelper savingsAccountHelper;
+    private SchedulerJobHelper schedulerJobHelper;
+    private JournalEntryHelper journalEntryHelper;
+    private AccountHelper accountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec);
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void testPositiveAccrualPostsCorrectJournalEntries() {
+        // --- ARRANGE ---
+        LOG.info("------------------------- INITIATING POSITIVE ACCRUAL ACCOUNTING TEST -------------------------");
+        final int daysToSubtract = 10;
+        final String amount = "10000";
+
+        final Account savingsReferenceAccount = this.accountHelper.createAssetAccount("Savings Reference");
+        final Account interestOnSavingsAccount = this.accountHelper.createExpenseAccount("Interest on Savings (Expense)");
+        final Account savingsControlAccount = this.accountHelper.createLiabilityAccount("Savings Control");
+        final Account interestPayableAccount = this.accountHelper.createLiabilityAccount("Interest Payable (Liability)");
+        final Account incomeFromFeesAccount = this.accountHelper.createIncomeAccount("Income from Fees");
+        final Account[] accountList = { savingsReferenceAccount, savingsControlAccount, interestOnSavingsAccount, interestPayableAccount,
+                incomeFromFeesAccount };
+
+        final SavingsProductHelper productHelper = new SavingsProductHelper().withNominalAnnualInterestRate(new BigDecimal("10.0"))
+                .withAccountingRuleAsAccrualBased(accountList)
+                .withSavingsReferenceAccountId(savingsReferenceAccount.getAccountID().toString())
+                .withSavingsControlAccountId(savingsControlAccount.getAccountID().toString())
+                .withInterestOnSavingsAccountId(interestOnSavingsAccount.getAccountID().toString())
+                .withInterestPayableAccountId(interestPayableAccount.getAccountID().toString())
+                .withIncomeFromFeeAccountId(incomeFromFeesAccount.getAccountID().toString());
+
+        final Integer savingsProductId = SavingsProductHelper.createSavingsProduct(productHelper.build(), this.requestSpec,
+                this.responseSpec);
+        Assertions.assertNotNull(savingsProductId, "Failed to create savings product.");
+
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2020");
+        final LocalDate startDate = LocalDate.now(Utils.getZoneIdOfTenant()).minusDays(daysToSubtract);
+        final String startDateString = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
+        final Integer savingsAccountId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, savingsProductId,
+                SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startDateString);
+        this.savingsAccountHelper.approveSavingsOnDate(savingsAccountId, startDateString);
+        this.savingsAccountHelper.activateSavings(savingsAccountId, startDateString);
+        this.savingsAccountHelper.depositToSavingsAccount(savingsAccountId, amount, startDateString, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        // --- ACT ---
+        schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions For Savings");
+
+        // --- ASSERT ---
+        List<HashMap> accrualTransactions = getAccrualTransactions(savingsAccountId);
+        Assertions.assertFalse(accrualTransactions.isEmpty(), "No accrual transactions were found.");
+
+        Number firstTransactionIdNumber = (Number) accrualTransactions.get(0).get("id");
+        ArrayList<HashMap> journalEntries = journalEntryHelper.getJournalEntriesByTransactionId("S" + firstTransactionIdNumber.intValue());
+        Assertions.assertFalse(journalEntries.isEmpty(), "No journal entries found for positive accrual.");
+
+        boolean debitFound = false;
+        boolean creditFound = false;
+        for (Map<String, Object> entry : journalEntries) {
+            String entryType = (String) ((HashMap) entry.get("entryType")).get("value");
+            Integer accountId = ((Number) entry.get("glAccountId")).intValue();
+            if ("DEBIT".equals(entryType) && accountId.equals(interestOnSavingsAccount.getAccountID())) {
+                debitFound = true;
+            }
+            if ("CREDIT".equals(entryType) && accountId.equals(interestPayableAccount.getAccountID())) {
+                creditFound = true;
+            }
+        }
+
+        Assertions.assertTrue(debitFound, "DEBIT to Interest on Savings (Expense) Account not found for positive accrual.");
+        Assertions.assertTrue(creditFound, "CREDIT to Interest Payable (Liability) Account not found for positive accrual.");
+
+        BigDecimal interest = getCalculateAccrualsForDay(productHelper, amount);
+
+        for (HashMap accrual : accrualTransactions) {
+            BigDecimal amountAccrualTransaccion = BigDecimal.valueOf((Double) accrual.get("amount"));
+            Assertions.assertEquals(interest, amountAccrualTransaccion);
+        }
+        LOG.info("VALIDATE AMOUNT AND ACCOUNT");
+
+    }
+
+    @Test
+    public void testNegativeAccrualPostsCorrectJournalEntries() {
+        // --- ARRANGE ---
+        LOG.info("------------------------- INITIATING NEGATIVE ACCRUAL (OVERDRAFT) ACCOUNTING TEST -------------------------");
+        final int daysToSubtract = 10;
+        final String amount = "10000";
+
+        final Account savingsReferenceAccount = this.accountHelper.createAssetAccount("Savings Reference");
+        final Account overdraftPortfolioControl = this.accountHelper.createAssetAccount("Overdraft Portfolio");
+        final Account interestReceivableAccount = this.accountHelper.createAssetAccount("Interest Receivable (Asset)");
+        final Account savingsControlAccount = this.accountHelper.createLiabilityAccount("Savings Control");
+        final Account interestPayableAccount = this.accountHelper.createLiabilityAccount("Interest Payable");
+        final Account overdraftInterestIncomeAccount = this.accountHelper.createIncomeAccount("Overdraft Interest Income");
+        final Account expenseAccount = this.accountHelper.createExpenseAccount("Interest on Savings (Expense)");
+
+        final Account[] accountList = { savingsReferenceAccount, savingsControlAccount, expenseAccount, overdraftInterestIncomeAccount };
+
+        final String overdraftLimit = "10000";
+        final String overdraftInterestRate = "21.0";
+        final SavingsProductHelper productHelper = new SavingsProductHelper()
+                .withNominalAnnualInterestRate(new BigDecimal(overdraftInterestRate)).withAccountingRuleAsAccrualBased(accountList)
+                .withOverDraftRate(overdraftLimit, overdraftInterestRate)
+                .withSavingsReferenceAccountId(savingsReferenceAccount.getAccountID().toString())
+                .withSavingsControlAccountId(savingsControlAccount.getAccountID().toString())
+                .withInterestReceivableAccountId(interestReceivableAccount.getAccountID().toString())
+                .withIncomeFromInterestId(overdraftInterestIncomeAccount.getAccountID().toString())
+                .withInterestPayableAccountId(interestPayableAccount.getAccountID().toString())
+                .withInterestOnSavingsAccountId(expenseAccount.getAccountID().toString())
+                .withOverdraftPortfolioControlId(overdraftPortfolioControl.getAccountID().toString());
+
+        final Integer savingsProductId = SavingsProductHelper.createSavingsProduct(productHelper.build(), this.requestSpec,
+                this.responseSpec);
+        Assertions.assertNotNull(savingsProductId, "Savings product with overdraft creation failed.");
+
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2020");
+        final LocalDate startDate = LocalDate.now(Utils.getZoneIdOfTenant()).minusDays(daysToSubtract);
+        final String startDateString = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
+        final Integer savingsAccountId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, savingsProductId,
+                SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startDateString);
+        this.savingsAccountHelper.approveSavingsOnDate(savingsAccountId, startDateString);
+        this.savingsAccountHelper.activateSavings(savingsAccountId, startDateString);
+        this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsAccountId, "10000", startDateString,
+                CommonConstants.RESPONSE_RESOURCE_ID);
+
+        // --- ACT ---
+        schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions For Savings");
+
+        // --- ASSERT ---
+        List<HashMap> accrualTransactions = getAccrualTransactions(savingsAccountId);
+        Assertions.assertFalse(accrualTransactions.isEmpty(), "No accrual transactions were found for overdraft.");
+
+        Number firstTransactionIdNumber = (Number) accrualTransactions.get(0).get("id");
+        ArrayList<HashMap> journalEntries = journalEntryHelper.getJournalEntriesByTransactionId("S" + firstTransactionIdNumber.intValue());
+        Assertions.assertFalse(journalEntries.isEmpty(), "No journal entries found for negative accrual.");
+
+        boolean debitFound = false;
+        boolean creditFound = false;
+        for (Map<String, Object> entry : journalEntries) {
+            String entryType = (String) ((HashMap) entry.get("entryType")).get("value");
+            Integer accountId = ((Number) entry.get("glAccountId")).intValue();
+            if ("DEBIT".equals(entryType) && accountId.equals(interestReceivableAccount.getAccountID())) {
+                debitFound = true;
+            }
+            if ("CREDIT".equals(entryType) && accountId.equals(overdraftInterestIncomeAccount.getAccountID())) {
+                creditFound = true;
+            }
+        }
+
+        Assertions.assertTrue(debitFound, "DEBIT to Interest Receivable (Asset) Account not found for negative accrual.");
+        Assertions.assertTrue(creditFound, "CREDIT to Overdraft Interest Income Account not found for negative accrual.");
+
+        BigDecimal interest = getCalculateAccrualsForDay(productHelper, amount);
+
+        for (HashMap accrual : accrualTransactions) {
+            BigDecimal amountAccrualTransaccion = BigDecimal.valueOf((Double) accrual.get("amount"));
+            Assertions.assertEquals(interest, amountAccrualTransaccion);
+        }
+        LOG.info("VALIDATE AMOUNT AND ACCOUNT");
+    }
+
+    private List<HashMap> getAccrualTransactions(Integer savingsAccountId) {
+        List<HashMap> allTransactions = savingsAccountHelper.getSavingsTransactions(savingsAccountId);
+        List<HashMap> accrualTransactions = new ArrayList<>();
+        for (HashMap transaction : allTransactions) {
+            Map<String, Object> type = (Map<String, Object>) transaction.get("transactionType");
+            if (type != null && Boolean.TRUE.equals(type.get("accrual"))) {
+                accrualTransactions.add(transaction);
+            }
+        }
+        return accrualTransactions;
+    }
+
+    private BigDecimal getCalculateAccrualsForDay(SavingsProductHelper productHelper, String amount) {
+        BigDecimal interest = BigDecimal.ZERO;
+        BigDecimal interestRateAsFraction = productHelper.getNominalAnnualInterestRate().divide(new BigDecimal(100.00));
+        BigDecimal realBalanceForInterestCalculation = new BigDecimal(amount);
+
+        final BigDecimal multiplicand = BigDecimal.ONE.divide(productHelper.getInterestCalculationDaysInYearType(), MathContext.DECIMAL64);
+        final BigDecimal dailyInterestRate = interestRateAsFraction.multiply(multiplicand, MathContext.DECIMAL64);
+        final BigDecimal periodicInterestRate = dailyInterestRate.multiply(BigDecimal.valueOf(1), MathContext.DECIMAL64);
+        interest = realBalanceForInterestCalculation.multiply(periodicInterestRate, MathContext.DECIMAL64)
+                .setScale(productHelper.getDecimalCurrency(), RoundingMode.HALF_EVEN);
+
+        return interest;
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccrualIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccrualIntegrationTest.java
new file mode 100644
index 0000000..6dd1d04
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsAccrualIntegrationTest.java
@@ -0,0 +1,145 @@
+/**
+ * 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 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.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
+import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SavingsAccrualIntegrationTest {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SavingsAccrualIntegrationTest.class);
+    private ResponseSpecification responseSpec;
+    private RequestSpecification requestSpec;
+    private SavingsAccountHelper savingsAccountHelper;
+    private SchedulerJobHelper schedulerJobHelper;
+    private JournalEntryHelper journalEntryHelper;
+    private AccountHelper accountHelper;
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
+        this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec);
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec);
+        this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec);
+    }
+
+    @Test
+    public void testAccrualsAreGeneratedForTenDayPeriod() {
+        // --- ARRANGE ---
+
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account liabilityAccount = this.accountHelper.createLiabilityAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = this.accountHelper.createExpenseAccount();
+        final String interestRate = "10.0";
+        final int daysToTest = 10;
+
+        final SavingsProductHelper productHelper = new SavingsProductHelper().withInterestCompoundingPeriodTypeAsDaily()
+                .withInterestPostingPeriodTypeAsMonthly().withInterestCalculationPeriodTypeAsDailyBalance()
+                .withNominalAnnualInterestRate(new BigDecimal(interestRate))
+                .withAccountingRuleAsAccrualBased(new Account[] { assetAccount, liabilityAccount, incomeAccount, expenseAccount });
+
+        final Integer savingsProductId = SavingsProductHelper.createSavingsProduct(productHelper.build(), this.requestSpec,
+                this.responseSpec);
+        Assertions.assertNotNull(savingsProductId, "Error creating savings product.");
+
+        final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2020");
+        Assertions.assertNotNull(clientId, "Error creating client.");
+
+        final LocalDate startDate = LocalDate.now(Utils.getZoneIdOfTenant()).minusDays(daysToTest);
+        final String startDateString = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
+
+        final Integer savingsAccountId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, savingsProductId,
+                SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startDateString);
+        Assertions.assertNotNull(savingsAccountId, "Error applying for savings account.");
+
+        this.savingsAccountHelper.approveSavingsOnDate(savingsAccountId, startDateString);
+        this.savingsAccountHelper.activateSavings(savingsAccountId, startDateString);
+
+        final HashMap<String, Object> savingsStatus = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec,
+                savingsAccountId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatus);
+
+        this.savingsAccountHelper.depositToSavingsAccount(savingsAccountId, "10000", startDateString, CommonConstants.RESPONSE_RESOURCE_ID);
+
+        // --- ACT ---
+        schedulerJobHelper.executeAndAwaitJob("Add Accrual Transactions For Savings");
+
+        // --- ASSERT ---
+        List<HashMap> allTransactions = savingsAccountHelper.getSavingsTransactions(savingsAccountId);
+        List<HashMap> accrualTransactions = new ArrayList<>();
+        for (HashMap transaction : allTransactions) {
+            Map<String, Object> type = (Map<String, Object>) transaction.get("transactionType");
+            if (type != null && Boolean.TRUE.equals(type.get("accrual"))) {
+                accrualTransactions.add(transaction);
+            }
+        }
+        Assertions.assertFalse(accrualTransactions.isEmpty(), "No accrual transactions were found.");
+
+        long daysBetween = ChronoUnit.DAYS.between(startDate, LocalDate.now(Utils.getZoneIdOfTenant()));
+        long actualNumberOfTransactions = accrualTransactions.size();
+
+        Assertions.assertTrue(actualNumberOfTransactions >= daysBetween && actualNumberOfTransactions <= daysBetween + 1, "For a period of "
+                + daysBetween + " days, a close number of transactions was expected, but found " + actualNumberOfTransactions);
+
+        BigDecimal principal = new BigDecimal("10000");
+        BigDecimal rate = new BigDecimal(interestRate).divide(new BigDecimal(100));
+        BigDecimal daysInYear = new BigDecimal("365");
+
+        BigDecimal expectedTotalAccrual = principal.multiply(rate).divide(daysInYear, 8, RoundingMode.HALF_EVEN)
+                .multiply(new BigDecimal(actualNumberOfTransactions)).setScale(2, RoundingMode.HALF_EVEN);
+
+        BigDecimal actualTotalAccrual = savingsAccountHelper.getTotalAccrualAmount(savingsAccountId);
+
+        Assertions.assertEquals(0, expectedTotalAccrual.compareTo(actualTotalAccrual),
+                "The total accrual (" + actualTotalAccrual + ") does not match the expected (" + expectedTotalAccrual + ")");
+    }
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
index d400958..8434b98 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
@@ -50,6 +50,7 @@
 import org.apache.fineract.integrationtests.common.CommonConstants;
 import org.apache.fineract.integrationtests.common.FineractClientHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.junit.jupiter.api.Assertions;
@@ -1475,4 +1476,29 @@
         return Utils.performServerGet(requestSpec, responseSpec, url, "");
     }
 
+    public Integer createSavingsProductWithAccrualAccounting(final Account assetAccount, final Account liabilityAccount,
+            final Account incomeAccount, final Account expenseAccount, final String interestRate) {
+
+        SavingsProductHelper productHelper = new SavingsProductHelper();
+        final Account[] accountList = { assetAccount, liabilityAccount, incomeAccount, expenseAccount };
+
+        final String savingsProductJSON = productHelper.withInterestCompoundingPeriodTypeAsDaily().withInterestPostingPeriodTypeAsMonthly()
+                .withInterestCalculationPeriodTypeAsDailyBalance().withAccountingRuleAsAccrualBased(accountList)
+                .withNominalAnnualInterestRate(new BigDecimal(interestRate)).build();
+
+        return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec);
+    }
+
+    public BigDecimal getTotalAccrualAmount(Integer savingsId) {
+        List<HashMap> transactions = getSavingsTransactions(savingsId);
+        BigDecimal total = BigDecimal.ZERO;
+        for (HashMap tx : transactions) {
+            Map<String, Object> type = (Map<String, Object>) tx.get("transactionType");
+            if (type != null && Boolean.TRUE.equals(type.get("accrual"))) {
+                total = total.add(new BigDecimal(String.valueOf(tx.get("amount"))));
+            }
+        }
+        return total.setScale(2, java.math.RoundingMode.HALF_UP);
+    }
+
 }
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java
index 98bd7ea..2aa7725 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java
@@ -101,6 +101,7 @@
     private Boolean withgsimID = null;
     private Integer gsimID = null;
     private String nominalAnnualInterestRateOverdraft = null;
+    private String interestPayableAccountId;
     private String interestReceivableAccountId = null;
 
     // TODO: Rewrite to use fineract-client instead!
@@ -163,6 +164,14 @@
             map.put("daysToEscheat", this.daysToEscheat);
 
         }
+        if (this.accountingRule.equals(ACCRUAL_PERIODIC) && this.interestReceivableAccountId != null) {
+            map.put("interestReceivableAccountId", this.interestReceivableAccountId);
+        }
+        if (this.accountingRule.equals(ACCRUAL_PERIODIC)) {
+            if (this.interestReceivableAccountId != null) {
+                map.put("interestReceivableAccountId", this.interestReceivableAccountId);
+            }
+        }
 
         String savingsProductCreateJson = new Gson().toJson(map);
         LOG.info("{}", savingsProductCreateJson);
@@ -304,6 +313,58 @@
         return this;
     }
 
+    public SavingsProductHelper withSavingsReferenceAccountId(final String savingsReferenceAccountId) {
+        this.savingsReferenceAccountId = savingsReferenceAccountId;
+        return this;
+    }
+
+    public SavingsProductHelper withSavingsControlAccountId(final String savingsControlAccountId) {
+        this.savingsControlAccountId = savingsControlAccountId;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestOnSavingsAccountId(final String interestOnSavingsAccountId) {
+        this.interestOnSavingsAccountId = interestOnSavingsAccountId;
+        return this;
+    }
+
+    public SavingsProductHelper withIncomeFromFeeAccountId(final String incomeFromFeeAccountId) {
+        this.incomeFromFeeAccountId = incomeFromFeeAccountId;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestPayableAccountId(final String interestPayableAccountId) {
+        this.interestPayableAccountId = interestPayableAccountId;
+        return this;
+    }
+
+    public SavingsProductHelper withOverdraftPortfolioControlId(final String overdraftPortfolioControlId) {
+        this.overdraftPortfolioControlId = overdraftPortfolioControlId;
+        return this;
+    }
+
+    public SavingsProductHelper withInterestReceivableAccountId(final String interestReceivableAccountId) {
+        this.interestReceivableAccountId = interestReceivableAccountId;
+        return this;
+    }
+
+    public SavingsProductHelper withIncomeFromInterestId(final String incomeFromInterestId) {
+        this.incomeFromInterestId = incomeFromInterestId;
+        return this;
+    }
+
+    public BigDecimal getNominalAnnualInterestRate() {
+        return new BigDecimal(nominalAnnualInterestRate);
+    }
+
+    public BigDecimal getInterestCalculationDaysInYearType() {
+        return new BigDecimal(interestCalculationDaysInYearType);
+    }
+
+    public Integer getDecimalCurrency() {
+        return Integer.parseInt(DIGITS_AFTER_DECIMAL);
+    }
+
     // TODO: Rewrite to use fineract-client instead!
     // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
     // org.apache.fineract.client.models.PostLoansLoanIdRequest)