This is an automated email from the ASF dual-hosted git repository.
manojvm 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 a97a3d040 FINERACT-1603- Post Interest Job Fix
new 3827417a6 Merge pull request #2322 from apurbraj/develop
a97a3d040 is described below
commit a97a3d0402aff5cc5c25a3b0c67c078c63b89158
Author: apurbraj <[email protected]>
AuthorDate: Tue May 10 21:18:30 2022 +0530
FINERACT-1603- Post Interest Job Fix
---
.../portfolio/savings/data/SavingsAccountData.java | 6 +-
.../savings/data/SavingsAccountSummaryData.java | 2 +-
.../SavingsAccountInterestPostingServiceImpl.java | 9 +-
.../SavingsAccountReadPlatformServiceImpl.java | 3 +-
.../service/SavingsSchedularInterestPoster.java | 2 +-
.../SavingsInterestPostingJobIntegrationTest.java | 171 +++++++++++++++++++++
.../common/savings/SavingsAccountHelper.java | 2 +-
.../common/savings/SavingsProductHelper.java | 9 ++
8 files changed, 194 insertions(+), 10 deletions(-)
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
index dbc354737..83617b084 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java
@@ -180,7 +180,7 @@ public final class SavingsAccountData implements
Serializable {
this.reasonForBlock = null;
this.timeline = null;
this.currency = null;
- this.nominalAnnualInterestRate = nominalAnnualInterestRate;
+ this.nominalAnnualInterestRate = nominalAnnualInterestRate != null ?
nominalAnnualInterestRate : BigDecimal.ZERO;
this.interestCompoundingPeriodType = interestCompoundingPeriodType;
this.interestPostingPeriodType = interestPostingPeriodType;
this.interestCalculationType = interestCalculationType;
@@ -537,7 +537,7 @@ public final class SavingsAccountData implements
Serializable {
this.reasonForBlock = null;
this.timeline = null;
this.currency = null;
- this.nominalAnnualInterestRate = nominalAnnualInterestRate;
+ this.nominalAnnualInterestRate = nominalAnnualInterestRate == null ?
BigDecimal.ZERO : nominalAnnualInterestRate;
this.interestCompoundingPeriodType = interestCompoundingPeriodType;
this.interestPostingPeriodType = interestPostingPeriodType;
this.interestCalculationType = interestCalculationType;
@@ -959,7 +959,7 @@ public final class SavingsAccountData implements
Serializable {
this.reasonForBlock = reasonForBlock;
this.timeline = timeline;
this.currency = currency;
- this.nominalAnnualInterestRate = nominalAnnualInterestRate;
+ this.nominalAnnualInterestRate = nominalAnnualInterestRate == null ?
BigDecimal.ZERO : nominalAnnualInterestRate;
this.interestCompoundingPeriodType = interestPeriodType;
this.interestPostingPeriodType = interestPostingPeriodType;
this.interestCalculationType = interestCalculationType;
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java
index fbbe5cccb..2cb810b15 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java
@@ -78,7 +78,7 @@ public class SavingsAccountSummaryData implements
Serializable {
this.interestNotPosted = interestNotPosted;
this.lastInterestCalculationDate = lastInterestCalculationDate;
this.availableBalance = availableBalance;
- this.interestPostedTillDate = interestPostedTillDate;
+ this.interestPostedTillDate = interestPostedTillDate == null ?
lastInterestCalculationDate : interestPostedTillDate;
}
public void setPrevInterestPostedTillDate(LocalDate
interestPostedTillDate) {
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java
index dd660ffad..74a19d350 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java
@@ -108,7 +108,7 @@ public class SavingsAccountInterestPostingServiceImpl
implements SavingsAccountI
newPostingTransaction =
SavingsAccountTransactionData.interestPosting(savingsAccountData,
interestPostingTransactionDate,
interestEarnedToBePostedForPeriod, interestPostingPeriod.isUserPosting());
} else {
- newPostingTransaction =
SavingsAccountTransactionData.interestPosting(savingsAccountData,
+ newPostingTransaction =
SavingsAccountTransactionData.overdraftInterest(savingsAccountData,
interestPostingTransactionDate,
interestEarnedToBePostedForPeriod.negated(),
interestPostingPeriod.isUserPosting());
}
@@ -250,7 +250,8 @@ public class SavingsAccountInterestPostingServiceImpl
implements SavingsAccountI
final List<PostingPeriod> allPostingPeriods = new ArrayList<>();
Money periodStartingBalance;
- if (savingsAccountData.getStartInterestCalculationDate() != null) {
+ if (savingsAccountData.getStartInterestCalculationDate() != null
+ &&
!savingsAccountData.getStartInterestCalculationDate().equals(savingsAccountData.getActivationLocalDate()))
{
final SavingsAccountTransactionData transaction =
retrieveLastTransactions(savingsAccountData);
if (transaction == null) {
@@ -361,7 +362,9 @@ public class SavingsAccountInterestPostingServiceImpl
implements SavingsAccountI
}
private BigDecimal getEffectiveOverdraftInterestRateAsFraction(MathContext
mc, final SavingsAccountData savingsAccountData) {
- return
savingsAccountData.getNominalAnnualInterestRateOverdraft().divide(BigDecimal.valueOf(100L),
mc);
+ return savingsAccountData.getNominalAnnualInterestRateOverdraft() !=
null
+ ?
savingsAccountData.getNominalAnnualInterestRateOverdraft().divide(BigDecimal.valueOf(100L),
mc)
+ : BigDecimal.ZERO;
}
@SuppressWarnings("unused")
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
index f75dc60aa..7e98984e9 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java
@@ -274,7 +274,8 @@ public class SavingsAccountReadPlatformServiceImpl
implements SavingsAccountRead
+ "where (CASE WHEN sa.interest_posted_till_date is not
null THEN tr.transaction_date >= sa.interest_posted_till_date ELSE
tr.transaction_date >= sa.activatedon_date END) ";
}
- sql = sql + "and apm.product_type=2 and sa.interest_posted_till_date <
'" + java.sql.Date.valueOf(currentDate) + "'";
+ sql = sql + " and (sa.interest_posted_till_date is null or
sa.interest_posted_till_date < '" + java.sql.Date.valueOf(currentDate)
+ + "') ";
sql = sql + " order by sa.id, tr.transaction_date, tr.created_date,
tr.id";
List<SavingsAccountData> savingsAccountDataList =
this.jdbcTemplate.query(sql, this.savingAccountMapperForInterestPosting, //
NOSONAR
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
index b78a39bfe..ae896230f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java
@@ -359,7 +359,7 @@ public class SavingsSchedularInterestPoster implements
Callable<Void> {
query.append("transaction_type_enum, transaction_date, amount,
balance_end_date_derived,");
query.append("balance_number_of_days_derived, running_balance_derived,
cumulative_balance_derived,");
query.append("created_date, appuser_id, is_manual,
is_loan_disbursement, ref_no) VALUES ");
- query.append("(?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)");
+ query.append("(?, ?, false, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, false, ?)");
return query.toString();
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingJobIntegrationTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingJobIntegrationTest.java
new file mode 100644
index 000000000..d92213c06
--- /dev/null
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingJobIntegrationTest.java
@@ -0,0 +1,171 @@
+/**
+ * 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.SchedulerJobHelper;
+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;
+
+public class SavingsInterestPostingJobIntegrationTest {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(SavingsInterestPostingJobIntegrationTest.class);
+ public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL";
+
+ private ResponseSpecification responseSpec;
+ private RequestSpecification requestSpec;
+ private SavingsProductHelper savingsProductHelper;
+ private SavingsAccountHelper savingsAccountHelper;
+ private SchedulerJobHelper scheduleJobHelper;
+
+ @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();
+ this.scheduleJobHelper = new SchedulerJobHelper(requestSpec);
+ }
+
+ @Test
+ public void testSavingsDailyInterestPostingJob() {
+ // client activation, savings activation and 1st transaction date
+ final String startDate = "10 April 2022";
+ final String jobName = "Post Interest For Savings";
+ final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec, startDate);
+ Assertions.assertNotNull(clientID);
+
+ final Integer savingsId = createSavingsAccountDailyPosting(clientID,
startDate);
+
+ this.savingsAccountHelper.depositToSavingsAccount(savingsId, "10000",
startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+ /***
+ * Runs Post interest posting job and verify the new account created
with accounting configuration set as none
+ * is picked up by job
+ */
+ this.scheduleJobHelper.executeAndAwaitJob(jobName);
+ Object transactionObj =
this.savingsAccountHelper.getSavingsDetails(savingsId, "transactions");
+ ArrayList<HashMap<String, Object>> transactions =
(ArrayList<HashMap<String, Object>>) transactionObj;
+ HashMap<String, Object> interestPostingTransaction =
transactions.get(transactions.size() - 3);
+ for (Map.Entry<String, Object> entry :
interestPostingTransaction.entrySet()) {
+ LOG.info("{} - {}", entry.getKey(), entry.getValue().toString());
+ }
+ assertEquals("2.7405",
interestPostingTransaction.get("amount").toString(), "Equality check for
interest posted amount");
+ assertEquals("[2022, 4, 12]",
interestPostingTransaction.get("date").toString(), "Date check for Interest
Posting transaction");
+ }
+
+ @Test
+ public void testSavingsDailyOverdraftInterestPostingJob() {
+ // client activation, savings activation and 1st transaction date
+ final String startDate = "10 April 2022";
+ final String jobName = "Post Interest For Savings";
+ final Integer clientID = ClientHelper.createClient(this.requestSpec,
this.responseSpec, startDate);
+ Assertions.assertNotNull(clientID);
+
+ final Integer savingsId =
createSavingsAccountDailyPostingOverdraft(clientID, startDate);
+
+ this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId,
"10000", startDate, CommonConstants.RESPONSE_RESOURCE_ID);
+
+ /***
+ * Runs Post interest posting job and verify the new account created
with Overdraft is posting negative interest
+ */
+ this.scheduleJobHelper.executeAndAwaitJob(jobName);
+ Object transactionObj =
this.savingsAccountHelper.getSavingsDetails(savingsId, "transactions");
+ ArrayList<HashMap<String, Object>> transactions =
(ArrayList<HashMap<String, Object>>) transactionObj;
+ HashMap<String, Object> interestPostingTransaction =
transactions.get(transactions.size() - 2);
+ for (Map.Entry<String, Object> entry :
interestPostingTransaction.entrySet()) {
+ LOG.info("{} - {}", entry.getKey(), entry.getValue().toString());
+ }
+ assertEquals("2.7397",
interestPostingTransaction.get("amount").toString(), "Equality check for
overdatft interest posted amount");
+ assertEquals("[2022, 4, 11]",
interestPostingTransaction.get("date").toString(),
+ "Date check for overdraft 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 createSavingsAccountDailyPostingOverdraft(final Integer
clientID, final String startDate) {
+ final Integer savingsProductID =
createSavingsProductDailyPostingOverdraft();
+ 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);
+ }
+
+ private Integer createSavingsProductDailyPostingOverdraft() {
+ final String overDraftLimit = "10000.0";
+ final String nominalAnnualInterestRateOverdraft = "10";
+ final String savingsProductJSON =
this.savingsProductHelper.withInterestCompoundingPeriodTypeAsDaily()
+
.withInterestPostingPeriodTypeAsDaily().withInterestCalculationPeriodTypeAsDailyBalance()
+ .withOverDraftRate(overDraftLimit,
nominalAnnualInterestRateOverdraft).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/SavingsAccountHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
index 6ec624203..90127462e 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java
@@ -644,7 +644,7 @@ public class SavingsAccountHelper {
}
public Object getSavingsDetails(final Integer savingsID, final String
returnAttribute) {
- final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID + "?" +
Utils.TENANT_IDENTIFIER;
+ final String URL = SAVINGS_ACCOUNT_URL + "/" + savingsID +
"?associations=all&" + Utils.TENANT_IDENTIFIER;
final Object response = Utils.performServerGet(requestSpec,
responseSpec, URL, returnAttribute);
return response;
}
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 4b1b66b84..6ede76cbd 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
@@ -96,6 +96,7 @@ public class SavingsProductHelper {
private String daysToEscheat = null;
private Boolean withgsimID = null;
private Integer gsimID = null;
+ private String nominalAnnualInterestRateOverdraft = null;
public String build() {
final HashMap<String, String> map = new HashMap<>();
@@ -134,6 +135,7 @@ public class SavingsProductHelper {
map.put("lienAllowed", this.lienAllowed);
map.put("maxAllowedLienLimit", this.maxAllowedLienLimit);
map.put("withHoldTax", this.withHoldTax.toString());
+ map.put("nominalAnnualInterestRateOverdraft",
this.nominalAnnualInterestRateOverdraft);
if (withHoldTax) {
map.put("taxGroupId", taxGroupId);
@@ -242,6 +244,13 @@ public class SavingsProductHelper {
return this;
}
+ public SavingsProductHelper withOverDraftRate(final String overdraftLimit,
String nominalAnnualInterestRateOverdraft) {
+ this.allowOverdraft = "true";
+ this.overdraftLimit = overdraftLimit;
+ this.nominalAnnualInterestRateOverdraft =
nominalAnnualInterestRateOverdraft;
+ return this;
+ }
+
public SavingsProductHelper withWithHoldTax(final String taxGroupId) {
if (taxGroupId != null) {
this.withHoldTax = true;