FINERACT-2394:Fixed Deposits is creating double Interest Postings
This commit is contained in:
committed by
Adam Saghy
parent
8c3c44b618
commit
5c8d86cba5
+108
-65
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import static org.apache.fineract.integrationtests.common.BusinessDateHelper.runAt;
|
||||
|
||||
import io.restassured.builder.RequestSpecBuilder;
|
||||
import io.restassured.builder.ResponseSpecBuilder;
|
||||
import io.restassured.http.ContentType;
|
||||
@@ -34,11 +36,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
||||
import org.apache.fineract.client.models.PostSavingsAccountsAccountIdRequest;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.CommonConstants;
|
||||
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
|
||||
@@ -51,6 +50,7 @@ import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsTestLifecycleExtension;
|
||||
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -88,9 +88,14 @@ public class SavingsInterestPostingTest {
|
||||
this.globalConfigurationHelper = new GlobalConfigurationHelper();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanupAfterTest() {
|
||||
cleanupSavingsAccountsFromDuplicatePreventionTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostInterestWithOverdraftProduct() {
|
||||
try {
|
||||
runAt("12 March 2025", () -> {
|
||||
final String amount = "10000";
|
||||
|
||||
final Account assetAccount = accountHelper.createAssetAccount();
|
||||
@@ -115,13 +120,10 @@ public class SavingsInterestPostingTest {
|
||||
savingsAccountHelper.activateSavings(accountId, startDateString);
|
||||
savingsAccountHelper.depositToSavingsAccount(accountId, amount, startDateString, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
|
||||
// Simulate time passing - update business date to March
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(true));
|
||||
LocalDate marchDate = LocalDate.of(2025, 3, 2);
|
||||
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||
|
||||
runAccrualsThenPost();
|
||||
schedulerJobHelper.executeAndAwaitJob(ACCRUALS_JOB_NAME);
|
||||
schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
|
||||
|
||||
long days = ChronoUnit.DAYS.between(startDate, marchDate.minusDays(1));
|
||||
BigDecimal expected = calcInterestPosting(productHelper, amount, days);
|
||||
@@ -135,15 +137,12 @@ public class SavingsInterestPostingTest {
|
||||
Assertions.assertEquals(0L, overdraftCount, "Expected NO OVERDRAFT posting on posting date");
|
||||
|
||||
assertNoAccrualReversals(accountId);
|
||||
} finally {
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverdraftInterestWithOverdraftProduct() {
|
||||
try {
|
||||
runAt("12 March 2025", () -> {
|
||||
final String amount = "10000";
|
||||
|
||||
final Account assetAccount = accountHelper.createAssetAccount();
|
||||
@@ -168,13 +167,10 @@ public class SavingsInterestPostingTest {
|
||||
savingsAccountHelper.activateSavings(accountId, startDateString);
|
||||
savingsAccountHelper.withdrawalFromSavingsAccount(accountId, amount, startDateString, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
|
||||
// Simulate time passing - update business date to March
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(true));
|
||||
LocalDate marchDate = LocalDate.of(2025, 3, 2);
|
||||
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||
|
||||
runAccrualsThenPost();
|
||||
schedulerJobHelper.executeAndAwaitJob(ACCRUALS_JOB_NAME);
|
||||
schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
|
||||
|
||||
long days = ChronoUnit.DAYS.between(startDate, marchDate.minusDays(1));
|
||||
BigDecimal expected = calcOverdraftPosting(productHelper, amount, days);
|
||||
@@ -191,15 +187,12 @@ public class SavingsInterestPostingTest {
|
||||
Assertions.assertEquals(1L, overdraftCount, "Expected exactly one OVERDRAFT posting on posting date");
|
||||
|
||||
assertNoAccrualReversals(accountId);
|
||||
} finally {
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverdraftAndInterestPosting_WithOverdraftProduct_WhitBalanceLessZero() {
|
||||
try {
|
||||
runAt("12 March 2025", () -> {
|
||||
final String amountDeposit = "10000";
|
||||
final String amountWithdrawal = "20000";
|
||||
|
||||
@@ -230,12 +223,10 @@ public class SavingsInterestPostingTest {
|
||||
savingsAccountHelper.withdrawalFromSavingsAccount(accountId, amountWithdrawal, withdrawalStr,
|
||||
CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(true));
|
||||
LocalDate marchDate = LocalDate.of(2025, 3, 2);
|
||||
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||
|
||||
runAccrualsThenPost();
|
||||
schedulerJobHelper.executeAndAwaitJob(ACCRUALS_JOB_NAME);
|
||||
schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
|
||||
|
||||
List<HashMap> txs = getInterestTransactions(accountId);
|
||||
for (HashMap tx : txs) {
|
||||
@@ -262,15 +253,12 @@ public class SavingsInterestPostingTest {
|
||||
"Expected exactly one OVERDRAFT posting on posting date");
|
||||
|
||||
assertNoAccrualReversals(accountId);
|
||||
} finally {
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverdraftAndInterestPosting_WithOverdraftProduct_WhitBalanceGreaterZero() {
|
||||
try {
|
||||
runAt("12 March 2025", () -> {
|
||||
final String amountDeposit = "20000";
|
||||
final String amountWithdrawal = "10000";
|
||||
|
||||
@@ -300,12 +288,10 @@ public class SavingsInterestPostingTest {
|
||||
final String depositStr = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(depositDate);
|
||||
savingsAccountHelper.depositToSavingsAccount(accountId, amountDeposit, depositStr, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(true));
|
||||
LocalDate marchDate = LocalDate.of(2025, 3, 2);
|
||||
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||
|
||||
runAccrualsThenPost();
|
||||
schedulerJobHelper.executeAndAwaitJob(ACCRUALS_JOB_NAME);
|
||||
schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
|
||||
|
||||
List<HashMap> txs = getInterestTransactions(accountId);
|
||||
for (HashMap tx : txs) {
|
||||
@@ -332,15 +318,12 @@ public class SavingsInterestPostingTest {
|
||||
"Expected exactly one INTEREST posting on posting date");
|
||||
|
||||
assertNoAccrualReversals(accountId);
|
||||
} finally {
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostInterestNotZero() {
|
||||
try {
|
||||
runAt("12 March 2025", () -> {
|
||||
final String amountDeposit = "1000";
|
||||
final String amountWithdrawal = "1000";
|
||||
|
||||
@@ -366,20 +349,17 @@ public class SavingsInterestPostingTest {
|
||||
savingsAccountHelper.activateSavings(accountId, startStr);
|
||||
savingsAccountHelper.depositToSavingsAccount(accountId, amountDeposit, startStr, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(true));
|
||||
LocalDate februaryDate = LocalDate.of(startDate.getYear(), 2, 1);
|
||||
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, februaryDate);
|
||||
LocalDate februaryDate = LocalDate.of(2025, 2, 1);
|
||||
|
||||
schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
|
||||
|
||||
List<HashMap> txsFebruary = getInterestTransactions(accountId); // OBTENER EL POSTEO DEL INTEREST
|
||||
List<HashMap> txsFebruary = getInterestTransactions(accountId);
|
||||
|
||||
long daysFebruary = ChronoUnit.DAYS.between(startDate, februaryDate);
|
||||
BigDecimal expectedFebruary = calcInterestPosting(productHelper, amountDeposit, daysFebruary);
|
||||
Assertions.assertEquals(expectedFebruary, BigDecimal.valueOf(((Double) txsFebruary.get(0).get("amount"))));
|
||||
|
||||
final LocalDate withdrawalDate = LocalDate.of(startDate.getYear(), 2, 1);
|
||||
final LocalDate withdrawalDate = LocalDate.of(2025, 2, 1);
|
||||
final String withdrawal = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(withdrawalDate);
|
||||
|
||||
BigDecimal runningBalance = new BigDecimal(txsFebruary.get(0).get("runningBalance").toString());
|
||||
@@ -390,13 +370,12 @@ public class SavingsInterestPostingTest {
|
||||
savingsAccountHelper.withdrawalFromSavingsAccount(accountId, amountWithdrawal, withdrawal,
|
||||
CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
|
||||
LocalDate marchDate = LocalDate.of(startDate.getYear(), 3, 1);
|
||||
BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate);
|
||||
LocalDate marchDate = LocalDate.of(2025, 3, 1);
|
||||
|
||||
schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
|
||||
|
||||
List<HashMap> txs = getInterestTransactions(accountId); // CON ESTE DEBEMOS DE VALIDAR QUE EL DIA DE MARZO
|
||||
// NO SE TENGA POSTEO EN CERO
|
||||
List<HashMap> txs = getInterestTransactions(accountId);
|
||||
|
||||
for (HashMap tx : txs) {
|
||||
BigDecimal amt = BigDecimal.valueOf(((Double) tx.get("amount")));
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -418,9 +397,82 @@ public class SavingsInterestPostingTest {
|
||||
"Expected exactly one OVERDRAFT posting on posting date");
|
||||
|
||||
assertNoAccrualReversals(accountId);
|
||||
} finally {
|
||||
globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE,
|
||||
new PutGlobalConfigurationsRequest().enabled(false));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostInterestForDuplicatePrevention() {
|
||||
runAt("18 March 2025", () -> {
|
||||
final String amount = "10000";
|
||||
|
||||
final Account assetAccount = accountHelper.createAssetAccount();
|
||||
final Account incomeAccount = accountHelper.createIncomeAccount();
|
||||
final Account expenseAccount = accountHelper.createExpenseAccount();
|
||||
final Account liabilityAccount = accountHelper.createLiabilityAccount();
|
||||
final Account interestReceivableAccount = accountHelper.createAssetAccount("interestReceivableAccount");
|
||||
final Account savingsControlAccount = accountHelper.createLiabilityAccount("Savings Control");
|
||||
final Account interestPayableAccount = accountHelper.createLiabilityAccount("Interest Payable");
|
||||
|
||||
final Integer productId = createSavingsProductWithAccrualAccountingWithOutOverdraftAllowed(
|
||||
interestPayableAccount.getAccountID().toString(), savingsControlAccount.getAccountID().toString(),
|
||||
interestReceivableAccount.getAccountID().toString(), assetAccount, incomeAccount, expenseAccount, liabilityAccount);
|
||||
|
||||
final LocalDate startDate = LocalDate.of(2025, 2, 1);
|
||||
|
||||
List<Integer> accountIdList = new ArrayList<>();
|
||||
for (int i = 0; i < 800; i++) {
|
||||
|
||||
final Integer clientId = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2025");
|
||||
final String startDateString = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate);
|
||||
final Integer accountId = savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, productId,
|
||||
SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startDateString);
|
||||
|
||||
savingsAccountHelper.approveSavingsOnDate(accountId, startDateString);
|
||||
savingsAccountHelper.activateSavings(accountId, startDateString);
|
||||
savingsAccountHelper.depositToSavingsAccount(accountId, amount, startDateString, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
|
||||
accountIdList.add(accountId);
|
||||
}
|
||||
Assertions.assertEquals(800, accountIdList.size(), "ERROR: Expected 800");
|
||||
|
||||
schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
|
||||
|
||||
for (Integer accountId : accountIdList) {
|
||||
List<HashMap> txs = getInterestTransactions(accountId);
|
||||
Assertions.assertEquals(1, txs.size(), "ERROR: Duplicate interest postings exist.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void cleanupSavingsAccountsFromDuplicatePreventionTest() {
|
||||
try {
|
||||
LOG.info("Starting cleanup of savings accounts after duplicate prevention test");
|
||||
|
||||
List<Long> savingsIds = SavingsAccountHelper.getSavingsIdsByStatusId(300);
|
||||
if (!savingsIds.isEmpty()) {
|
||||
LOG.info("Found {} savings accounts to cleanup", savingsIds.size());
|
||||
|
||||
savingsIds.forEach(savingsId -> {
|
||||
try {
|
||||
|
||||
savingsAccountHelper.postInterestForSavings(savingsId.intValue());
|
||||
|
||||
savingsAccountHelper.closeSavingsAccount(savingsId,
|
||||
new PostSavingsAccountsAccountIdRequest().locale("en").dateFormat(Utils.DATE_FORMAT)
|
||||
.closedOnDate(Utils.dateFormatter.format(Utils.getLocalDateOfTenant())).withdrawBalance(true));
|
||||
|
||||
LOG.debug("Savings account {} closed successfully", savingsId);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Unable to close savings account {}: {}", savingsId, e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
LOG.info("Savings accounts cleanup completed");
|
||||
} else {
|
||||
LOG.info("No savings accounts found to cleanup");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error during savings accounts cleanup: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,15 +580,6 @@ public class SavingsInterestPostingTest {
|
||||
.filter(SavingsAccountTransactionType::isOverDraftInterestPosting).count();
|
||||
}
|
||||
|
||||
private void runAccrualsThenPost() {
|
||||
try {
|
||||
schedulerJobHelper.executeAndAwaitJob(ACCRUALS_JOB_NAME);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
LOG.warn("Accruals job not found ({}). Continuing without it.", ACCRUALS_JOB_NAME, ex);
|
||||
}
|
||||
schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
private boolean isReversed(HashMap tx) {
|
||||
Object v = tx.get("reversed");
|
||||
|
||||
Reference in New Issue
Block a user