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 d782e65fc2 FINERACT-2376: Unable to re-invest maturity amount or 
principal for fixed deposit at closing
d782e65fc2 is described below

commit d782e65fc2c2217c295f7da60cb3fddc5acd9642
Author: sayhaed <[email protected]>
AuthorDate: Fri Sep 12 19:07:59 2025 -0600

    FINERACT-2376: Unable to re-invest maturity amount or principal for fixed 
deposit at closing
---
 .../domain/DepositAccountDomainServiceJpa.java     |   5 +-
 .../savings/domain/FixedDepositAccount.java        |   3 +
 .../savings/domain/RecurringDepositAccount.java    |   4 +-
 .../SavingsAccrualWritePlatformServiceImpl.java    |   5 +-
 .../portfolio/savings/domain/SavingsAccount.java   |   2 +-
 .../service/SavingsSchedularInterestPoster.java    |   5 +-
 .../integrationtests/FixedDepositTest.java         | 166 +++++++++++++++++++++
 .../SavingsInterestPostingTest.java                |  86 +++++++++++
 .../fixeddeposit/FixedDepositAccountHelper.java    |  11 ++
 .../fixeddeposit/FixedDepositProductHelper.java    |  44 ++++++
 10 files changed, 324 insertions(+), 7 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java
index 7fe875bedd..961d9a7480 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java
@@ -212,6 +212,7 @@ public class DepositAccountDomainServiceJpa implements 
DepositAccountDomainServi
         final LocalDate closedDate = 
command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName);
         Long savingsTransactionId = null;
         
account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, 
financialYearBeginningMonth);
+        account.setClosedOnDate(closedDate);
         final Integer onAccountClosureId = 
command.integerValueOfParameterNamed(onAccountClosureIdParamName);
         final DepositAccountOnClosureType onClosureType = 
DepositAccountOnClosureType.fromInt(onAccountClosureId);
         if (onClosureType.isReinvest()) {
@@ -272,6 +273,7 @@ public class DepositAccountDomainServiceJpa implements 
DepositAccountDomainServi
         final MathContext mc = MathContext.DECIMAL64;
         Long savingsTransactionId = null;
         
account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, 
financialYearBeginningMonth);
+        account.setClosedOnDate(closedDate);
         final DepositAccountOnClosureType onClosureType = 
