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 86bd2763f Fineract-1964: Add functionality to calculate maturity 
amount before creating FD account
86bd2763f is described below

commit 86bd2763f0c249d11e9f443720a9229f6c762d83
Author: goyalrocks007 <[email protected]>
AuthorDate: Fri Mar 8 20:07:02 2024 +0530

    Fineract-1964: Add functionality to calculate maturity amount before 
creating FD account
---
 .../portfolio/savings/DepositsApiConstants.java    | 19 ++++-
 .../api/FixedDepositAccountsApiResource.java       | 30 +++++++
 .../FixedDepositAccountsApiResourceSwagger.java    | 31 +++++++
 .../savings/data/DepositAccountDataValidator.java  | 56 +++++++++++++
 ...edDepositAccountInterestCalculationService.java | 28 +++++++
 ...positAccountInterestCalculationServiceImpl.java | 74 +++++++++++++++++
 ...tAccountInterestCalculationServiceImplTest.java | 85 +++++++++++++++++++
 .../integrationtests/FixedDepositTest.java         | 94 ++++++++++++++++++++++
 8 files changed, 416 insertions(+), 1 deletion(-)

diff --git 
a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
index 166560395..5419a7902 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java
@@ -112,6 +112,11 @@ public final class DepositsApiConstants {
     public static final String routingCodeParamName = "routingCode";
     public static final String receiptNumberParamName = "receiptNumber";
     public static final String bankNumberParamName = "bankNumber";
+    public static final String principalAmountParamName = "principalAmount";
+    public static final String annualInterestRateParamName = 
"annualInterestRate";
+    public static final String interestPostingPeriodInMonthsParamName = 
"interestPostingPeriodInMonths";
+    public static final String tenureInMonthsParamName = "tenureInMonths";
+    public static final String interestCompoundingPeriodInMonthsParamName = 
"interestCompoundingPeriodInMonths";
 
     // Preclosure parameters
     public static final String preClosurePenalApplicableParamName = 
"preClosurePenalApplicable";
@@ -307,10 +312,22 @@ public final class DepositsApiConstants {
 
     public static final Set<String> 
FIXED_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = 
fixedDepositAccountRequestData();
     public static final Set<String> 
FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = 
fixedDepositAccountResponseData();
-
+    public static final Set<String> 
FIXED_DEPOSIT_ACCOUNT_INTEREST_CALCULATION_PARAMETERS = 
fixedDepositInterestCalculationData();
     public static final Set<String> 
RECURRING_DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS = 
recurringDepositAccountRequestData();
     public static final Set<String> 
RECURRING_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS = 
recurringDepositAccountResponseData();
 
+    private static Set<String> fixedDepositInterestCalculationData() {
+        final Set<String> fixedDepositInterestCalculationData = new 
HashSet<>();
+        fixedDepositInterestCalculationData.add(principalAmountParamName);
+        fixedDepositInterestCalculationData.add(annualInterestRateParamName);
+        fixedDepositInterestCalculationData.add(tenureInMonthsParamName);
+        
fixedDepositInterestCalculationData.add(interestPostingPeriodInMonthsParamName);
+        
fixedDepositInterestCalculationData.add(interestCompoundingPeriodInMonthsParamName);
+
+        return fixedDepositInterestCalculationData;
+
+    }
+
     private static Set<String> fixedDepositAccountRequestData() {
         final Set<String> fixedDepositRequestData = new HashSet<>();
         
fixedDepositRequestData.addAll(DEPOSIT_ACCOUNT_REQUEST_DATA_PARAMETERS);
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java
index e202dd54e..3c1479555 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResource.java
@@ -46,6 +46,7 @@ import java.io.InputStream;
 import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Set;
 import lombok.RequiredArgsConstructor;
@@ -79,6 +80,7 @@ import 
org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
 import 
org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
 import 
org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService;
 import 
org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
+import 
org.apache.fineract.portfolio.savings.service.FixedDepositAccountInterestCalculationService;
 import 
org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
 import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
 import org.glassfish.jersey.media.multipart.FormDataParam;
@@ -102,6 +104,7 @@ public class FixedDepositAccountsApiResource {
     private final AccountAssociationsReadPlatformService 
accountAssociationsReadPlatformService;
     private final BulkImportWorkbookService bulkImportWorkbookService;
     private final BulkImportWorkbookPopulatorService 
bulkImportWorkbookPopulatorService;
+    private final FixedDepositAccountInterestCalculationService 
fixedDepositAccountInterestCalculationService;
 
     @GET
     @Path("template")
@@ -208,6 +211,33 @@ public class FixedDepositAccountsApiResource {
                 
DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESPONSE_DATA_PARAMETERS);
     }
 
+    @GET
+    @Path("calculate-fd-interest")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
FixedDepositAccountsApiResourceSwagger.CalculateFixedDepositInterestResponse.class)))
 })
