From 7ff3f3824366742c78db5e622a208b07dc289358 Mon Sep 17 00:00:00 2001 From: Manoj Date: Wed, 10 Nov 2021 12:02:08 +0530 Subject: [PATCH] FINERACT-1440 : Enable Daily Interest Posting for Savings --- .../SavingsPostingInterestPeriodType.java | 4 + .../savings/domain/SavingsAccount.java | 4 + .../savings/domain/SavingsHelper.java | 4 + ...avingsDropdownReadPlatformServiceImpl.java | 14 +-- .../savings/service/SavingsEnumerations.java | 4 + ...SavingsInterestPostingIntegrationTest.java | 118 ++++++++++++++++++ .../common/savings/SavingsProductHelper.java | 5 + 7 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingIntegrationTest.java diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java index 204ab96b0..aefdb5f08 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsPostingInterestPeriodType.java @@ -28,6 +28,7 @@ import java.util.List; public enum SavingsPostingInterestPeriodType { INVALID(0, "savingsPostingInterestPeriodType.invalid"), // + DAILY(1, "savingsPostingInterestPeriodType.daily"), // MONTHLY(4, "savingsPostingInterestPeriodType.monthly"), // QUATERLY(5, "savingsPostingInterestPeriodType.quarterly"), // BIANNUAL(6, "savingsPostingInterestPeriodType.biannual"), ANNUAL(7, "savingsPostingInterestPeriodType.annual"); @@ -63,6 +64,9 @@ public enum SavingsPostingInterestPeriodType { SavingsPostingInterestPeriodType repaymentFrequencyType = SavingsPostingInterestPeriodType.INVALID; if (type != null) { switch (type) { + case 1: + repaymentFrequencyType = SavingsPostingInterestPeriodType.DAILY; + break; case 4: repaymentFrequencyType = SavingsPostingInterestPeriodType.MONTHLY; break; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 7446c6c76..3ffbc25d4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -2813,6 +2813,10 @@ public class SavingsAccount extends AbstractPersistableCustom { public void validateInterestPostingAndCompoundingPeriodTypes(final DataValidatorBuilder baseDataValidator) { Map> postingtoCompoundMap = new HashMap<>(); + + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.DAILY, + Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY })); + postingtoCompoundMap.put(SavingsPostingInterestPeriodType.MONTHLY, Arrays.asList(new SavingsCompoundingInterestPeriodType[] { SavingsCompoundingInterestPeriodType.DAILY, SavingsCompoundingInterestPeriodType.MONTHLY })); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java index e5630fde7..14930e103 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsHelper.java @@ -118,6 +118,10 @@ public final class SavingsHelper { switch (interestPostingPeriodType) { case INVALID: break; + case DAILY: + // produce period end date on current day + periodEndDate = periodStartDate; + break; case MONTHLY: // produce period end date on last day of current month periodEndDate = periodStartDate.with(TemporalAdjusters.lastDayOfMonth()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java index 6496b68dd..ef141961c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsDropdownReadPlatformServiceImpl.java @@ -56,10 +56,6 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe public Collection retrieveCompoundingInterestPeriodTypeOptions() { final List allowedOptions = Arrays.asList( SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.DAILY), - // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.WEEKLY), - // // - // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BIWEEKLY), - // // SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.MONTHLY), SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.QUATERLY), SavingsEnumerations.compoundingInterestPeriodType(SavingsCompoundingInterestPeriodType.BI_ANNUAL), @@ -75,10 +71,12 @@ public class SavingsDropdownReadPlatformServiceImpl implements SavingsDropdownRe @Override public Collection retrieveInterestPostingPeriodTypeOptions() { final List allowedOptions = Arrays.asList( - SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY), - SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.QUATERLY), - SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.BIANNUAL), - SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.ANNUAL)); + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.DAILY), // + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.MONTHLY), // + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.QUATERLY), // + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.BIANNUAL), // + SavingsEnumerations.interestPostingPeriodType(SavingsPostingInterestPeriodType.ANNUAL) // + ); return allowedOptions; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java index 5e1375f73..eb4ba984b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsEnumerations.java @@ -364,6 +364,10 @@ public final class SavingsEnumerations { switch (type) { case INVALID: break; + case DAILY: + optionData = new EnumOptionData(SavingsPostingInterestPeriodType.DAILY.getValue().longValue(), + codePrefix + SavingsPostingInterestPeriodType.DAILY.getCode(), "Daily"); + break; case MONTHLY: optionData = new EnumOptionData(SavingsPostingInterestPeriodType.MONTHLY.getValue().longValue(), codePrefix + SavingsPostingInterestPeriodType.MONTHLY.getCode(), "Monthly"); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingIntegrationTest.java new file mode 100644 index 000000000..3fe166a36 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingIntegrationTest.java @@ -0,0 +1,118 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +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.Utils; +import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; +import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper; +import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings({ "rawtypes", "unused", "unchecked" }) +public class SavingsInterestPostingIntegrationTest { + + private static final Logger LOG = LoggerFactory.getLogger(SavingsInterestPostingIntegrationTest.class); + public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL"; + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private SavingsProductHelper savingsProductHelper; + private SavingsAccountHelper savingsAccountHelper; + + @BeforeEach + public void setup() { + Utils.initializeRESTAssured(); + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + this.savingsProductHelper = new SavingsProductHelper(); + } + + @Test + public void testSavingsDailyInterestPosting() { + // client activation, savings activation and 1st transaction date + final String startDate = "01 November 2021"; + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, startDate); + Assertions.assertNotNull(clientID); + + final Integer savingsId = createSavingsAccountDailyPosting(clientID, startDate); + + this.savingsAccountHelper.depositToSavingsAccount(savingsId, "1000", startDate, CommonConstants.RESPONSE_RESOURCE_ID); + + /*** + * Perform Post interest transaction and verify the posted transaction date + */ + this.savingsAccountHelper.postInterestForSavings(savingsId); + HashMap accountDetails = this.savingsAccountHelper.getSavingsDetails(savingsId); + ArrayList> transactions = (ArrayList>) accountDetails.get("transactions"); + HashMap interestPostingTransaction = transactions.get(transactions.size() - 2); + for (Map.Entry entry : interestPostingTransaction.entrySet()) { + LOG.info("{} - {}", entry.getKey(), entry.getValue().toString()); + } + assertEquals("0.274", interestPostingTransaction.get("amount").toString(), "Equality check for interest posted amount"); + assertEquals("[2021, 11, 2]", interestPostingTransaction.get("date").toString(), "Date check for Interest Posting transaction"); + + } + + private Integer createSavingsAccountDailyPosting(final Integer clientID, final String startDate) { + final Integer savingsProductID = createSavingsProductDailyPosting(); + Assertions.assertNotNull(savingsProductID); + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplicationOnDate(clientID, savingsProductID, + ACCOUNT_TYPE_INDIVIDUAL, startDate); + Assertions.assertNotNull(savingsId); + HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavingsOnDate(savingsId, startDate); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + savingsStatusHashMap = this.savingsAccountHelper.activateSavingsAccount(savingsId, startDate); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + return savingsId; + } + + private Integer createSavingsProductDailyPosting() { + final String savingsProductJSON = this.savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily() + .withInterestPostingPeriodTypeAsDaily().withInterestCalculationPeriodTypeAsDailyBalance().build(); + return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec); + } + + // Reset configuration fields + @AfterEach + public void tearDown() { + GlobalConfigurationHelper.resetAllDefaultGlobalConfigurations(this.requestSpec, this.responseSpec); + GlobalConfigurationHelper.verifyAllDefaultGlobalConfigurations(this.requestSpec, this.responseSpec); + } + +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java index e6b3b808a..507cd0b4b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsProductHelper.java @@ -190,6 +190,11 @@ public class SavingsProductHelper { return this; } + public SavingsProductHelper withInterestPostingPeriodTypeAsDaily() { + this.interestPostingPeriodType = DAILY; + return this; + } + public SavingsProductHelper withInterestCalculationPeriodTypeAsDailyBalance() { this.interestCalculationType = INTEREST_CALCULATION_USING_DAILY_BALANCE; return this;