DepositAccountOnClosureType.fromInt(onAccountClosureId);
         if (onClosureType.isReinvest()) {
             BigDecimal reInvestAmount;
@@ -367,10 +369,11 @@ public class DepositAccountDomainServiceJpa implements 
DepositAccountDomainServi
             this.calendarInstanceRepository.save(calendarInstance);
             final Calendar calendar = calendarInstance.getCalendar();
             final PeriodFrequencyType frequencyType = 
CalendarFrequencyType.from(CalendarUtils.getFrequency(calendar.getRecurrence()));
+            final Long relaxingDaysConfigForPivotDate = 
this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
             Integer frequency = 
CalendarUtils.getInterval(calendar.getRecurrence());
             frequency = frequency == -1 ? 1 : frequency;
             reinvestedDeposit.generateSchedule(frequencyType, frequency, 
calendar);
-            reinvestedDeposit.processAccountUponActivation(fmt, postReversals);
+            reinvestedDeposit.processAccountUponActivation(fmt, postReversals, 
relaxingDaysConfigForPivotDate);
             reinvestedDeposit.updateMaturityDateAndAmount(mc, 
isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
                     financialYearBeginningMonth);
             this.savingsAccountRepository.save(reinvestedDeposit);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
index 2855bc1e56..207d7bd55e 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java
@@ -882,4 +882,7 @@ public class FixedDepositAccount extends SavingsAccount {
         return SavingsAccountStatusType.MATURED.getValue().equals(this.status);
     }
 
+    public void setClosedOnDate(final LocalDate closedOnDate) {
+        this.closedOnDate = closedOnDate;
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
index 5eea23621b..fb064ab0e8 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java
@@ -536,11 +536,11 @@ public class RecurringDepositAccount extends 
SavingsAccount {
         return Money.of(this.currency, this.minRequiredOpeningBalance);
     }
 
-    protected void processAccountUponActivation(final DateTimeFormatter fmt, 
final boolean postReversals) {
+    protected void processAccountUponActivation(final DateTimeFormatter fmt, 
final boolean postReversals,
+            final Long relaxingDaysConfigForPivotDate) {
         final Money minRequiredOpeningBalance = Money.of(this.currency, 
this.minRequiredOpeningBalance);
         final boolean backdatedTxnsAllowedTill = false;
         String refNo = null;
-        final Long relaxingDaysConfigForPivotDate = 
this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
         if (minRequiredOpeningBalance.isGreaterThanZero()) {
             final SavingsAccountTransactionDTO transactionDTO = new 
SavingsAccountTransactionDTO(fmt, getActivationDate(),
                     minRequiredOpeningBalance.getAmount(), null, null, 
accountType);
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
index 8ee466cdd3..01309e69b3 100644
--- 
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
@@ -32,6 +32,7 @@ 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;
@@ -177,7 +178,9 @@ public class SavingsAccrualWritePlatformServiceImpl 
implements SavingsAccrualWri
                         savingsAccount.office(), 
period.getPeriodInterval().endDate(), period.getInterestEarned().abs(), false, 
refNo);
                 
savingsAccountTransaction.setRunningBalance(period.getClosingBalance());
                 
savingsAccountTransaction.setOverdraftAmount(period.getInterestEarned());
-                savingsAccount.addTransaction(savingsAccountTransaction);
+                if (!MathUtil.isZero(savingsAccountTransaction.getAmount())) {
+                    savingsAccount.addTransaction(savingsAccountTransaction);
+                }
             }
         }
 
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 50b2db3987..eb574a051a 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
@@ -1042,7 +1042,7 @@ public class SavingsAccount extends 
AbstractAuditableWithUTCDateTimeCustom<Long>
                 if (MathUtil.isEmpty(overdraftAmount) && 
runningBalance.isLessThanZero() && !transaction.isAmountOnHold()) {
                     overdraftAmount = runningBalance.negated();
                 }
-                if (!calculateInterest || transaction.getId() == null) {
+                if (!calculateInterest || transaction.getId() == null || 
transaction.getOverdraftAmount(this.currency).isZero()) {
                     transaction.setOverdraftAmount(overdraftAmount);
                 } else if (!MathUtil.isEqualTo(overdraftAmount, 
transaction.getOverdraftAmount(this.currency))
                         && !transaction.isAccrual()) {
diff --git 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
index 913fbdb31e..6928f730b6 100644
--- 
a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
+++ 
b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
@@ -36,6 +36,7 @@ import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
 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.infrastructure.security.service.PlatformSecurityContext;
 import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
@@ -106,7 +107,7 @@ public class SavingsSchedularInterestPoster {
 
             List<SavingsAccountTransactionData> 
savingsAccountTransactionDataList = 
savingsAccountData.getSavingsAccountTransactionData();
             for (SavingsAccountTransactionData savingsAccountTransactionData : 
savingsAccountTransactionDataList) {
-                if (savingsAccountTransactionData.getId() == null) {
+                if (savingsAccountTransactionData.getId() == null && 
!MathUtil.isZero(savingsAccountTransactionData.getAmount())) {
                     final String key = 
savingsAccountTransactionData.getRefNo();
                     final Boolean isOverdraft = 
savingsAccountTransactionData.getIsOverdraft();
                     final SavingsAccountTransactionData dataFromFetch = 
savingsAccountTransactionDataHashMap.get(key);
@@ -183,7 +184,7 @@ public class SavingsSchedularInterestPoster {
                     auditTime, userId, savingsAccountData.getId() });
             List<SavingsAccountTransactionData> 
savingsAccountTransactionDataList = 
savingsAccountData.getSavingsAccountTransactionData();
             for (SavingsAccountTransactionData savingsAccountTransactionData : 
savingsAccountTransactionDataList) {
-                if (savingsAccountTransactionData.getId() == null) {
+                if (savingsAccountTransactionData.getId() == null && 
!MathUtil.isZero(savingsAccountTransactionData.getAmount())) {
                     UUID uuid = UUID.randomUUID();
                     savingsAccountTransactionData.setRefNo(uuid.toString());
                     transRefNo.add(uuid.toString());
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
index 3162faff4c..a55e312cc5 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/FixedDepositTest.java
@@ -48,12 +48,17 @@ import java.util.Locale;
 import java.util.TimeZone;
 import lombok.extern.slf4j.Slf4j;
 import 
org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity;
+import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import 
org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
 import org.apache.fineract.infrastructure.core.api.JsonQuery;
 import 
org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.integrationtests.client.IntegrationTest;
+import org.apache.fineract.integrationtests.common.BusinessDateHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
 import org.apache.fineract.integrationtests.common.TaxComponentHelper;
 import org.apache.fineract.integrationtests.common.TaxGroupHelper;
@@ -92,6 +97,7 @@ public class FixedDepositTest extends IntegrationTest {
     private SavingsAccountHelper savingsAccountHelper;
     private JournalEntryHelper journalEntryHelper;
     private FinancialActivityAccountHelper financialActivityAccountHelper;
+    private GlobalConfigurationHelper globalConfigurationHelper;
 
     private FixedDepositAccountInterestCalculationServiceImpl 
fixedDepositAccountInterestCalculationServiceImpl;
 
@@ -108,12 +114,14 @@ public class FixedDepositTest extends IntegrationTest {
 
     private static final String NONE = "1";
     private static final String CASH_BASED = "2";
+    private static final String ACCRUAL = "3";
 
     public static final String MINIMUM_OPENING_BALANCE = "1000.0";
     public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
     public static final String CLOSURE_TYPE_WITHDRAW_DEPOSIT = "100";
     public static final String CLOSURE_TYPE_TRANSFER_TO_SAVINGS = "200";
     public static final String CLOSURE_TYPE_REINVEST = "300";
+    public static final String CLOSURE_TYPE_REINVEST_PRINCIPAL_ONLY = "400";
     public static final Integer DAILY_COMPOUNDING_INTERVAL = 0;
     public static final Integer MONTHLY_INTERVAL = 1;
     public static final Integer QUARTERLY_INTERVAL = 3;
@@ -130,6 +138,7 @@ public class FixedDepositTest extends IntegrationTest {
     public static final Float THRESHOLD = 1.0f;
 
     private MockedStatic<MoneyHelper> moneyHelperStatic;
+    private SchedulerJobHelper schedulerJobHelper;
 
     @BeforeEach
     public void setup() {
@@ -138,8 +147,11 @@ public class FixedDepositTest extends IntegrationTest {
         this.requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
         this.requestSpec.header("Fineract-Platform-TenantId", "default");
         this.responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+        this.accountHelper = new AccountHelper(this.requestSpec, 
this.responseSpec);
+        this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec);
         this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, 
this.responseSpec);
         this.financialActivityAccountHelper = new 
FinancialActivityAccountHelper(this.requestSpec);
+        this.globalConfigurationHelper = new GlobalConfigurationHelper();
         TimeZone.setDefault(TimeZone.getTimeZone(Utils.TENANT_TIME_ZONE));
     }
 
@@ -2598,6 +2610,158 @@ public class FixedDepositTest extends IntegrationTest {
         
FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap);
     }
 
+    @Test
+    public void 
testCloseFixedDepositForCLOSURE_TYPE_REINVEST_withConfigurationMaturityInstruction()
 {
+        try {
+            final Account assetAccount = 
this.accountHelper.createAssetAccount();
+            final Account liabilityAccount = 
this.accountHelper.createLiabilityAccount();
+            final Account incomeAccount = 
this.accountHelper.createIncomeAccount();
+            final Account expenseAccount = 
this.accountHelper.createExpenseAccount();
+
+            this.fixedDepositProductHelper = new 
FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+            this.fixedDepositAccountHelper = new 
FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+            DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", 
Locale.US);
+            DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", 
Locale.US);
+            DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+            Calendar todaysDate = Calendar.getInstance();
+            int currentYear = todaysDate.get(Calendar.YEAR);
+            todaysDate.set(currentYear, Calendar.JANUARY, 1);
+            final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+            todaysDate.set(currentYear + 1, Calendar.MARCH, 1);
+            final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+            todaysDate = Calendar.getInstance();
+            Integer currentDate = 
Integer.valueOf(currentDateFormat.format(todaysDate.getTime()));
+            todaysDate.set(currentYear, Calendar.JANUARY, 1);
+            final String SUBMITTED_ON_DATE = 
dateFormat.format(todaysDate.getTime());
+            final String APPROVED_ON_DATE = 
dateFormat.format(todaysDate.getTime());
+            dateFormat.format(todaysDate.getTime());
+            monthDayFormat.format(todaysDate.getTime());
+
+            
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
+                    new PutGlobalConfigurationsRequest().enabled(true));
+
+            LocalDate marchDate = LocalDate.of(currentYear + 1, 3, 1);
+            BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, marchDate);
+
+            log.info("Submitted Date: {}", SUBMITTED_ON_DATE);
+
+            final String accountingRule = ACCRUAL;
+            Integer fixedDepositProductId = 
createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount, 
liabilityAccount,
+                    incomeAccount, expenseAccount);
+            Assertions.assertNotNull(fixedDepositProductId);
+
+            Integer clientId = ClientHelper.createClient(this.requestSpec, 
this.responseSpec);
+            Assertions.assertNotNull(clientId);
+
+            Integer fixedDepositAccountId = 
applyForFixedDepositApplication(clientId.toString(), 
fixedDepositProductId.toString(),
+                    SUBMITTED_ON_DATE, WHOLE_TERM, 
Integer.valueOf(CLOSURE_TYPE_REINVEST));
+            Assertions.assertNotNull(fixedDepositAccountId);
+
+            
this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, 
APPROVED_ON_DATE);
+            
this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, 
APPROVED_ON_DATE);
+
+            schedulerJobHelper.executeAndAwaitJob("Update Deposit Accounts 
Maturity details");
+
+            HashMap fixedDepositAccountStatusHashMap = 
FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                    this.responseSpec, fixedDepositAccountId.toString());
+
+            
FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsClosed(fixedDepositAccountStatusHashMap);
+        } finally {
+            
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
+                    new PutGlobalConfigurationsRequest().enabled(false));
+        }
+
+    }
+
+    @Test
+    public void testCloseFixedDepositForCLOSURE_TYPE_REINVEST() {
+        testClosureTypeReinvestVariants(CLOSURE_TYPE_REINVEST);
+    }
+
+    @Test
+    public void testCloseFixedDepositForCLOSURE_TYPE_REINVEST_PRINCIPAL_ONLY() 
{
+        testClosureTypeReinvestVariants(CLOSURE_TYPE_REINVEST_PRINCIPAL_ONLY);
+    }
+
+    public void testClosureTypeReinvestVariants(String reInvest) {
+        try {
+            final Account assetAccount = 
this.accountHelper.createAssetAccount();
+            final Account liabilityAccount = 
this.accountHelper.createLiabilityAccount();
+            final Account incomeAccount = 
this.accountHelper.createIncomeAccount();
+            final Account expenseAccount = 
this.accountHelper.createExpenseAccount();
+
+            this.fixedDepositProductHelper = new 
FixedDepositProductHelper(this.requestSpec, this.responseSpec);
+            this.fixedDepositAccountHelper = new 
FixedDepositAccountHelper(this.requestSpec, this.responseSpec);
+
+            DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", 
Locale.US);
+            DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", 
Locale.US);
+            DateFormat currentDateFormat = new SimpleDateFormat("dd");
+
+            Calendar todaysDate = Calendar.getInstance();
+            int currentYear = todaysDate.get(Calendar.YEAR);
+            todaysDate.set(currentYear, Calendar.JANUARY, 1);
+            final String VALID_FROM = dateFormat.format(todaysDate.getTime());
+            todaysDate.set(currentYear + 1, Calendar.JANUARY, 1);
+            final String VALID_TO = dateFormat.format(todaysDate.getTime());
+
+            todaysDate = Calendar.getInstance();
+            Integer currentDate = 
Integer.valueOf(currentDateFormat.format(todaysDate.getTime()));
+            todaysDate.set(currentYear, Calendar.JANUARY, 1);
+            final String SUBMITTED_ON_DATE = 
dateFormat.format(todaysDate.getTime());
+            final String APPROVED_ON_DATE = 
dateFormat.format(todaysDate.getTime());
+            dateFormat.format(todaysDate.getTime());
+            monthDayFormat.format(todaysDate.getTime());
+
+            
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
+                    new PutGlobalConfigurationsRequest().enabled(true));
+
+            LocalDate marchDate = LocalDate.of(currentYear + 1, 1, 1);
+            BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, marchDate);
+
+            log.info("Submitted Date: {}", SUBMITTED_ON_DATE);
+
+            final String accountingRule = ACCRUAL;
+            Integer fixedDepositProductId = 
createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount, 
liabilityAccount,
+                    incomeAccount, expenseAccount);
+            Assertions.assertNotNull(fixedDepositProductId);
+
+            Integer clientId = ClientHelper.createClient(this.requestSpec, 
this.responseSpec);
+            Assertions.assertNotNull(clientId);
+
+            Integer fixedDepositAccountId = 
applyForFixedDepositApplication(clientId.toString(), 
fixedDepositProductId.toString(),
+                    SUBMITTED_ON_DATE, WHOLE_TERM, "10000", "12");
+            Assertions.assertNotNull(fixedDepositAccountId);
+
+            
this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, 
APPROVED_ON_DATE);
+            
this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, 
APPROVED_ON_DATE);
+
+            schedulerJobHelper.executeAndAwaitJob("Update Deposit Accounts 
Maturity details");
+
+            HashMap fixedDepositAccountStatusHashMap = 
FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                    this.responseSpec, fixedDepositAccountId.toString());
+
+            
FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsMatured(fixedDepositAccountStatusHashMap);
+
+            todaysDate.set(currentYear + 1, Calendar.JANUARY, 1);
+            final String CLOSED_ON_DATE = 
dateFormat.format(todaysDate.getTime());
+
+            Integer prematureClosureTransactionId = (Integer) 
this.fixedDepositAccountHelper.closeForFixedDeposit(fixedDepositAccountId,
+                    CLOSED_ON_DATE, reInvest, null, 
CommonConstants.RESPONSE_RESOURCE_ID);
+
+            fixedDepositAccountStatusHashMap = 
FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec,
+                    this.responseSpec, fixedDepositAccountId.toString());
+
+            
FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsClosed(fixedDepositAccountStatusHashMap);
+        } finally {
+            
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
+                    new PutGlobalConfigurationsRequest().enabled(false));
+        }
+
+    }
+
     private Integer createFixedDepositProduct(final String validFrom, final 
String validTo, final String accountingRule,
             Account... accounts) {
         log.info("------------------------------CREATING NEW FIXED DEPOSIT 
PRODUCT ---------------------------------------");
@@ -2606,6 +2770,8 @@ public class FixedDepositTest extends IntegrationTest {
             fixedDepositProductHelper = 
fixedDepositProductHelper.withAccountingRuleAsCashBased(accounts);
         } else if (accountingRule.equals(NONE)) {
             fixedDepositProductHelper = 
fixedDepositProductHelper.withAccountingRuleAsNone();
+        } else if (accountingRule.equals(ACCRUAL)) {
+            fixedDepositProductHelper = 
fixedDepositProductHelper.withAccountingRuleAsAccrual(accounts);
         }
         final String fixedDepositProductJSON = 
fixedDepositProductHelper.withPeriodRangeChart() //
                 .build(validFrom, validTo, true);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingTest.java
index efdc69d2bf..a8e7a15e53 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingTest.java
@@ -340,6 +340,92 @@ public class SavingsInterestPostingTest {
         }
     }
 
+    @Test
+    public void testPostInterestNotZero() {
+        try {
+            final String amountDeposit = "1000";
+            final String amountWithdrawal = "1000";
+
+            final Account assetAccount = accountHelper.createAssetAccount();
+            final Account incomeAccount = accountHelper.createIncomeAccount();
+            final Account expenseAccount = 
accountHelper.createExpenseAccount();
+            final Account liabilityAccount = 
accountHelper.createLiabilityAccount();
+            final Account interestReceivableAccount = 
accountHelper.createAssetAccount("interestReceivableAccount");
+            final Account savingsControlAccount = 
accountHelper.createLiabilityAccount("Savings Control");
+            final Account interestPayableAccount = 
accountHelper.createLiabilityAccount("Interest Payable");
+
+            final Integer productId = 
createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed(
+                    interestPayableAccount.getAccountID().toString(), 
savingsControlAccount.getAccountID().toString(),
+                    interestReceivableAccount.getAccountID().toString(), 
assetAccount, incomeAccount, expenseAccount, liabilityAccount);
+
+            final Integer clientId = ClientHelper.createClient(requestSpec, 
responseSpec, "01 January 2025");
+            final LocalDate startDate = 
LocalDate.of(LocalDate.now(Utils.getZoneIdOfTenant()).getYear(), 1, 1);
+            final String startStr = DateTimeFormatter.ofPattern("dd MMMM 
yyyy", Locale.US).format(startDate);
+
+            final Integer accountId = 
savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, productId,
+                    SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startStr);
+            savingsAccountHelper.approveSavingsOnDate(accountId, startStr);
+            savingsAccountHelper.activateSavings(accountId, startStr);
+            savingsAccountHelper.depositToSavingsAccount(accountId, 
amountDeposit, startStr, CommonConstants.RESPONSE_RESOURCE_ID);
+
+            
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
+                    new PutGlobalConfigurationsRequest().enabled(true));
+            LocalDate februaryDate = LocalDate.of(startDate.getYear(), 2, 1);
+            BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, februaryDate);
+
+            schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
+
+            List<HashMap> txsFebruary = getInterestTransactions(accountId); // 
OBTENER EL POSTEO DEL INTEREST
+
+            long daysFebruary = ChronoUnit.DAYS.between(startDate, 
februaryDate);
+            BigDecimal expectedFebruary = calcInterestPosting(productHelper, 
amountDeposit, daysFebruary);
+            Assertions.assertEquals(expectedFebruary, 
BigDecimal.valueOf(((Double) txsFebruary.get(0).get("amount"))));
+
+            final LocalDate withdrawalDate = LocalDate.of(startDate.getYear(), 
2, 1);
+            final String withdrawal = DateTimeFormatter.ofPattern("dd MMMM 
yyyy", Locale.US).format(withdrawalDate);
+
+            BigDecimal runningBalance = new 
BigDecimal(txsFebruary.get(0).get("runningBalance").toString());
+            String withdrawalRunning = runningBalance.setScale(2, 
RoundingMode.HALF_UP).toString();
+
+            savingsAccountHelper.withdrawalFromSavingsAccount(accountId, 
withdrawalRunning, withdrawal,
+                    CommonConstants.RESPONSE_RESOURCE_ID);
+            savingsAccountHelper.withdrawalFromSavingsAccount(accountId, 
amountWithdrawal, withdrawal,
+                    CommonConstants.RESPONSE_RESOURCE_ID);
+
+            LocalDate marchDate = LocalDate.of(startDate.getYear(), 3, 1);
+            BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, 
BusinessDateType.BUSINESS_DATE, marchDate);
+
+            schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
+
+            List<HashMap> txs = getInterestTransactions(accountId); // CON 
ESTE DEBEMOS DE VALIDAR QUE EL DIA DE MARZO
+                                                                    // NO SE 
TENGA POSTEO EN CERO
+            for (HashMap tx : txs) {
+                BigDecimal amt = BigDecimal.valueOf(((Double) 
tx.get("amount")));
+                @SuppressWarnings("unchecked")
+                Map<String, Object> typeMap = (Map<String, Object>) 
tx.get("transactionType");
+                SavingsAccountTransactionType type = 
SavingsAccountTransactionType.fromInt(((Double) typeMap.get("id")).intValue());
+                if (type.isOverDraftInterestPosting()) {
+                    long days = ChronoUnit.DAYS.between(withdrawalDate, 
marchDate);
+                    BigDecimal decimalsss = new 
BigDecimal(txsFebruary.get(0).get("runningBalance").toString())
+                            .subtract(runningBalance.setScale(2, 
RoundingMode.HALF_UP));
+                    BigDecimal withdraw = new BigDecimal(amountWithdrawal);
+                    BigDecimal res = withdraw.subtract(decimalsss);
+                    BigDecimal expected = calcOverdraftPosting(productHelper, 
res.toString(), days);
+                    Assertions.assertEquals(expected, amt);
+                }
+            }
+
+            Assertions.assertEquals(0L, countInterestOnDate(accountId, 
marchDate), "Expected exactly one INTEREST posting on posting date");
+            Assertions.assertEquals(1L, countOverdraftOnDate(accountId, 
marchDate),
+                    "Expected exactly one OVERDRAFT posting on posting date");
+
+            assertNoAccrualReversals(accountId);
+        } finally {
+            
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
+                    new PutGlobalConfigurationsRequest().enabled(false));
+        }
+    }
+
     private List<HashMap> getInterestTransactions(Integer savingsAccountId) {
         List<HashMap> all = 
savingsAccountHelper.getSavingsTransactions(savingsAccountId);
         List<HashMap> filtered = new ArrayList<>();
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
index 68a29fc49c..9c84b4f645 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java
@@ -402,6 +402,17 @@ public class FixedDepositAccountHelper {
                 getPrematureCloseForFixedDepositAccountAsJSON(closedOnDate, 
closureType, toSavingsId), jsonAttributeToGetBack);
     }
 
