diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java index 7fe875bed..961d9a748 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java @@ -212,6 +212,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi final LocalDate closedDate = command.localDateValueOfParameterNamed(SavingsApiConstants.closedOnDateParamName); Long savingsTransactionId = null; account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.setClosedOnDate(closedDate); final Integer onAccountClosureId = command.integerValueOfParameterNamed(onAccountClosureIdParamName); final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); if (onClosureType.isReinvest()) { @@ -272,6 +273,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi final MathContext mc = MathContext.DECIMAL64; Long savingsTransactionId = null; account.postMaturityInterest(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + account.setClosedOnDate(closedDate); final DepositAccountOnClosureType onClosureType = DepositAccountOnClosureType.fromInt(onAccountClosureId); if (onClosureType.isReinvest()) { BigDecimal reInvestAmount; @@ -367,10 +369,11 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi this.calendarInstanceRepository.save(calendarInstance); final Calendar calendar = calendarInstance.getCalendar(); final PeriodFrequencyType frequencyType = CalendarFrequencyType.from(CalendarUtils.getFrequency(calendar.getRecurrence())); + final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); Integer frequency = CalendarUtils.getInterval(calendar.getRecurrence()); frequency = frequency == -1 ? 1 : frequency; reinvestedDeposit.generateSchedule(frequencyType, frequency, calendar); - reinvestedDeposit.processAccountUponActivation(fmt, postReversals); + reinvestedDeposit.processAccountUponActivation(fmt, postReversals, relaxingDaysConfigForPivotDate); reinvestedDeposit.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); this.savingsAccountRepository.save(reinvestedDeposit); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index 2855bc1e5..207d7bd55 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -882,4 +882,7 @@ public class FixedDepositAccount extends SavingsAccount { return SavingsAccountStatusType.MATURED.getValue().equals(this.status); } + public void setClosedOnDate(final LocalDate closedOnDate) { + this.closedOnDate = closedOnDate; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java index 5eea23621..fb064ab0e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java @@ -536,11 +536,11 @@ public class RecurringDepositAccount extends SavingsAccount { return Money.of(this.currency, this.minRequiredOpeningBalance); } - protected void processAccountUponActivation(final DateTimeFormatter fmt, final boolean postReversals) { + protected void processAccountUponActivation(final DateTimeFormatter fmt, final boolean postReversals, + final Long relaxingDaysConfigForPivotDate) { final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); final boolean backdatedTxnsAllowedTill = false; String refNo = null; - final Long relaxingDaysConfigForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate(); if (minRequiredOpeningBalance.isGreaterThanZero()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationDate(), minRequiredOpeningBalance.getAmount(), null, null, accountType); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java index 8ee466cdd..01309e69b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccrualWritePlatformServiceImpl.java @@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; @@ -177,7 +178,9 @@ public class SavingsAccrualWritePlatformServiceImpl implements SavingsAccrualWri savingsAccount.office(), period.getPeriodInterval().endDate(), period.getInterestEarned().abs(), false, refNo); savingsAccountTransaction.setRunningBalance(period.getClosingBalance()); savingsAccountTransaction.setOverdraftAmount(period.getInterestEarned()); - savingsAccount.addTransaction(savingsAccountTransaction); + if (!MathUtil.isZero(savingsAccountTransaction.getAmount())) { + savingsAccount.addTransaction(savingsAccountTransaction); + } } } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 50b2db398..eb574a051 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -1042,7 +1042,7 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom if (MathUtil.isEmpty(overdraftAmount) && runningBalance.isLessThanZero() && !transaction.isAmountOnHold()) { overdraftAmount = runningBalance.negated(); } - if (!calculateInterest || transaction.getId() == null) { + if (!calculateInterest || transaction.getId() == null || transaction.getOverdraftAmount(this.currency).isZero()) { transaction.setOverdraftAmount(overdraftAmount); } else if (!MathUtil.isEqualTo(overdraftAmount, transaction.getOverdraftAmount(this.currency)) && !transaction.isAccrual()) { diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java index 913fbdb31..6928f730b 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java @@ -36,6 +36,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.accounting.journalentry.domain.JournalEntryType; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.savings.data.SavingsAccountData; @@ -106,7 +107,7 @@ public class SavingsSchedularInterestPoster { List savingsAccountTransactionDataList = savingsAccountData.getSavingsAccountTransactionData(); for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) { - if (savingsAccountTransactionData.getId() == null) { + if (savingsAccountTransactionData.getId() == null && !MathUtil.isZero(savingsAccountTransactionData.getAmount())) { final String key = savingsAccountTransactionData.getRefNo(); final Boolean isOverdraft = savingsAccountTransactionData.getIsOverdraft(); final SavingsAccountTransactionData dataFromFetch = savingsAccountTransactionDataHashMap.get(key); @@ -183,7 +184,7 @@ public class SavingsSchedularInterestPoster { auditTime, userId, savingsAccountData.getId() }); List savingsAccountTransactionDataList = savingsAccountData.getSavingsAccountTransactionData(); for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) { - if (savingsAccountTransactionData.getId() == null) { + if (savingsAccountTransactionData.getId() == null && !MathUtil.isZero(savingsAccountTransactionData.getAmount())) { UUID uuid = UUID.randomUUID(); savingsAccountTransactionData.setRefNo(uuid.toString()); transRefNo.add(uuid.toString()); 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 3162faff4..a55e312cc 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 @@ -48,12 +48,17 @@ 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.client.models.PutGlobalConfigurationsRequest; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; 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.client.IntegrationTest; +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; import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.TaxComponentHelper; import org.apache.fineract.integrationtests.common.TaxGroupHelper; @@ -92,6 +97,7 @@ public class FixedDepositTest extends IntegrationTest { private SavingsAccountHelper savingsAccountHelper; private JournalEntryHelper journalEntryHelper; private FinancialActivityAccountHelper financialActivityAccountHelper; + private GlobalConfigurationHelper globalConfigurationHelper; private FixedDepositAccountInterestCalculationServiceImpl fixedDepositAccountInterestCalculationServiceImpl; @@ -108,12 +114,14 @@ public class FixedDepositTest extends IntegrationTest { private static final String NONE = "1"; private static final String CASH_BASED = "2"; + private static final String ACCRUAL = "3"; public static final String MINIMUM_OPENING_BALANCE = "1000.0"; public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL"; public static final String CLOSURE_TYPE_WITHDRAW_DEPOSIT = "100"; public static final String CLOSURE_TYPE_TRANSFER_TO_SAVINGS = "200"; public static final String CLOSURE_TYPE_REINVEST = "300"; + public static final String CLOSURE_TYPE_REINVEST_PRINCIPAL_ONLY = "400"; public static final Integer DAILY_COMPOUNDING_INTERVAL = 0; public static final Integer MONTHLY_INTERVAL = 1; public static final Integer QUARTERLY_INTERVAL = 3; @@ -130,6 +138,7 @@ public class FixedDepositTest extends IntegrationTest { public static final Float THRESHOLD = 1.0f; private MockedStatic moneyHelperStatic; + private SchedulerJobHelper schedulerJobHelper; @BeforeEach public void setup() { @@ -138,8 +147,11 @@ public class FixedDepositTest extends IntegrationTest { this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.requestSpec.header("Fineract-Platform-TenantId", "default"); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec); this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec); this.financialActivityAccountHelper = new FinancialActivityAccountHelper(this.requestSpec); + this.globalConfigurationHelper = new GlobalConfigurationHelper(); TimeZone.setDefault(TimeZone.getTimeZone(Utils.TENANT_TIME_ZONE)); } @@ -2598,6 +2610,158 @@ public class FixedDepositTest extends IntegrationTest { FixedDepositAccountStatusChecker.verifyFixedDepositIsActive(fixedDepositAccountStatusHashMap); } + @Test + public void testCloseFixedDepositForCLOSURE_TYPE_REINVEST_withConfigurationMaturityInstruction() { + try { + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account liabilityAccount = this.accountHelper.createLiabilityAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + + this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec); + this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); + DateFormat currentDateFormat = new SimpleDateFormat("dd"); + + Calendar todaysDate = Calendar.getInstance(); + int currentYear = todaysDate.get(Calendar.YEAR); + todaysDate.set(currentYear, Calendar.JANUARY, 1); + final String VALID_FROM = dateFormat.format(todaysDate.getTime()); + todaysDate.set(currentYear + 1, Calendar.MARCH, 1); + final String VALID_TO = dateFormat.format(todaysDate.getTime()); + + todaysDate = Calendar.getInstance(); + Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime())); + todaysDate.set(currentYear, Calendar.JANUARY, 1); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); + dateFormat.format(todaysDate.getTime()); + monthDayFormat.format(todaysDate.getTime()); + + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(true)); + + LocalDate marchDate = LocalDate.of(currentYear + 1, 3, 1); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate); + + log.info("Submitted Date: {}", SUBMITTED_ON_DATE); + + final String accountingRule = ACCRUAL; + Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount, liabilityAccount, + incomeAccount, expenseAccount); + Assertions.assertNotNull(fixedDepositProductId); + + Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + + Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), + SUBMITTED_ON_DATE, WHOLE_TERM, Integer.valueOf(CLOSURE_TYPE_REINVEST)); + Assertions.assertNotNull(fixedDepositAccountId); + + this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE); + this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE); + + schedulerJobHelper.executeAndAwaitJob("Update Deposit Accounts Maturity details"); + + HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec, + this.responseSpec, fixedDepositAccountId.toString()); + + FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsClosed(fixedDepositAccountStatusHashMap); + } finally { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(false)); + } + + } + + @Test + public void testCloseFixedDepositForCLOSURE_TYPE_REINVEST() { + testClosureTypeReinvestVariants(CLOSURE_TYPE_REINVEST); + } + + @Test + public void testCloseFixedDepositForCLOSURE_TYPE_REINVEST_PRINCIPAL_ONLY() { + testClosureTypeReinvestVariants(CLOSURE_TYPE_REINVEST_PRINCIPAL_ONLY); + } + + public void testClosureTypeReinvestVariants(String reInvest) { + try { + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account liabilityAccount = this.accountHelper.createLiabilityAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + + this.fixedDepositProductHelper = new FixedDepositProductHelper(this.requestSpec, this.responseSpec); + this.fixedDepositAccountHelper = new FixedDepositAccountHelper(this.requestSpec, this.responseSpec); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", Locale.US); + DateFormat monthDayFormat = new SimpleDateFormat("dd MMM", Locale.US); + DateFormat currentDateFormat = new SimpleDateFormat("dd"); + + Calendar todaysDate = Calendar.getInstance(); + int currentYear = todaysDate.get(Calendar.YEAR); + todaysDate.set(currentYear, Calendar.JANUARY, 1); + final String VALID_FROM = dateFormat.format(todaysDate.getTime()); + todaysDate.set(currentYear + 1, Calendar.JANUARY, 1); + final String VALID_TO = dateFormat.format(todaysDate.getTime()); + + todaysDate = Calendar.getInstance(); + Integer currentDate = Integer.valueOf(currentDateFormat.format(todaysDate.getTime())); + todaysDate.set(currentYear, Calendar.JANUARY, 1); + final String SUBMITTED_ON_DATE = dateFormat.format(todaysDate.getTime()); + final String APPROVED_ON_DATE = dateFormat.format(todaysDate.getTime()); + dateFormat.format(todaysDate.getTime()); + monthDayFormat.format(todaysDate.getTime()); + + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(true)); + + LocalDate marchDate = LocalDate.of(currentYear + 1, 1, 1); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, marchDate); + + log.info("Submitted Date: {}", SUBMITTED_ON_DATE); + + final String accountingRule = ACCRUAL; + Integer fixedDepositProductId = createFixedDepositProduct(VALID_FROM, VALID_TO, accountingRule, assetAccount, liabilityAccount, + incomeAccount, expenseAccount); + Assertions.assertNotNull(fixedDepositProductId); + + Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + Assertions.assertNotNull(clientId); + + Integer fixedDepositAccountId = applyForFixedDepositApplication(clientId.toString(), fixedDepositProductId.toString(), + SUBMITTED_ON_DATE, WHOLE_TERM, "10000", "12"); + Assertions.assertNotNull(fixedDepositAccountId); + + this.fixedDepositAccountHelper.approveFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE); + this.fixedDepositAccountHelper.activateFixedDeposit(fixedDepositAccountId, APPROVED_ON_DATE); + + schedulerJobHelper.executeAndAwaitJob("Update Deposit Accounts Maturity details"); + + HashMap fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec, + this.responseSpec, fixedDepositAccountId.toString()); + + FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsMatured(fixedDepositAccountStatusHashMap); + + todaysDate.set(currentYear + 1, Calendar.JANUARY, 1); + final String CLOSED_ON_DATE = dateFormat.format(todaysDate.getTime()); + + Integer prematureClosureTransactionId = (Integer) this.fixedDepositAccountHelper.closeForFixedDeposit(fixedDepositAccountId, + CLOSED_ON_DATE, reInvest, null, CommonConstants.RESPONSE_RESOURCE_ID); + + fixedDepositAccountStatusHashMap = FixedDepositAccountStatusChecker.getStatusOfFixedDepositAccount(this.requestSpec, + this.responseSpec, fixedDepositAccountId.toString()); + + FixedDepositAccountStatusChecker.verifyFixedDepositAccountIsClosed(fixedDepositAccountStatusHashMap); + } finally { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(false)); + } + + } + private Integer createFixedDepositProduct(final String validFrom, final String validTo, final String accountingRule, Account... accounts) { log.info("------------------------------CREATING NEW FIXED DEPOSIT PRODUCT ---------------------------------------"); @@ -2606,6 +2770,8 @@ public class FixedDepositTest extends IntegrationTest { fixedDepositProductHelper = fixedDepositProductHelper.withAccountingRuleAsCashBased(accounts); } else if (accountingRule.equals(NONE)) { fixedDepositProductHelper = fixedDepositProductHelper.withAccountingRuleAsNone(); + } else if (accountingRule.equals(ACCRUAL)) { + fixedDepositProductHelper = fixedDepositProductHelper.withAccountingRuleAsAccrual(accounts); } final String fixedDepositProductJSON = fixedDepositProductHelper.withPeriodRangeChart() // .build(validFrom, validTo, true); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingTest.java index efdc69d2b..a8e7a15e5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SavingsInterestPostingTest.java @@ -340,6 +340,92 @@ public class SavingsInterestPostingTest { } } + @Test + public void testPostInterestNotZero() { + try { + final String amountDeposit = "1000"; + final String amountWithdrawal = "1000"; + + 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 Integer clientId = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2025"); + final LocalDate startDate = LocalDate.of(LocalDate.now(Utils.getZoneIdOfTenant()).getYear(), 1, 1); + final String startStr = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(startDate); + + final Integer accountId = savingsAccountHelper.applyForSavingsApplicationOnDate(clientId, productId, + SavingsAccountHelper.ACCOUNT_TYPE_INDIVIDUAL, startStr); + savingsAccountHelper.approveSavingsOnDate(accountId, startStr); + 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); + + schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME); + + List txsFebruary = getInterestTransactions(accountId); // OBTENER EL POSTEO DEL INTEREST + + 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 String withdrawal = DateTimeFormatter.ofPattern("dd MMMM yyyy", Locale.US).format(withdrawalDate); + + BigDecimal runningBalance = new BigDecimal(txsFebruary.get(0).get("runningBalance").toString()); + String withdrawalRunning = runningBalance.setScale(2, RoundingMode.HALF_UP).toString(); + + savingsAccountHelper.withdrawalFromSavingsAccount(accountId, withdrawalRunning, withdrawal, + CommonConstants.RESPONSE_RESOURCE_ID); + 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); + + schedulerJobHelper.executeAndAwaitJob(POST_INTEREST_JOB_NAME); + + List txs = getInterestTransactions(accountId); // CON ESTE DEBEMOS DE VALIDAR QUE EL DIA DE MARZO + // NO SE TENGA POSTEO EN CERO + for (HashMap tx : txs) { + BigDecimal amt = BigDecimal.valueOf(((Double) tx.get("amount"))); + @SuppressWarnings("unchecked") + Map typeMap = (Map) tx.get("transactionType"); + SavingsAccountTransactionType type = SavingsAccountTransactionType.fromInt(((Double) typeMap.get("id")).intValue()); + if (type.isOverDraftInterestPosting()) { + long days = ChronoUnit.DAYS.between(withdrawalDate, marchDate); + BigDecimal decimalsss = new BigDecimal(txsFebruary.get(0).get("runningBalance").toString()) + .subtract(runningBalance.setScale(2, RoundingMode.HALF_UP)); + BigDecimal withdraw = new BigDecimal(amountWithdrawal); + BigDecimal res = withdraw.subtract(decimalsss); + BigDecimal expected = calcOverdraftPosting(productHelper, res.toString(), days); + Assertions.assertEquals(expected, amt); + } + } + + Assertions.assertEquals(0L, countInterestOnDate(accountId, marchDate), "Expected exactly one INTEREST posting on posting date"); + Assertions.assertEquals(1L, countOverdraftOnDate(accountId, marchDate), + "Expected exactly one OVERDRAFT posting on posting date"); + + assertNoAccrualReversals(accountId); + } finally { + globalConfigurationHelper.updateGlobalConfiguration(GlobalConfigurationConstants.ENABLE_BUSINESS_DATE, + new PutGlobalConfigurationsRequest().enabled(false)); + } + } + private List getInterestTransactions(Integer savingsAccountId) { List all = savingsAccountHelper.getSavingsTransactions(savingsAccountId); List filtered = new ArrayList<>(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java index 68a29fc49..9c84b4f64 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositAccountHelper.java @@ -402,6 +402,17 @@ public class FixedDepositAccountHelper { getPrematureCloseForFixedDepositAccountAsJSON(closedOnDate, closureType, toSavingsId), jsonAttributeToGetBack); } + // TODO: Rewrite to use fineract-client instead! + // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // org.apache.fineract.client.models.PostLoansLoanIdRequest) + @Deprecated(forRemoval = true) + public Object closeForFixedDeposit(final Integer fixedDepositAccountId, final String closedOnDate, final String closureType, + final Integer toSavingsId, final String jsonAttributeToGetBack) { + LOG.info("--------------------- CLOSE FOR FIXED DEPOSIT ----------------------------"); + return performFixedDepositActions(createFixedDepositCalculateInterestURL(CLOSE_FIXED_DEPOSIT_COMMAND, fixedDepositAccountId), + getPrematureCloseForFixedDepositAccountAsJSON(closedOnDate, closureType, toSavingsId), jsonAttributeToGetBack); + } + // TODO: Rewrite to use fineract-client instead! // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest) diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java index 20cf1a91e..3d7af6583 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/fixeddeposit/FixedDepositProductHelper.java @@ -163,6 +163,9 @@ public class FixedDepositProductHelper { if (this.accountingRule.equals(CASH_BASED)) { map.putAll(getAccountMappingForCashBased()); } + if (this.accountingRule.equals(ACCRUAL_PERIODIC)) { + map.putAll(getAccountMappingForAccrualBased()); + } String FixedDepositProductCreateJson = new Gson().toJson(map); LOG.info("{}", FixedDepositProductCreateJson); @@ -397,6 +400,12 @@ public class FixedDepositProductHelper { return this; } + public FixedDepositProductHelper withAccountingRuleAsAccrual(final Account[] account_list) { + this.accountingRule = ACCRUAL_PERIODIC; + this.accountList = account_list; + return this; + } + public FixedDepositProductHelper withPeriodRangeChart() { this.chartSlabs = constructChartSlabWithPeriodRange(); return this; @@ -456,6 +465,41 @@ public class FixedDepositProductHelper { return map; } + // TODO: Rewrite to use fineract-client instead! + // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, + // org.apache.fineract.client.models.PostLoansLoanIdRequest) + @Deprecated(forRemoval = true) + private Map getAccountMappingForAccrualBased() { + final Map map = new HashMap<>(); + if (accountList != null) { + for (int i = 0; i < this.accountList.length; i++) { + if (this.accountList[i].getAccountType().equals(Account.AccountType.ASSET)) { + final String ID = this.accountList[i].getAccountID().toString(); + map.put("savingsReferenceAccountId", ID); + map.put("feesReceivableAccountId", ID); + map.put("penaltiesReceivableAccountId", ID); + } + if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) { + + final String ID = this.accountList[i].getAccountID().toString(); + map.put("savingsControlAccountId", ID); + map.put("transfersInSuspenseAccountId", ID); + map.put("interestPayableAccountId", ID); + } + if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) { + final String ID = this.accountList[i].getAccountID().toString(); + map.put("interestOnSavingsAccountId", ID); + } + if (this.accountList[i].getAccountType().equals(Account.AccountType.INCOME)) { + final String ID = this.accountList[i].getAccountID().toString(); + map.put("incomeFromFeeAccountId", ID); + map.put("incomeFromPenaltyAccountId", ID); + } + } + } + return map; + } + // TODO: Rewrite to use fineract-client instead! // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest)