Fineract-1964: Add functionality to calculate maturity amount before creating FD account
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 1665603..5419a79 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 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 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 e202dd5..3c14795 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.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.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 @@
private final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService;
private final BulkImportWorkbookService bulkImportWorkbookService;
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
+ private final FixedDepositAccountInterestCalculationService fixedDepositAccountInterestCalculationService;
@GET
@Path("template")
@@ -208,6 +211,33 @@
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 3249c2b..74a130c 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 @@
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 a28eab4..806036a 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 @@
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.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 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 @@
}
}
+ 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 0000000..5bfd07a
--- /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 0000000..56c7862
--- /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 0000000..943b460
--- /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 69741dc..51ce8d9 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 @@
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.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.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 @@
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 @@
// and then to compare the exact results
public static final Float THRESHOLD = 1.0f;
+ private MockedStatic<MoneyHelper> moneyHelperStatic;
+
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
@@ -127,6 +144,83 @@
}
/***
+ * 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
*/
@Test