+    // 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)
+    @Deprecated(forRemoval = true)
+    public Object closeForFixedDeposit(final Integer fixedDepositAccountId, 
final String closedOnDate, final String closureType,
+            final Integer toSavingsId, final String jsonAttributeToGetBack) {
+        LOG.info("--------------------- CLOSE FOR FIXED DEPOSIT 
----------------------------");
+        return 
performFixedDepositActions(createFixedDepositCalculateInterestURL(CLOSE_FIXED_DEPOSIT_COMMAND,
 fixedDepositAccountId),
+                getPrematureCloseForFixedDepositAccountAsJSON(closedOnDate, 
closureType, toSavingsId), jsonAttributeToGetBack);
+    }
+
     // 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)
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java
index 20cf1a91ea..3d7af6583a 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java
@@ -163,6 +163,9 @@ public class FixedDepositProductHelper {
         if (this.accountingRule.equals(CASH_BASED)) {
             map.putAll(getAccountMappingForCashBased());
         }
+        if (this.accountingRule.equals(ACCRUAL_PERIODIC)) {
+            map.putAll(getAccountMappingForAccrualBased());
+        }
 
         String FixedDepositProductCreateJson = new Gson().toJson(map);
         LOG.info("{}", FixedDepositProductCreateJson);
@@ -397,6 +400,12 @@ public class FixedDepositProductHelper {
         return this;
     }
 
+    public FixedDepositProductHelper withAccountingRuleAsAccrual(final 
Account[] account_list) {
+        this.accountingRule = ACCRUAL_PERIODIC;
+        this.accountList = account_list;
+        return this;
+    }
+
     public FixedDepositProductHelper withPeriodRangeChart() {
         this.chartSlabs = constructChartSlabWithPeriodRange();
         return this;
@@ -456,6 +465,41 @@ public class FixedDepositProductHelper {
         return map;
     }
 
+    // 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)
+    @Deprecated(forRemoval = true)
+    private Map<String, String> getAccountMappingForAccrualBased() {
+        final Map<String, String> map = new HashMap<>();
+        if (accountList != null) {
+            for (int i = 0; i < this.accountList.length; i++) {
+                if 
(this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) {
+                    final String ID = 
this.accountList[i].getAccountID().toString();
+                    map.put("savingsReferenceAccountId", ID);
+                    map.put("feesReceivableAccountId", ID);
+                    map.put("penaltiesReceivableAccountId", ID);
+                }
+                if 
(this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) {
+
+                    final String ID = 
this.accountList[i].getAccountID().toString();
+                    map.put("savingsControlAccountId", ID);
+                    map.put("transfersInSuspenseAccountId", ID);
+                    map.put("interestPayableAccountId", ID);
+                }
+                if 
(this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) {
+                    final String ID = 
this.accountList[i].getAccountID().toString();
+                    map.put("interestOnSavingsAccountId", ID);
+                }
+                if 
(this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) {
+                    final String ID = 
this.accountList[i].getAccountID().toString();
+                    map.put("incomeFromFeeAccountId", ID);
+                    map.put("incomeFromPenaltyAccountId", ID);
+                }
+            }
+        }
+        return map;
+    }
+
     // 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)


Reply via email to