+    public String calculateFixedDepositInterest(@Context final UriInfo uriInfo,
+            @QueryParam("principalAmount") @Parameter(description = 
"BigDecimal principalAmount") final BigDecimal principalAmount,
+            @QueryParam("annualInterestRate") @Parameter(description = 
"annualInterestRate") final BigDecimal annualInterestRate,
+            @QueryParam("tenureInMonths") @Parameter(description = 
"tenureInMonths") final Long tenureInMonths,
+            @QueryParam("interestCompoundingPeriodInMonths") 
@Parameter(description = "interestCompoundingPeriodInMonths") final Long 
interestCompoundingPeriodInMonths,
+            @QueryParam("interestPostingPeriodInMonths") 
@Parameter(description = "interestPostingPeriodInMonths") final Long 
interestPostingPeriodInMonths) {
+        HashMap request = new HashMap<>();
+        request.put("annualInterestRate", annualInterestRate);
+        request.put("tenureInMonths", tenureInMonths);
+        request.put("interestCompoundingPeriodInMonths", 
interestCompoundingPeriodInMonths);
+        request.put("interestPostingPeriodInMonths", 
interestPostingPeriodInMonths);
+        request.put("principalAmount", principalAmount);
+        String apiRequestBodyAsJson = toApiJsonSerializer.serialize(request);
+        JsonElement jsonElement = fromJsonHelper.parse(apiRequestBodyAsJson);
+        HashMap result = fixedDepositAccountInterestCalculationService
+                .calculateInterest(new JsonQuery(apiRequestBodyAsJson, 
jsonElement, fromJsonHelper));
+
+        return toApiJsonSerializer.serializeResult(result);
+
+    }
+
     private BigDecimal getActivationCharge(Long accountId) {
         BigDecimal activationCharge = BigDecimal.ZERO;
         Collection<SavingsAccountChargeData> savingCharges = 
this.savingsAccountChargeReadPlatformService
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java
index 3249c2b81..74a130cac 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositAccountsApiResourceSwagger.java
@@ -19,6 +19,7 @@
 package org.apache.fineract.portfolio.savings.api;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.Set;
 
@@ -256,6 +257,36 @@ final class FixedDepositAccountsApiResourceSwagger {
         public GetFixedDepositAccountsDepositPeriodFrequency 
depositPeriodFrequency;
     }
 
+    @Schema(description = "CalculateFixedDepositInterestRequest")
+    public static final class CalculateFixedDepositInterestRequest {
+
+        private CalculateFixedDepositInterestRequest() {}
+
+        @Schema(example = "10000")
+        public BigDecimal principalAmount;
+        @Schema(example = "5")
+        public BigDecimal annualInterestRate;
+        @Schema(example = "12")
+        public Long tenureInMonths;
+        @Schema(example = "3")
+        public Long interestPostingPeriodInMonths;
+        @Schema(example = "1")
+        public Long interestCompoundingPeriodInMonths;
+
+    }
+
+    @Schema(description = "CalculateFixedDepositInterestResponse")
+    public static final class CalculateFixedDepositInterestResponse {
+
+        private CalculateFixedDepositInterestResponse() {}
+
+        @Schema(example = "10511.61")
+        public BigDecimal maturityAmount;
+
+        @Schema(example = "Accuracy Warning")
+        public String warning;
+    }
+
     @Schema(description = "PostFixedDepositAccountsRequest")
     public static final class PostFixedDepositAccountsRequest {
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
index a28eab439..806036a98 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositAccountDataValidator.java
@@ -20,11 +20,14 @@ package org.apache.fineract.portfolio.savings.data;
 
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.adjustAdvanceTowardsFuturePaymentsParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.allowWithdrawalParamName;
+import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.annualInterestRateParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodFrequencyIdParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.depositPeriodParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.inMultiplesOfDepositTermTypeIdParamName;
+import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.interestCompoundingPeriodInMonthsParamName;
+import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.interestPostingPeriodInMonthsParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.isCalendarInheritedParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.isMandatoryDepositParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.linkedAccountParamName;
@@ -37,8 +40,10 @@ import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.minDepo
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalApplicableParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestOnTypeIdParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.preClosurePenalInterestParamName;
+import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.principalAmountParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.recurringFrequencyTypeParamName;
+import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInMonthsParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
 import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.transferToSavingsIdParamName;
 import static 
org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName;
@@ -201,6 +206,23 @@ public class DepositAccountDataValidator {
 
     }
 
+    public void validateFixedDepositForInterestCalculation(final String json) {
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() 
{}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
+                
DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_INTEREST_CALCULATION_PARAMETERS);
+        final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
+        final DataValidatorBuilder baseDataValidator = new 
DataValidatorBuilder(dataValidationErrors)
+                
.resource(DepositsApiConstants.FIXED_DEPOSIT_ACCOUNT_RESOURCE_NAME);
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+
+        validateForInterestCalc(element, baseDataValidator);
+        throwExceptionIfValidationWarningsExist(dataValidationErrors);
+
+    }
+
     private void validateDepositDetailsForSubmit(final JsonElement element, 
final DataValidatorBuilder baseDataValidator) {
 
         final Long clientId = 
this.fromApiJsonHelper.extractLongNamed(clientIdParamName, element);
@@ -582,6 +604,40 @@ public class DepositAccountDataValidator {
         }
     }
 
+    private void validateForInterestCalc(final JsonElement element, final 
DataValidatorBuilder baseDataValidator) {
+
+        BigDecimal principalAmount = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalAmountParamName,
 element);
+        
baseDataValidator.reset().parameter(principalAmountParamName).value(principalAmount).notNull().positiveAmount();
+
+        BigDecimal annualInterestRate = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualInterestRateParamName,
 element);
