Skip to content

Commit

Permalink
fix: Savings accrual when overdraft
Browse files Browse the repository at this point in the history
  • Loading branch information
Jose Alberto Hernandez committed Jun 27, 2024
1 parent 2861734 commit 21affbe
Show file tree
Hide file tree
Showing 15 changed files with 137 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ public void validateForSavingsProductCreate(final String json, DepositAccountTyp
Locale.getDefault());
baseDataValidator.reset().parameter(accountingRuleParamName).value(accountingRuleType).notNull().inMinMaxRange(1, 3);


if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
|| AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3419,6 +3419,15 @@ public CommandWrapperBuilder unblockSavingsAccount(final Long accountId) {
return this;
}

public CommandWrapperBuilder addAccrualsToSavingsAccount(final Long accountId) {
this.actionName = "ADD_ACCRUALS";
this.entityName = "SAVINGSACCOUNT";
this.savingsId = accountId;
this.entityId = null;
this.href = "/savingsaccounts/" + accountId + "?command=addAccrualTransactions";
return this;
}

public CommandWrapperBuilder disableAdHoc(Long adHocId) {
this.actionName = "DISABLE";
this.entityName = "ADHOC";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@ public CommandProcessingResult executeCommand(final CommandWrapper wrapper, fina
result = commandSourceService.processCommand(findCommandHandler(wrapper), command, commandSource, user, isApprovedByChecker,
isMakerChecker);
} catch (Throwable t) { // NOSONAR
t.printStackTrace();
RuntimeException mappable = ErrorHandler.getMappable(t);
ErrorInfo errorInfo = commandSourceService.generateErrorInfo(mappable);
Integer statusCode = errorInfo.getStatusCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class SavingsApiConstants {
public static final String COMMAND_BLOCK_DEBIT = "blockDebit";
public static final String COMMAND_UNBLOCK_DEBIT = "unblockDebit";
public static final String COMMAND_UNBLOCK_CREDIT = "unblockCredit";
public static final String COMMAND_ADD_ACCRUAL_TRANSACTION = "addAccrualTransactions";

// general
public static final String localeParamName = "locale";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,9 @@ private String handleCommands(Long accountId, String externalId, String commandP
} else if (CommandParameterUtil.is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) {
final CommandWrapper commandRequest = builder.unblockSavingsAccount(accountId).build();
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
} else if (CommandParameterUtil.is(commandParam, SavingsApiConstants.COMMAND_ADD_ACCRUAL_TRANSACTION)) {
final CommandWrapper commandRequest = builder.addAccrualsToSavingsAccount(accountId).build();
result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
}

if (result == null) {
Expand All @@ -595,7 +598,8 @@ private String handleCommands(Long accountId, String externalId, String commandP
"postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer",
SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT,
SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT,
SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT });
SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT,
SavingsApiConstants.COMMAND_ADD_ACCRUAL_TRANSACTION });
}

return toApiJsonSerializer.serialize(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsDepositBusinessEvent;
import org.apache.fineract.infrastructure.event.business.domain.savings.transaction.SavingsWithdrawalBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
Expand Down Expand Up @@ -339,7 +340,7 @@ public void postInterest(SavingsAccount account, final MathContext mc, final Loc
final List<PostingPeriod> postingPeriods = account.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill,
postReversals);
log.info("postInterest {}", postingPeriods.size());
log.debug("postInterest {}", postingPeriods.size());

MonetaryCurrency currency = account.getCurrency();
Money interestPostedToDate = Money.zero(currency);
Expand All @@ -359,11 +360,11 @@ public void postInterest(SavingsAccount account, final MathContext mc, final Loc
}

for (final PostingPeriod interestPostingPeriod : postingPeriods) {
log.info(" period: {}", interestPostingPeriod.dateOfPostingTransaction());
log.debug(" period: {}", interestPostingPeriod.dateOfPostingTransaction());

final LocalDate interestPostingTransactionDate = interestPostingPeriod.dateOfPostingTransaction();
final Money interestEarnedToBePostedForPeriod = interestPostingPeriod.getInterestEarned();
log.info(" interestEarnedToBePostedForPeriod: {}", interestEarnedToBePostedForPeriod.toString());
log.debug(" interestEarnedToBePostedForPeriod: {}", interestEarnedToBePostedForPeriod.toString());

if (!interestPostingTransactionDate.isAfter(interestPostingUpToDate)) {
interestPostedToDate = interestPostedToDate.plus(interestEarnedToBePostedForPeriod);
Expand Down Expand Up @@ -394,13 +395,17 @@ public void postInterest(SavingsAccount account, final MathContext mc, final Loc
account.addTransaction(newPostingTransaction);
}
if (account.savingsProduct().isAccrualBasedAccountingEnabled()) {
SavingsAccountTransaction accrualTransaction = SavingsAccountTransaction.accrual(account, account.office(),
interestPostingTransactionDate, interestEarnedToBePostedForPeriod,
interestPostingPeriod.isUserPosting());
if (backdatedTxnsAllowedTill) {
account.addTransactionToExisting(accrualTransaction);
if (MathUtil.isGreaterThanZero(interestEarnedToBePostedForPeriod)) {
SavingsAccountTransaction accrualTransaction = SavingsAccountTransaction.accrual(account, account.office(),
interestPostingTransactionDate, interestEarnedToBePostedForPeriod,
interestPostingPeriod.isUserPosting());
if (backdatedTxnsAllowedTill) {
account.addTransactionToExisting(accrualTransaction);
} else {
account.addTransaction(accrualTransaction);
}
} else {
account.addTransaction(accrualTransaction);
log.info("Accrual for Overdraft interest");
}
}
if (applyWithHoldTax) {
Expand All @@ -416,7 +421,7 @@ public void postInterest(SavingsAccount account, final MathContext mc, final Loc
} else {
correctionRequired = postingTransaction.hasNotAmount(interestEarnedToBePostedForPeriod.negated());
}
log.info(" correctionRequired {}", correctionRequired);
log.debug(" correctionRequired {}", correctionRequired);
if (correctionRequired) {
boolean applyWithHoldTaxForOldTransaction = false;
postingTransaction.reverse();
Expand Down Expand Up @@ -451,7 +456,9 @@ public void postInterest(SavingsAccount account, final MathContext mc, final Loc
account.addTransaction(reversal);
}
}
if (account.savingsProduct().isAccrualBasedAccountingEnabled()) {
if (account.savingsProduct().isAccrualBasedAccountingEnabled()
&& MathUtil.isGreaterThanZero(interestEarnedToBePostedForPeriod)) {
log.info("TX2: {}", interestEarnedToBePostedForPeriod.getAmount());
SavingsAccountTransaction accrualTransaction = SavingsAccountTransaction.accrual(account, account.office(),
interestPostingTransactionDate, interestEarnedToBePostedForPeriod,
interestPostingPeriod.isUserPosting());
Expand All @@ -460,6 +467,8 @@ public void postInterest(SavingsAccount account, final MathContext mc, final Loc
} else {
account.addTransaction(accrualTransaction);
}
} else {
log.info("Accrual for Overdraft2 interest");
}
if (applyWithHoldTaxForOldTransaction) {
account.createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* 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.handler;

import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.savings.service.SavingsAccrualWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@CommandType(entity = "SAVINGSACCOUNT", action = "ADD_ACCRUALS")
@RequiredArgsConstructor
public class AddAccrualTransactionsToSavingsAccountCommandHandler implements NewCommandSourceHandler {

private final SavingsAccrualWritePlatformService savingsAccrualWritePlatformService;

@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {

return savingsAccrualWritePlatformService.addAccrualEntries(command.getSavingsId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,19 +168,16 @@ private void postInterest(List<SavingsAccountData> savingsAccounts, int threadPo
}

while (queue.size() <= QUEUE_SIZE) {
log.debug("Fetching while threads are running!..:: this is not supposed to run........");
savingsAccounts = Collections.synchronizedList(this.savingAccountReadPlatformService
.retrieveAllSavingsDataForInterestPosting(backdatedTxnsAllowedTill, pageSize, ACTIVE.getValue(), maxId));
if (savingsAccounts.isEmpty()) {
break;
}
maxId = savingsAccounts.get(savingsAccounts.size() - 1).getId();
log.debug("Add to the Queue");
queue.add(savingsAccounts);
}

checkCompletion(responses);
log.debug("Queue size {}", queue.size());
}

private <T> List<T> safeSubList(List<T> list, int fromIndex, int toIndex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeRepositoryWrapper;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountDomainService;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ public List<SavingsAccountData> retrieveAllSavingsDataForInterestPosting(final b
new Object[] { maxSavingsId, status, pageSize, yesterday });
for (SavingsAccountData savingsAccountData : savingsAccountDataList) {
this.savingAccountAssembler.assembleSavings(savingsAccountData);
log.info(" to process {} as {}", savingsAccountData.getAccountNo(), savingsAccountData.getDepositType().getValue());
log.debug(" to process {} as {}", savingsAccountData.getAccountNo(), savingsAccountData.getDepositType().getValue());
}
return savingsAccountDataList;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.time.LocalDate;
import java.util.Collection;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.MultiException;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge;
Expand All @@ -34,4 +35,6 @@ public interface SavingsAccrualWritePlatformService {
SavingsAccountTransaction addSavingsChargeAccrualTransaction(SavingsAccount savingsAccount, SavingsAccountCharge savingsAccountCharge,
LocalDate transactionDate);

CommandProcessingResult addAccrualEntries(Long savingsAccountId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
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;
Expand Down Expand Up @@ -92,6 +95,37 @@ public void addAccrualEntries(LocalDate tillDate) throws JobExecutionException {
}
}

@Transactional
@Override
public CommandProcessingResult addAccrualEntries(Long savingsAccountId) {
SavingsAccount savingsAccount = savingsAccountAssembler.assembleFrom(savingsAccountId, false);
final LocalDate tillDate = DateUtils.getBusinessLocalDate();
final Collection<SavingsAccrualData> savingsAccrualData = savingsAccountReadPlatformService.retrievePeriodicAccrualData(tillDate,
savingsAccount);
final Integer financialYearBeginningMonth = configurationDomainService.retrieveFinancialYearBeginningMonth();
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd();
final MathContext mc = MoneyHelper.getMathContext();

List<Throwable> errors = new ArrayList<>();
for (SavingsAccrualData savingsAccrual : savingsAccrualData) {
try {
LocalDate fromDate = savingsAccrual.getAccruedTill();
if (fromDate == null) {
fromDate = savingsAccount.getActivationDate();
}
log.debug("Processing savings account {} from date {} till date {}", savingsAccrual.getAccountNo(), fromDate, tillDate);
addAccrualTransactions(savingsAccount, fromDate, tillDate, financialYearBeginningMonth,
isSavingsInterestPostingAtCurrentPeriodEnd, mc);
} catch (Exception e) {
log.error("Failed to add accrual transaction for savings {} : {}", savingsAccrual.getAccountNo(), e.getMessage());
errors.add(e.getCause());
}
}

return CommandProcessingResult.empty();
}

@Override
public boolean isChargeToBeRecognizedAsAccrual(final Collection<Long> chargeIds, final SavingsAccountCharge savingsAccountCharge) {
if (chargeIds.isEmpty()) {
Expand Down Expand Up @@ -165,13 +199,15 @@ private void addAccrualTransactions(SavingsAccount savingsAccount, final LocalDa

LocalDate accruedTillDate = fromDate;
for (PostingPeriod period : allPostingPeriods) {
period.calculateInterest(compoundInterestValues);
log.debug(" period {} {} : {} {}", period.getPeriodInterval().startDate(), period.getPeriodInterval().endDate(),
period.getInterestEarned());
if (!accrualTransactionDates.contains(period.getPeriodInterval().endDate())) {
SavingsAccountTransaction savingsAccountTransaction = SavingsAccountTransaction.accrual(savingsAccount,
savingsAccount.office(), period.getPeriodInterval().endDate(), period.getInterestEarned(), false);
savingsAccount.addTransaction(savingsAccountTransaction);
if (MathUtil.isGreaterThanZero(period.closingBalance())) {
period.calculateInterest(compoundInterestValues);
log.debug(" period {} {} : {}", period.getPeriodInterval().startDate(), period.getPeriodInterval().endDate(),
period.getInterestEarned());
if (!accrualTransactionDates.contains(period.getPeriodInterval().endDate())) {
SavingsAccountTransaction savingsAccountTransaction = SavingsAccountTransaction.accrual(savingsAccount,
savingsAccount.office(), period.getPeriodInterval().endDate(), period.getInterestEarned(), false);
savingsAccount.addTransaction(savingsAccountTransaction);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ public class SavingsProductDataValidator {
nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
SavingsApiConstants.minRequiredBalanceParamName, SavingsApiConstants.enforceMinRequiredBalanceParamName,
SavingsApiConstants.maxAllowedLienLimitParamName, SavingsApiConstants.lienAllowedParamName,
minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName));
minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName,
SavingsApiConstants.accrualChargesParamName));

public void validateForCreate(final String json) {

Expand Down
Loading

0 comments on commit 21affbe

Please sign in to comment.