+        
baseDataValidator.reset().parameter(annualInterestRateParamName).value(annualInterestRate).notNull()
+                .notLessThanMin(BigDecimal.valueOf(0));
+
+        Long tenureInMonths = 
this.fromApiJsonHelper.extractLongNamed(tenureInMonthsParamName, element);
+        
baseDataValidator.reset().parameter(tenureInMonthsParamName).value(tenureInMonths).notNull().longGreaterThanZero();
+
+        Long interestPostingPeriodInMonths = 
this.fromApiJsonHelper.extractLongNamed(interestPostingPeriodInMonthsParamName, 
element);
+        
baseDataValidator.reset().parameter(interestPostingPeriodInMonthsParamName).value(interestPostingPeriodInMonths).notNull()
+                .longGreaterThanZero();
+
+        Long interestCompoundingPeriodInMonths = 
this.fromApiJsonHelper.extractLongNamed(interestCompoundingPeriodInMonthsParamName,
+                element);
+        
baseDataValidator.reset().parameter(interestCompoundingPeriodInMonthsParamName).value(interestCompoundingPeriodInMonths).notNull();
+
+        List<Long> allowedValues = List.of(1L, 2L, 3L, 4L, 6L, 12L);
+        if (interestCompoundingPeriodInMonths != null && 
!allowedValues.contains(interestCompoundingPeriodInMonths)) {
+            
baseDataValidator.reset().parameter(interestCompoundingPeriodInMonthsParamName).value(interestCompoundingPeriodInMonths)
+                    .failWithCode("parameter.value.error", 
"interestCompoundingPeriodInMonths can only be one {1,2,3,4,6,12}");
+        }
+        if (interestCompoundingPeriodInMonths != null && tenureInMonths != null
+                && tenureInMonths % interestCompoundingPeriodInMonths != 0) {
+            
baseDataValidator.reset().parameter(interestCompoundingPeriodInMonthsParamName).value(interestCompoundingPeriodInMonths)
+                    .failWithCode("parameter.relational.validation",
+                            "tenureInMonths must be perfectly divisible by 
interestCompoundingPeriodInMonths");
+        }
+
+    }
+
     private void validateRecurringDetailForSubmit(final JsonElement element, 
final DataValidatorBuilder baseDataValidator) {
 
         final BigDecimal mandatoryRecommendedDepositAmount = fromApiJsonHelper
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationService.java
new file mode 100644
index 000000000..5bfd07ab8
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationService.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.util.HashMap;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+
+public interface FixedDepositAccountInterestCalculationService {
+
+    HashMap calculateInterest(JsonQuery query);
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImpl.java
new file mode 100644
index 000000000..56c786224
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImpl.java
@@ -0,0 +1,74 @@
+/**
+ * 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 static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.annualInterestRateParamName;
+import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.interestCompoundingPeriodInMonthsParamName;
+import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.principalAmountParamName;
+import static 
org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInMonthsParamName;
+
+import com.google.gson.JsonElement;
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.util.HashMap;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.api.JsonQuery;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.savings.data.DepositAccountDataValidator;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class FixedDepositAccountInterestCalculationServiceImpl implements 
FixedDepositAccountInterestCalculationService {
+
+    private final DepositAccountDataValidator depositAccountDataValidator;
+    private final FromJsonHelper fromApiJsonHelper;
+
+    @Override
+    public HashMap calculateInterest(JsonQuery query) {
+        
depositAccountDataValidator.validateFixedDepositForInterestCalculation(query.json());
+        JsonElement element = query.parsedJson();
+        BigDecimal principalAmount = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(principalAmountParamName,
 element);
+        BigDecimal annualInterestRate = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualInterestRateParamName,
 element);
+        Long tenureInMonths = 
this.fromApiJsonHelper.extractLongNamed(tenureInMonthsParamName, element);
+        Long interestCompoundingPeriodInMonths = 
this.fromApiJsonHelper.extractLongNamed(interestCompoundingPeriodInMonthsParamName,
+                element);
+        BigDecimal maturityAmount = 
this.calculateInterestInternal(principalAmount, annualInterestRate, 
tenureInMonths,
+                interestCompoundingPeriodInMonths);
+        String warning = "This is an approximate calculated amount - it may 
vary slightly when the account is created";
+
+        HashMap result = new HashMap<>();
+        result.put("maturityAmount", maturityAmount);
+        result.put("warning", warning);
+
+        return result;
+    }
+
+    public BigDecimal calculateInterestInternal(BigDecimal principalAmount, 
BigDecimal annualInterestRate, Long tenureInMonths,
+            Long interestCompoundingPeriodInMonths) {
+        BigDecimal numberOfCompoundingsPerAnnum = 
BigDecimal.valueOf(12).divide(BigDecimal.valueOf(interestCompoundingPeriodInMonths));
+        Long totalNumberOfCompoundings = tenureInMonths / 
interestCompoundingPeriodInMonths;
+        MathContext mc = MoneyHelper.getMathContext();
+        BigDecimal exponentialTerm = 
annualInterestRate.divide(numberOfCompoundingsPerAnnum, 
mc).divide(BigDecimal.valueOf(100), mc)
+                
.add(BigDecimal.valueOf(1)).pow(Math.toIntExact(totalNumberOfCompoundings));
+        return principalAmount.multiply(exponentialTerm);
+    }
+}
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImplTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImplTest.java
new file mode 100644
index 000000000..943b46026
--- /dev/null
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/savings/service/FixedDepositAccountInterestCalculationServiceImplTest.java
@@ -0,0 +1,85 @@
+/**
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.math.BigDecimal;
+import java.math.MathContext;
+import java.math.RoundingMode;
+import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.savings.data.DepositAccountDataValidator;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class FixedDepositAccountInterestCalculationServiceImplTest {
+
+    private FixedDepositAccountInterestCalculationServiceImpl service;
+
+    @Mock
+    private DepositAccountDataValidator depositAccountDataValidator;
+    @Mock
+    private FromJsonHelper fromApiJsonHelper;
+    private MockedStatic<MoneyHelper> moneyHelperStatic;
+
+    @BeforeEach
+    public void setUp() {
+        moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
+        moneyHelperStatic.when(() -> 
MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
+        service = new 
FixedDepositAccountInterestCalculationServiceImpl(depositAccountDataValidator, 
fromApiJsonHelper);
+    }
+
+    @AfterEach
+    public void deregister() {
+        moneyHelperStatic.close();
+    }
+
+    @Test
+    public void testCalculateInterestInternal1() {
+
+        // Calculate interest
+        BigDecimal expectedInterest = new 
BigDecimal("10509.4533691406250000"); // Expected interest calculated based
+                                                                               
 // on provided values
+        BigDecimal calculatedInterest = 
service.calculateInterestInternal(BigDecimal.valueOf(10000), 
BigDecimal.valueOf(5), 12L, 3L);
+
+        // Verify the result
+        assertEquals(expectedInterest, calculatedInterest);
+    }
+
+    @Test
+    public void testCalculateInterestInternal2() {
+
+        // Calculate interest
+        BigDecimal expectedInterest = new BigDecimal("105.062500"); // 
Expected interest calculated based on provided
+                                                                    // values
+        BigDecimal calculatedInterest = 
service.calculateInterestInternal(BigDecimal.valueOf(100), 
BigDecimal.valueOf(5), 12L, 6L);
+
+        // Verify the result
+        assertEquals(expectedInterest, calculatedInterest);
+    }
+
+}
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 69741dcb5..51ce8d99d 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
@@ -20,14 +20,19 @@ package org.apache.fineract.integrationtests;
 
 import static java.time.temporal.ChronoUnit.DAYS;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import com.google.common.truth.Truth;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
 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.text.DateFormat;
 import java.text.SimpleDateFormat;
@@ -44,6 +49,9 @@ 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.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.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CommonConstants;
 import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
@@ -62,10 +70,15 @@ import 
org.apache.fineract.integrationtests.common.fixeddeposit.FixedDepositProd
 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.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.savings.data.DepositAccountDataValidator;
+import 
org.apache.fineract.portfolio.savings.service.FixedDepositAccountInterestCalculationServiceImpl;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
 
 @Slf4j
 @SuppressWarnings({ "unused", "unchecked", "rawtypes", "static-access" })
@@ -80,6 +93,8 @@ public class FixedDepositTest {
     private JournalEntryHelper journalEntryHelper;
     private FinancialActivityAccountHelper financialActivityAccountHelper;
 
+    private FixedDepositAccountInterestCalculationServiceImpl 
fixedDepositAccountInterestCalculationServiceImpl;
+
     public static final String WHOLE_TERM = "1";
     public static final String TILL_PREMATURE_WITHDRAWAL = "2";
     private static final String DAILY = "1";
@@ -114,6 +129,8 @@ public class FixedDepositTest {
     // and then to compare the exact results
     public static final Float THRESHOLD = 1.0f;
 
+    private MockedStatic<MoneyHelper> moneyHelperStatic;
+
     @BeforeEach
     public void setup() {
         Utils.initializeRESTAssured();
@@ -126,6 +143,83 @@ public class FixedDepositTest {
         TimeZone.setDefault(TimeZone.getTimeZone(Utils.TENANT_TIME_ZONE));
     }
 
+    /***
+     * Test case for Fixed Deposit Account Interest Calculation
+     */
+    @Test
+    public void 
testFixedDepositInterestCalculationWithWrongCompoundingPeriod() {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("principalAmount", 100);
+        jsonObject.addProperty("annualInterestRate", 5);
+        jsonObject.addProperty("tenureInMonths", 12);
+        jsonObject.addProperty("interestPostingPeriodInMonths", 3);
+        jsonObject.addProperty("interestCompoundingPeriodInMonths", 7);
+        JsonParser parser = new JsonParser();
+        String apiRequestBodyAsJson = jsonObject.toString();
+        JsonElement element = parser.parse(apiRequestBodyAsJson);
+        moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
+        moneyHelperStatic.when(() -> 
MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
+        fixedDepositAccountInterestCalculationServiceImpl = new 
FixedDepositAccountInterestCalculationServiceImpl(
+                new DepositAccountDataValidator(new FromJsonHelper(), null), 
new FromJsonHelper());
+        try {
+            HashMap h = fixedDepositAccountInterestCalculationServiceImpl
+                    .calculateInterest(new JsonQuery(apiRequestBodyAsJson, 
element, new FromJsonHelper()));
+            fail("The function must throw an exception when called with 
invalid Compounding period");
+        } catch (PlatformApiDataValidationException e) {
+            assertEquals("Validation errors exist.", e.getMessage());
+        } finally {
+            moneyHelperStatic.close();
+        }
+    }
+
+    @Test
+    public void 
testFixedDepositInterestCalculationWithWrongCompoundingPeriod2() {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("principalAmount", 100);
+        jsonObject.addProperty("annualInterestRate", 5);
+        jsonObject.addProperty("tenureInMonths", 15);
+        jsonObject.addProperty("interestPostingPeriodInMonths", 3);
+        jsonObject.addProperty("interestCompoundingPeriodInMonths", 6);
+        JsonParser parser = new JsonParser();
+        String apiRequestBodyAsJson = jsonObject.toString();
+        JsonElement element = parser.parse(apiRequestBodyAsJson);
+        moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
+        moneyHelperStatic.when(() -> 
MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
+        fixedDepositAccountInterestCalculationServiceImpl = new 
FixedDepositAccountInterestCalculationServiceImpl(
+                new DepositAccountDataValidator(new FromJsonHelper(), null), 
new FromJsonHelper());
+        try {
+            HashMap h = fixedDepositAccountInterestCalculationServiceImpl
+                    .calculateInterest(new JsonQuery(apiRequestBodyAsJson, 
element, new FromJsonHelper()));
+            fail("The function must throw an exception when called with 
invalid Compounding period");
+        } catch (PlatformApiDataValidationException e) {
+            assertEquals("Validation errors exist.", e.getMessage());
+        } finally {
+            moneyHelperStatic.close();
+        }
+    }
+
+    @Test
+    public void testFixedDepositInterestCalculationWithValidInput() {
+        JsonObject jsonObject = new JsonObject();
+        jsonObject.addProperty("principalAmount", 10000);
+        jsonObject.addProperty("annualInterestRate", 5);
+        jsonObject.addProperty("tenureInMonths", 12);
+        jsonObject.addProperty("interestPostingPeriodInMonths", 3);
+        jsonObject.addProperty("interestCompoundingPeriodInMonths", 6);
+        JsonParser parser = new JsonParser();
+        String apiRequestBodyAsJson = jsonObject.toString();
+        JsonElement element = parser.parse(apiRequestBodyAsJson);
+        moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
+        moneyHelperStatic.when(() -> 
MoneyHelper.getMathContext()).thenReturn(new MathContext(12, RoundingMode.UP));
+        fixedDepositAccountInterestCalculationServiceImpl = new 
FixedDepositAccountInterestCalculationServiceImpl(
+                new DepositAccountDataValidator(new FromJsonHelper(), null), 
new FromJsonHelper());
+        BigDecimal expectedResult = new BigDecimal("10506.250000");
+        BigDecimal actualResult = new 
BigDecimal(fixedDepositAccountInterestCalculationServiceImpl
+                .calculateInterest(new JsonQuery(apiRequestBodyAsJson, 
element, new FromJsonHelper())).get("maturityAmount").toString());
+        assertEquals(expectedResult, actualResult);
+        moneyHelperStatic.close();
+    }
+
     /***
      * Test case for Fixed Deposit Product with default attributes
      */


Reply via email to