Feat: Filter Transactions from last interest posting date (#1933)

This commit is contained in:
Benura Abeywardena
2021-11-25 01:59:53 +05:30
committed by GitHub
parent d82560d7b3
commit 48e6a41b98
40 changed files with 1338 additions and 319 deletions
@@ -104,4 +104,10 @@ public interface ConfigurationDomainService {
String getAccountMappingForCharge(); String getAccountMappingForCharge();
boolean isNextDayFixedDepositInterestTransferEnabledForPeriodEnd(); boolean isNextDayFixedDepositInterestTransferEnabledForPeriodEnd();
boolean retrievePivotDateConfig();
boolean isRelaxingDaysConfigForPivotDateEnabled();
Long retrieveRelaxingDaysConfigForPivotDate();
} }
@@ -343,6 +343,31 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
return value; return value;
} }
@Override
public boolean retrievePivotDateConfig() {
final String propertyName = "allow-backdated-transaction-before-interest-posting";
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
return !property.isEnabled();
}
@Override
public boolean isRelaxingDaysConfigForPivotDateEnabled() {
final String propertyName = "allow-backdated-transaction-before-interest-posting-date-for-days";
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
return property.isEnabled();
}
@Override
public Long retrieveRelaxingDaysConfigForPivotDate() {
final String propertyName = "allow-backdated-transaction-before-interest-posting-date-for-days";
final GlobalConfigurationPropertyData property = getGlobalConfigurationPropertyData(propertyName);
if (property.getValue() == null) {
return Long.valueOf(0);
}
return property.getValue();
}
private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) { private GlobalConfigurationPropertyData getGlobalConfigurationPropertyData(final String propertyName) {
String identifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier(); String identifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
String key = identifier + "_" + propertyName; String key = identifier + "_" + propertyName;
@@ -427,6 +427,7 @@ public class InteropServiceImpl implements InteropService {
LocalDate transactionDate = transactionDateTime.toLocalDate(); LocalDate transactionDate = transactionDateTime.toLocalDate();
DateTimeFormatter fmt = getDateTimeFormatter(command); DateTimeFormatter fmt = getDateTimeFormatter(command);
SavingsAccountTransaction transaction; SavingsAccountTransaction transaction;
final boolean backdatedTxnsAllowedTill = false;
if (isDebit) { if (isDebit) {
SavingsAccountTransaction holdTransaction = findTransaction(savingsAccount, transferCode, AMOUNT_HOLD.getValue()); SavingsAccountTransaction holdTransaction = findTransaction(savingsAccount, transferCode, AMOUNT_HOLD.getValue());
@@ -457,10 +458,11 @@ public class InteropServiceImpl implements InteropService {
SavingsTransactionBooleanValues transactionValues = new SavingsTransactionBooleanValues(false, true, true, false, false); SavingsTransactionBooleanValues transactionValues = new SavingsTransactionBooleanValues(false, true, true, false, false);
transaction = savingsAccountService.handleWithdrawal(savingsAccount, fmt, transactionDate, request.getAmount().getAmount(), transaction = savingsAccountService.handleWithdrawal(savingsAccount, fmt, transactionDate, request.getAmount().getAmount(),
instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null), instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null),
transactionValues); transactionValues, backdatedTxnsAllowedTill);
} else { } else {
transaction = savingsAccountService.handleDeposit(savingsAccount, fmt, transactionDate, request.getAmount().getAmount(), transaction = savingsAccountService.handleDeposit(savingsAccount, fmt, transactionDate, request.getAmount().getAmount(),
instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null), false, true); instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null), false, true,
backdatedTxnsAllowedTill);
} }
String note = request.getNote(); String note = request.getNote();
@@ -64,10 +64,11 @@ public class AccountTransferDetailAssembler {
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) { public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName); final Long fromSavingsId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId); final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsId, false);
final boolean backdatedTxnsAllowedTill = false;
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, backdatedTxnsAllowedTill);
return assembleSavingsToSavingsTransfer(command, fromSavingsAccount, toSavingsAccount); return assembleSavingsToSavingsTransfer(command, fromSavingsAccount, toSavingsAccount);
@@ -76,7 +77,8 @@ public class AccountTransferDetailAssembler {
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) { public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
final Long fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); final Long fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId); final boolean backdatedTxnsAllowedTill = false;
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId, backdatedTxnsAllowedTill);
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId); final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
@@ -89,9 +91,9 @@ public class AccountTransferDetailAssembler {
final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId); final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
final boolean backdatedTxnsAllowedTill = false;
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
return assembleLoanToSavingsTransfer(command, fromLoanAccount, toSavingsAccount); return assembleLoanToSavingsTransfer(command, fromLoanAccount, toSavingsAccount);
} }
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.account.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.PortfolioAccountType;
@@ -41,6 +42,8 @@ public interface AccountTransfersReadPlatformService {
Collection<Long> fetchPostInterestTransactionIds(Long accountId); Collection<Long> fetchPostInterestTransactionIds(Long accountId);
Collection<Long> fetchPostInterestTransactionIdsWithPivotDate(Long accountId, Date pivotDate);
AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType, AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType); Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
@@ -26,6 +26,7 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
@@ -265,6 +266,16 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
return transactionId; return transactionId;
} }
@Override
public Collection<Long> fetchPostInterestTransactionIdsWithPivotDate(final Long accountId, final Date pivotDate) {
final String sql = "select att.from_savings_transaction_id from m_account_transfer_transaction att inner join m_account_transfer_details atd on atd.id = att.account_transfer_details_id where atd.from_savings_account_id=? and att.is_reversed = 0 and atd.transfer_type = ? and att.transaction_date >= ?";
final List<Long> transactionIds = this.jdbcTemplate.queryForList(sql, Long.class, accountId,
AccountTransferType.INTEREST_TRANSFER.getValue(), pivotDate);
return transactionIds;
}
private static final class AccountTransfersMapper implements RowMapper<AccountTransferData> { private static final class AccountTransfersMapper implements RowMapper<AccountTransferData> {
private final String schemaSql; private final String schemaSql;
@@ -131,22 +131,24 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
boolean isAccountTransfer = true; boolean isAccountTransfer = true;
Long fromLoanAccountId = null; Long fromLoanAccountId = null;
boolean isWithdrawBalance = false; boolean isWithdrawBalance = false;
final boolean backdatedTxnsAllowedTill = false;
if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) { if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId); final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId,
backdatedTxnsAllowedTill);
final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance); isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance);
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt, final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
transactionDate, transactionAmount, paymentDetail, transactionBooleanValues); transactionDate, transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill);
final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toSavingsId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, backdatedTxnsAllowedTill);
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate,
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction); transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
if (!fromSavingsAccount.getCurrency().getCode().equals(toSavingsAccount.getCurrency().getCode())) { if (!fromSavingsAccount.getCurrency().getCode().equals(toSavingsAccount.getCurrency().getCode())) {
throw new DifferentCurrenciesException(fromSavingsAccount.getCurrency().getCode(), throw new DifferentCurrenciesException(fromSavingsAccount.getCurrency().getCode(),
@@ -161,12 +163,13 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
} else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) { } else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
// //
fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName); fromSavingsAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId); final SavingsAccount fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(fromSavingsAccountId,
backdatedTxnsAllowedTill);
final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance); isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance);
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt, final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
transactionDate, transactionAmount, paymentDetail, transactionBooleanValues); transactionDate, transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill);
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId); final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
@@ -194,10 +197,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null); new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate,
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction); transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command, final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command,
fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction); fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction);
@@ -288,6 +291,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
Long transferTransactionId = null; Long transferTransactionId = null;
final boolean isAccountTransfer = true; final boolean isAccountTransfer = true;
final boolean isRegularTransaction = accountTransferDTO.isRegularTransaction(); final boolean isRegularTransaction = accountTransferDTO.isRegularTransaction();
final boolean backdatedTxnsAllowedTill = false;
AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails(); AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails();
if (isSavingsToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) { if (isSavingsToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
// //
@@ -295,7 +299,8 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
Loan toLoanAccount = null; Loan toLoanAccount = null;
if (accountTransferDetails == null) { if (accountTransferDetails == null) {
if (accountTransferDTO.getFromSavingsAccount() == null) { if (accountTransferDTO.getFromSavingsAccount() == null) {
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId()); fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId(),
backdatedTxnsAllowedTill);
} else { } else {
fromSavingsAccount = accountTransferDTO.getFromSavingsAccount(); fromSavingsAccount = accountTransferDTO.getFromSavingsAccount();
this.savingsAccountAssembler.setHelpers(fromSavingsAccount); this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
@@ -321,7 +326,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), transactionBooleanValues); accountTransferDTO.getPaymentDetail(), transactionBooleanValues, backdatedTxnsAllowedTill);
LoanTransaction loanTransaction = null; LoanTransaction loanTransaction = null;
@@ -351,13 +356,14 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
SavingsAccount toSavingsAccount = null; SavingsAccount toSavingsAccount = null;
if (accountTransferDetails == null) { if (accountTransferDetails == null) {
if (accountTransferDTO.getFromSavingsAccount() == null) { if (accountTransferDTO.getFromSavingsAccount() == null) {
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId()); fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId(),
backdatedTxnsAllowedTill);
} else { } else {
fromSavingsAccount = accountTransferDTO.getFromSavingsAccount(); fromSavingsAccount = accountTransferDTO.getFromSavingsAccount();
this.savingsAccountAssembler.setHelpers(fromSavingsAccount); this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
} }
if (accountTransferDTO.getToSavingsAccount() == null) { if (accountTransferDTO.getToSavingsAccount() == null) {
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId()); toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId(), false);
} else { } else {
toSavingsAccount = accountTransferDTO.getToSavingsAccount(); toSavingsAccount = accountTransferDTO.getToSavingsAccount();
this.savingsAccountAssembler.setHelpers(toSavingsAccount); this.savingsAccountAssembler.setHelpers(toSavingsAccount);
@@ -383,11 +389,11 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
accountTransferDTO.getFmt(), transactionDate, accountTransferDTO.getTransactionAmount(), accountTransferDTO.getFmt(), transactionDate, accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), transactionBooleanValues); accountTransferDTO.getPaymentDetail(), transactionBooleanValues, backdatedTxnsAllowedTill);
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
accountTransferDTO.getFmt(), transactionDate, accountTransferDTO.getTransactionAmount(), accountTransferDTO.getFmt(), transactionDate, accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction); accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
accountTransferDetails = this.accountTransferAssembler.assembleSavingsToSavingsTransfer(accountTransferDTO, fromSavingsAccount, accountTransferDetails = this.accountTransferAssembler.assembleSavingsToSavingsTransfer(accountTransferDTO, fromSavingsAccount,
toSavingsAccount, withdrawal, deposit); toSavingsAccount, withdrawal, deposit);
@@ -405,7 +411,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
fromLoanAccount = accountTransferDTO.getLoan(); fromLoanAccount = accountTransferDTO.getLoan();
this.loanAccountAssembler.setHelpers(fromLoanAccount); this.loanAccountAssembler.setHelpers(fromLoanAccount);
} }
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId()); toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId(), backdatedTxnsAllowedTill);
} else { } else {
fromLoanAccount = accountTransferDetails.fromLoanAccount(); fromLoanAccount = accountTransferDetails.fromLoanAccount();
this.loanAccountAssembler.setHelpers(fromLoanAccount); this.loanAccountAssembler.setHelpers(fromLoanAccount);
@@ -426,7 +432,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(), accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction); accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(accountTransferDTO, fromLoanAccount, accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(accountTransferDTO, fromLoanAccount,
toSavingsAccount, deposit, loanTransaction); toSavingsAccount, deposit, loanTransaction);
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails); this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
@@ -523,6 +529,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId); final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(fromLoanAccountId).getPaidInAdvance(); BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(fromLoanAccountId).getPaidInAdvance();
final boolean backdatedTxnsAllowedTill = false;
if (overpaid == null || overpaid.compareTo(BigDecimal.ZERO) == 0 ? Boolean.TRUE if (overpaid == null || overpaid.compareTo(BigDecimal.ZERO) == 0 ? Boolean.TRUE
: Boolean.FALSE || transactionAmount.floatValue() > overpaid.floatValue()) { : Boolean.FALSE || transactionAmount.floatValue() > overpaid.floatValue()) {
@@ -536,10 +543,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null); new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName); final Long toSavingsAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId); final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsAccountId, backdatedTxnsAllowedTill);
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount, fmt, transactionDate,
transactionAmount, paymentDetail, true, true); transactionAmount, paymentDetail, true, true, backdatedTxnsAllowedTill);
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command, final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command,
fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction); fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction);
@@ -111,12 +111,14 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
guarantorCommand.validateForCreate(); guarantorCommand.validateForCreate();
validateLoanStatus(loan); validateLoanStatus(loan);
final List<GuarantorFundingDetails> guarantorFundingDetails = new ArrayList<>(); final List<GuarantorFundingDetails> guarantorFundingDetails = new ArrayList<>();
final boolean backdatedTxnsAllowedTill = false;
AccountAssociations accountAssociations = null; AccountAssociations accountAssociations = null;
if (guarantorCommand.getSavingsId() != null) { if (guarantorCommand.getSavingsId() != null) {
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(guarantorCommand.getSavingsId()); final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(guarantorCommand.getSavingsId(),
backdatedTxnsAllowedTill);
validateGuarantorSavingsAccountActivationDateWithLoanSubmittedOnDate(loan, savingsAccount); validateGuarantorSavingsAccountActivationDateWithLoanSubmittedOnDate(loan, savingsAccount);
accountAssociations = AccountAssociations.associateSavingsAccount(loan, savingsAccount, accountAssociations = AccountAssociations.associateSavingsAccount(loan, savingsAccount,
AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue(), true); AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue(), backdatedTxnsAllowedTill);
GuarantorFundingDetails fundingDetails = new GuarantorFundingDetails(accountAssociations, GuarantorFundingDetails fundingDetails = new GuarantorFundingDetails(accountAssociations,
GuarantorFundStatusType.ACTIVE.getValue(), guarantorCommand.getAmount()); GuarantorFundStatusType.ACTIVE.getValue(), guarantorCommand.getAmount());
@@ -533,6 +533,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
// Save linked account information // Save linked account information
SavingsAccount savingsAccount; SavingsAccount savingsAccount;
final boolean backdatedTxnsAllowedTill = false;
AccountAssociations accountAssociations; AccountAssociations accountAssociations;
final Long savingsAccountId = command.longValueOfParameterNamed("linkAccountId"); final Long savingsAccountId = command.longValueOfParameterNamed("linkAccountId");
if (savingsAccountId != null) { if (savingsAccountId != null) {
@@ -551,8 +552,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
} }
if (gsimClientMembers.contains(BigDecimal.valueOf(newLoanApplication.getClientId()))) { if (gsimClientMembers.contains(BigDecimal.valueOf(newLoanApplication.getClientId()))) {
savingsAccount = this.savingsAccountAssembler savingsAccount = this.savingsAccountAssembler.assembleFrom(
.assembleFrom(clientAccountMappings.get(BigDecimal.valueOf(newLoanApplication.getClientId())).longValue()); clientAccountMappings.get(BigDecimal.valueOf(newLoanApplication.getClientId())).longValue(),
backdatedTxnsAllowedTill);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication); this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication);
boolean isActive = true; boolean isActive = true;
@@ -565,7 +567,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
} }
} else { } else {
savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId); savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId, backdatedTxnsAllowedTill);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication); this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication);
boolean isActive = true; boolean isActive = true;
accountAssociations = AccountAssociations.associateSavingsAccount(newLoanApplication, savingsAccount, accountAssociations = AccountAssociations.associateSavingsAccount(newLoanApplication, savingsAccount,
@@ -1156,6 +1158,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
// Save linked account information // Save linked account information
final String linkAccountIdParamName = "linkAccountId"; final String linkAccountIdParamName = "linkAccountId";
final boolean backdatedTxnsAllowedTill = false;
final Long savingsAccountId = command.longValueOfParameterNamed(linkAccountIdParamName); final Long savingsAccountId = command.longValueOfParameterNamed(linkAccountIdParamName);
AccountAssociations accountAssociations = this.accountAssociationsRepository.findByLoanIdAndType(loanId, AccountAssociations accountAssociations = this.accountAssociationsRepository.findByLoanIdAndType(loanId,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue()); AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
@@ -1181,7 +1184,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
} }
} }
if (isModified) { if (isModified) {
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId); final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId,
backdatedTxnsAllowedTill);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, existingLoanApplication); this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, existingLoanApplication);
if (accountAssociations == null) { if (accountAssociations == null) {
boolean isActive = true; boolean isActive = true;
@@ -86,6 +86,7 @@ public class SavingsApiConstants {
public static final String transactionLowerLimitParamName = "transactionLowerLimit"; public static final String transactionLowerLimitParamName = "transactionLowerLimit";
public static final String retailEntriesParamName = "retailEntries"; public static final String retailEntriesParamName = "retailEntries";
public static final String childAccountIdParamName = "childAccountId"; public static final String childAccountIdParamName = "childAccountId";
public static final String interestPostedTillDate = "interestPostedTillDate";
public static final String groupIdParamName = "groupId"; public static final String groupIdParamName = "groupId";
public static final String productIdParamName = "productId"; public static final String productIdParamName = "productId";
@@ -57,7 +57,7 @@ public class SavingsApiSetConstants extends SavingsApiConstants {
"interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions", "interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions",
"interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee",
"annualFee", onHoldFundsParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName, "annualFee", onHoldFundsParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
datatables, savingsAmountOnHold, accountMappingForPaymentParamName)); datatables, savingsAmountOnHold, accountMappingForPaymentParamName, interestPostedTillDate));
protected static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>( protected static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName, Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName,
@@ -44,12 +44,14 @@ public class SavingsAccountSummaryData implements Serializable {
private final BigDecimal interestNotPosted; private final BigDecimal interestNotPosted;
private final LocalDate lastInterestCalculationDate; private final LocalDate lastInterestCalculationDate;
private final BigDecimal availableBalance; private final BigDecimal availableBalance;
private final LocalDate interestPostedTillDate;
public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals, public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals,
final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned, final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned,
final BigDecimal totalInterestPosted, final BigDecimal accountBalance, final BigDecimal totalFeeCharge, final BigDecimal totalInterestPosted, final BigDecimal accountBalance, final BigDecimal totalFeeCharge,
final BigDecimal totalPenaltyCharge, final BigDecimal totalOverdraftInterestDerived, final BigDecimal totalWithholdTax, final BigDecimal totalPenaltyCharge, final BigDecimal totalOverdraftInterestDerived, final BigDecimal totalWithholdTax,
final BigDecimal interestNotPosted, final LocalDate lastInterestCalculationDate, final BigDecimal availableBalance) { final BigDecimal interestNotPosted, final LocalDate lastInterestCalculationDate, final BigDecimal availableBalance,
final LocalDate interestPostedTillDate) {
this.currency = currency; this.currency = currency;
this.totalDeposits = totalDeposits; this.totalDeposits = totalDeposits;
this.totalWithdrawals = totalWithdrawals; this.totalWithdrawals = totalWithdrawals;
@@ -65,5 +67,6 @@ public class SavingsAccountSummaryData implements Serializable {
this.interestNotPosted = interestNotPosted; this.interestNotPosted = interestNotPosted;
this.lastInterestCalculationDate = lastInterestCalculationDate; this.lastInterestCalculationDate = lastInterestCalculationDate;
this.availableBalance = availableBalance; this.availableBalance = availableBalance;
this.interestPostedTillDate = interestPostedTillDate;
} }
} }
@@ -44,6 +44,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -56,6 +57,7 @@ import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction;
import org.apache.fineract.portfolio.savings.exception.TransactionBeforePivotDateNotAllowed;
import org.apache.fineract.useradministration.domain.AppUser; import org.apache.fineract.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -67,10 +69,31 @@ public class SavingsAccountTransactionDataValidator {
private static final Set<String> SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>( private static final Set<String> SAVINGS_ACCOUNT_HOLD_AMOUNT_REQUEST_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(transactionDateParamName, SavingsApiConstants.dateFormatParamName, SavingsApiConstants.localeParamName, Arrays.asList(transactionDateParamName, SavingsApiConstants.dateFormatParamName, SavingsApiConstants.localeParamName,
transactionAmountParamName)); transactionAmountParamName));
private final ConfigurationDomainService configurationDomainService;
@Autowired @Autowired
public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) { public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper,
final ConfigurationDomainService configurationDomainService) {
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
this.configurationDomainService = configurationDomainService;
}
public void validateTransactionWithPivotDate(final LocalDate transactionDate, final SavingsAccount savingsAccount) {
final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig();
final boolean isRelaxingDaysConfigOn = this.configurationDomainService.isRelaxingDaysConfigForPivotDateEnabled();
final Date lastInterestPostingDate = savingsAccount.getSummary().getInterestPostedTillDate();
if (backdatedTxnsAllowedTill && lastInterestPostingDate != null) {
LocalDate pivotDate = LocalDate.ofInstant(lastInterestPostingDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
if (isRelaxingDaysConfigOn) {
pivotDate = pivotDate.minusDays(this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate());
}
if (pivotDate.isAfter(transactionDate)) {
throw new TransactionBeforePivotDateNotAllowed(transactionDate, pivotDate);
}
}
} }
public void validate(final JsonCommand command) { public void validate(final JsonCommand command) {
@@ -181,8 +204,8 @@ public class SavingsAccountTransactionDataValidator {
} }
public SavingsAccountTransaction validateHoldAndAssembleForm(final String json, final SavingsAccount account, public SavingsAccountTransaction validateHoldAndAssembleForm(final String json, final SavingsAccount account, final AppUser createdUser,
final AppUser createdUser) { final boolean backdatedTxnsAllowedTill) {
if (StringUtils.isBlank(json)) { if (StringUtils.isBlank(json)) {
throw new InvalidJsonException(); throw new InvalidJsonException();
} }
@@ -210,7 +233,15 @@ public class SavingsAccountTransactionDataValidator {
if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) { if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId()); baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId());
} }
LocalDate lastTransactionDate = account.retrieveLastTransactionDate();
LocalDate lastTransactionDate = null;
if (!backdatedTxnsAllowedTill) {
lastTransactionDate = account.retrieveLastTransactionDate();
} else {
lastTransactionDate = account.retrieveLastTransactionDateWithPivotConfig();
}
// compare two dates now // compare two dates now
if (lastTransactionDate != null && transactionDate.isBefore(lastTransactionDate)) { if (lastTransactionDate != null && transactionDate.isBefore(lastTransactionDate)) {
baseDataValidator.parameter(SavingsApiConstants.dateParamName).value(lastTransactionDate).failWithCode( baseDataValidator.parameter(SavingsApiConstants.dateParamName).value(lastTransactionDate).failWithCode(
@@ -115,11 +115,11 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
boolean isWithdrawBalance = false; boolean isWithdrawBalance = false;
final boolean backdatedTxnsAllowedTill = false;
SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
isRegularTransaction, applyWithdrawFee, isInterestTransfer, isWithdrawBalance); isRegularTransaction, applyWithdrawFee, isInterestTransfer, isWithdrawBalance);
return this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate, transactionAmount, paymentDetail, return this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate, transactionAmount, paymentDetail,
transactionBooleanValues); transactionBooleanValues, backdatedTxnsAllowedTill);
} }
@Transactional @Transactional
@@ -128,8 +128,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) { final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
boolean isRegularTransaction = false; boolean isRegularTransaction = false;
final boolean backdatedTxnsAllowedTill = false;
return this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, return this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail,
isAccountTransfer, isRegularTransaction); isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
} }
@Transactional @Transactional
@@ -146,8 +147,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final boolean isPreMatureClosure = false; final boolean isPreMatureClosure = false;
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
account.updateDepositAmount(transactionAmount); account.updateDepositAmount(transactionAmount);
final boolean backdatedTxnsAllowedTill = false;
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate,
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction); transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
final boolean isAnyActivationChargesDue = isAnyActivationChargesDue(account); final boolean isAnyActivationChargesDue = isAnyActivationChargesDue(account);
@@ -172,8 +174,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
final boolean isRegularTransaction) { final boolean isRegularTransaction) {
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
final boolean backdatedTxnsAllowedTill = false;
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate,
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction); transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -575,10 +578,11 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) { final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
final MonetaryCurrency currency = savingsAccount.getCurrency(); final MonetaryCurrency currency = savingsAccount.getCurrency();
final boolean backdatedTxnsAllowedTill = false;
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
} }
@@ -527,8 +527,9 @@ public class FixedDepositAccount extends SavingsAccount {
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
final boolean isInterestTransfer = false; final boolean isInterestTransfer = false;
final LocalDate postInterestOnDate = null; final LocalDate postInterestOnDate = null;
final boolean backdatedTxnsAllowedTill = false;
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate); isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
Money interestPostedToDate = Money.zero(this.currency); Money interestPostedToDate = Money.zero(this.currency);
@@ -562,11 +563,12 @@ public class FixedDepositAccount extends SavingsAccount {
} }
} }
} }
recalucateDailyBalanceDetails = applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails); recalucateDailyBalanceDetails = applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails,
backdatedTxnsAllowedTill);
if (recalucateDailyBalanceDetails) { if (recalucateDailyBalanceDetails) {
// update existing transactions so derived balance fields are // update existing transactions so derived balance fields are
// correct. // correct.
recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate); recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill);
} }
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
@@ -585,6 +587,7 @@ public class FixedDepositAccount extends SavingsAccount {
// post remaining interest // post remaining interest
final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate);
final boolean backdatedTxnsAllowedTill = false;
if (!remainigInterestToBePosted.isZero()) { if (!remainigInterestToBePosted.isZero()) {
final boolean postInterestAsOn = false; final boolean postInterestAsOn = false;
final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(), final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
@@ -593,12 +596,12 @@ public class FixedDepositAccount extends SavingsAccount {
recalucateDailyBalance = true; recalucateDailyBalance = true;
} }
recalucateDailyBalance = applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance); recalucateDailyBalance = applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill);
if (recalucateDailyBalance) { if (recalucateDailyBalance) {
// update existing transactions so derived balance fields are // update existing transactions so derived balance fields are
// correct. // correct.
recalculateDailyBalances(Money.zero(this.currency), accountCloseDate); recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill);
} }
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate); this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate);
@@ -639,19 +642,19 @@ public class FixedDepositAccount extends SavingsAccount {
@Override @Override
public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postInterestOnDate) { final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) {
final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
} }
@Override @Override
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postAsInterestOn) { final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill) {
final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postAsInterestOn); financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill);
} }
private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) {
@@ -539,14 +539,15 @@ public class RecurringDepositAccount extends SavingsAccount {
protected void processAccountUponActivation(final DateTimeFormatter fmt, final AppUser user) { protected void processAccountUponActivation(final DateTimeFormatter fmt, final AppUser user) {
final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance);
final boolean backdatedTxnsAllowedTill = false;
if (minRequiredOpeningBalance.isGreaterThanZero()) { if (minRequiredOpeningBalance.isGreaterThanZero()) {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(), final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(),
minRequiredOpeningBalance.getAmount(), null, new Date(), user, accountType); minRequiredOpeningBalance.getAmount(), null, new Date(), user, accountType);
deposit(transactionDTO); deposit(transactionDTO, backdatedTxnsAllowedTill);
// update existing transactions so derived balance fields are // update existing transactions so derived balance fields are
// correct. // correct.
recalculateDailyBalances(Money.zero(this.currency), DateUtils.getLocalDateOfTenant()); recalculateDailyBalances(Money.zero(this.currency), DateUtils.getLocalDateOfTenant(), backdatedTxnsAllowedTill);
} }
} }
@@ -640,8 +641,9 @@ public class RecurringDepositAccount extends SavingsAccount {
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
final boolean backdatedTxnsAllowedTill = false;
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer, final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate); isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
Money interestPostedToDate = Money.zero(this.currency); Money interestPostedToDate = Money.zero(this.currency);
@@ -673,11 +675,11 @@ public class RecurringDepositAccount extends SavingsAccount {
} }
} }
} }
applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails); applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails, backdatedTxnsAllowedTill);
if (recalucateDailyBalanceDetails) { if (recalucateDailyBalanceDetails) {
// update existing transactions so derived balance fields are // update existing transactions so derived balance fields are
// correct. // correct.
recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate); recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill);
} }
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
} }
@@ -693,6 +695,7 @@ public class RecurringDepositAccount extends SavingsAccount {
financialYearBeginningMonth); financialYearBeginningMonth);
boolean recalucateDailyBalance = false; boolean recalucateDailyBalance = false;
final boolean backdatedTxnsAllowedTill = false;
// post remaining interest // post remaining interest
final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate); final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate);
@@ -704,12 +707,12 @@ public class RecurringDepositAccount extends SavingsAccount {
recalucateDailyBalance = true; recalucateDailyBalance = true;
} }
applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance); applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill);
if (recalucateDailyBalance) { if (recalucateDailyBalance) {
// update existing transactions so derived balance fields are // update existing transactions so derived balance fields are
// correct. // correct.
recalculateDailyBalances(Money.zero(this.currency), accountCloseDate); recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill);
} }
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
@@ -750,19 +753,19 @@ public class RecurringDepositAccount extends SavingsAccount {
@Override @Override
public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer, public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postInterestAson) { final LocalDate postInterestAson, final boolean backdatedTxnsAllowedTill) {
final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestAson); financialYearBeginningMonth, postInterestAson, backdatedTxnsAllowedTill);
} }
@Override @Override
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postAsInterestOn) { final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill) {
final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postAsInterestOn); financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill);
} }
private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) {
@@ -834,7 +837,7 @@ public class RecurringDepositAccount extends SavingsAccount {
} }
@Override @Override
public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO) { public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill) {
if (isAccountMatured()) { if (isAccountMatured()) {
final String defaultUserMessage = "Transaction is not allowed. Account is matured."; final String defaultUserMessage = "Transaction is not allowed. Account is matured.";
@@ -872,7 +875,7 @@ public class RecurringDepositAccount extends SavingsAccount {
throw new PlatformApiDataValidationException(dataValidationErrors); throw new PlatformApiDataValidationException(dataValidationErrors);
} }
final SavingsAccountTransaction transaction = super.deposit(transactionDTO); final SavingsAccountTransaction transaction = super.deposit(transactionDTO, backdatedTxnsAllowedTill);
return transaction; return transaction;
} }
@@ -316,6 +316,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
protected SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper; protected SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
@Transient @Transient
protected SavingsHelper savingsHelper; protected SavingsHelper savingsHelper;
@Transient
protected List<SavingsAccountTransaction> savingsAccountTransactions = new ArrayList<>();
@Column(name = "deposit_type_enum", insertable = false, updatable = false) @Column(name = "deposit_type_enum", insertable = false, updatable = false)
private Integer depositType; private Integer depositType;
@@ -445,6 +447,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
this.savingsHelper = savingsHelper; this.savingsHelper = savingsHelper;
} }
public void setSavingsAccountTransactions(final List<SavingsAccountTransaction> savingsAccountTransactions) {
this.savingsAccountTransactions.addAll(savingsAccountTransactions);
}
public List<SavingsAccountTransaction> getSavingsAccountTransactionsWithPivotConfig() {
return this.savingsAccountTransactions;
}
public String getExternalId() { public String getExternalId() {
return externalId; return externalId;
} }
@@ -487,15 +497,25 @@ public class SavingsAccount extends AbstractPersistableCustom {
public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final boolean isInterestTransfer, public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postInterestOnDate) { final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) {
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate); isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
Money interestPostedToDate = Money.zero(this.currency); Money interestPostedToDate = Money.zero(this.currency);
if (backdatedTxnsAllowedTill) {
interestPostedToDate = Money.of(this.currency, this.summary.getTotalInterestPosted());
}
boolean recalucateDailyBalanceDetails = false; boolean recalucateDailyBalanceDetails = false;
boolean applyWithHoldTax = isWithHoldTaxApplicableForInterestPosting(); boolean applyWithHoldTax = isWithHoldTaxApplicableForInterestPosting();
final List<SavingsAccountTransaction> withholdTransactions = new ArrayList<>(); final List<SavingsAccountTransaction> withholdTransactions = new ArrayList<>();
withholdTransactions.addAll(findWithHoldTransactions());
if (backdatedTxnsAllowedTill) {
withholdTransactions.addAll(findWithHoldSavingsTransactionsWithPivotConfig());
} else {
withholdTransactions.addAll(findWithHoldTransactions());
}
for (final PostingPeriod interestPostingPeriod : postingPeriods) { for (final PostingPeriod interestPostingPeriod : postingPeriods) {
@@ -516,9 +536,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
newPostingTransaction = SavingsAccountTransaction.overdraftInterest(this, office(), interestPostingTransactionDate, newPostingTransaction = SavingsAccountTransaction.overdraftInterest(this, office(), interestPostingTransactionDate,
interestEarnedToBePostedForPeriod.negated(), interestPostingPeriod.isUserPosting()); interestEarnedToBePostedForPeriod.negated(), interestPostingPeriod.isUserPosting());
} }
addTransaction(newPostingTransaction); if (backdatedTxnsAllowedTill) {
addTransactionToExisting(newPostingTransaction);
} else {
addTransaction(newPostingTransaction);
}
if (applyWithHoldTax) { if (applyWithHoldTax) {
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate); createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate,
backdatedTxnsAllowedTill);
} }
recalucateDailyBalanceDetails = true; recalucateDailyBalanceDetails = true;
} else { } else {
@@ -547,9 +572,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(), interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(),
interestPostingPeriod.isUserPosting()); interestPostingPeriod.isUserPosting());
} }
addTransaction(newPostingTransaction); if (backdatedTxnsAllowedTill) {
addTransactionToExisting(newPostingTransaction);
} else {
addTransaction(newPostingTransaction);
}
if (applyWithHoldTaxForOldTransaction) { if (applyWithHoldTaxForOldTransaction) {
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate); createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate,
backdatedTxnsAllowedTill);
} }
recalucateDailyBalanceDetails = true; recalucateDailyBalanceDetails = true;
} }
@@ -561,14 +591,27 @@ public class SavingsAccount extends AbstractPersistableCustom {
// no openingBalance concept supported yet but probably will to // no openingBalance concept supported yet but probably will to
// allow // allow
// for migrations. // for migrations.
final Money openingAccountBalance = Money.zero(this.currency); Money openingAccountBalance = Money.zero(this.currency);
if (backdatedTxnsAllowedTill) {
if (this.summary.getLastInterestCalculationDate() == null) {
openingAccountBalance = Money.zero(this.currency);
} else {
openingAccountBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
}
}
// update existing transactions so derived balance fields are // update existing transactions so derived balance fields are
// correct. // correct.
recalculateDailyBalances(openingAccountBalance, interestPostingUpToDate); recalculateDailyBalances(openingAccountBalance, interestPostingUpToDate, backdatedTxnsAllowedTill);
} }
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); if (!backdatedTxnsAllowedTill) {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
} else {
this.summary.updateSummaryWithPivotConfig(this.currency, this.savingsAccountTransactionSummaryWrapper, null,
this.savingsAccountTransactions);
}
} }
protected List<SavingsAccountTransaction> findWithHoldTransactions() { protected List<SavingsAccountTransaction> findWithHoldTransactions() {
@@ -582,6 +625,17 @@ public class SavingsAccount extends AbstractPersistableCustom {
return withholdTransactions; return withholdTransactions;
} }
protected List<SavingsAccountTransaction> findWithHoldSavingsTransactionsWithPivotConfig() {
final List<SavingsAccountTransaction> withholdTransactions = new ArrayList<>();
List<SavingsAccountTransaction> trans = getSavingsAccountTransactionsWithPivotConfig();
for (final SavingsAccountTransaction transaction : trans) {
if (transaction.isWithHoldTaxAndNotReversed()) {
withholdTransactions.add(transaction);
}
}
return withholdTransactions;
}
private boolean isWithHoldTaxApplicableForInterestPosting() { private boolean isWithHoldTaxApplicableForInterestPosting() {
return this.withHoldTax() && this.depositAccountType().isSavingsDeposit(); return this.withHoldTax() && this.depositAccountType().isSavingsDeposit();
} }
@@ -599,6 +653,19 @@ public class SavingsAccount extends AbstractPersistableCustom {
return postingTransation; return postingTransation;
} }
protected SavingsAccountTransaction findInterestPostingSavingsTransactionWithPivotConfig(final LocalDate postingDate) {
SavingsAccountTransaction postingTransation = null;
List<SavingsAccountTransaction> trans = getSavingsAccountTransactionsWithPivotConfig();
for (final SavingsAccountTransaction transaction : trans) {
if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
&& transaction.occursOn(postingDate)) {
postingTransation = transaction;
break;
}
}
return postingTransation;
}
protected SavingsAccountTransaction findTransactionFor(final LocalDate postingDate, protected SavingsAccountTransaction findTransactionFor(final LocalDate postingDate,
final List<SavingsAccountTransaction> transactions) { final List<SavingsAccountTransaction> transactions) {
SavingsAccountTransaction transaction = null; SavingsAccountTransaction transaction = null;
@@ -611,7 +678,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
return transaction; return transaction;
} }
protected boolean createWithHoldTransaction(final BigDecimal amount, final LocalDate date) { protected boolean createWithHoldTransaction(final BigDecimal amount, final LocalDate date, final boolean backdatedTxnsAllowedTill) {
boolean isTaxAdded = false; boolean isTaxAdded = false;
if (this.taxGroup != null && amount.compareTo(BigDecimal.ZERO) > 0) { if (this.taxGroup != null && amount.compareTo(BigDecimal.ZERO) > 0) {
Map<TaxComponent, BigDecimal> taxSplit = TaxUtils.splitTax(amount, date, this.taxGroup.getTaxGroupMappings(), amount.scale()); Map<TaxComponent, BigDecimal> taxSplit = TaxUtils.splitTax(amount, date, this.taxGroup.getTaxGroupMappings(), amount.scale());
@@ -619,7 +686,11 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (totalTax.compareTo(BigDecimal.ZERO) > 0) { if (totalTax.compareTo(BigDecimal.ZERO) > 0) {
SavingsAccountTransaction withholdTransaction = SavingsAccountTransaction.withHoldTax(this, office(), date, SavingsAccountTransaction withholdTransaction = SavingsAccountTransaction.withHoldTax(this, office(), date,
Money.of(currency, totalTax), taxSplit); Money.of(currency, totalTax), taxSplit);
addTransaction(withholdTransaction); if (backdatedTxnsAllowedTill) {
addTransactionToExisting(withholdTransaction);
} else {
addTransaction(withholdTransaction);
}
isTaxAdded = true; isTaxAdded = true;
} }
} }
@@ -665,6 +736,18 @@ public class SavingsAccount extends AbstractPersistableCustom {
return savingsTransaction; return savingsTransaction;
} }
protected SavingsAccountTransaction findLastFilteredTransactionWithPivotConfig(final LocalDate date) {
SavingsAccountTransaction savingsTransaction = null;
List<SavingsAccountTransaction> trans = getSavingsAccountTransactionsWithPivotConfig();
for (final SavingsAccountTransaction transaction : trans) {
if (transaction.isNotReversed() && transaction.occursOn(date)) {
savingsTransaction = transaction;
break;
}
}
return savingsTransaction;
}
public List<LocalDate> getManualPostingDates() { public List<LocalDate> getManualPostingDates() {
List<LocalDate> transactions = new ArrayList<>(); List<LocalDate> transactions = new ArrayList<>();
for (SavingsAccountTransaction trans : this.transactions) { for (SavingsAccountTransaction trans : this.transactions) {
@@ -675,6 +758,16 @@ public class SavingsAccount extends AbstractPersistableCustom {
return transactions; return transactions;
} }
public List<LocalDate> getManualPostingDatesWithPivotConfig() {
List<LocalDate> transactions = new ArrayList<>();
for (SavingsAccountTransaction trans : this.savingsAccountTransactions) {
if (trans.isInterestPosting() && trans.isNotReversed() && trans.isManualTransaction()) {
transactions.add(trans.getTransactionLocalDate());
}
}
return transactions;
}
/** /**
* All interest calculation based on END-OF-DAY-BALANCE. * All interest calculation based on END-OF-DAY-BALANCE.
* *
@@ -692,15 +785,22 @@ public class SavingsAccount extends AbstractPersistableCustom {
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate, public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate,
boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postInterestOnDate) { final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) {
// no openingBalance concept supported yet but probably will to allow // no openingBalance concept supported yet but probably will to allow
// for migrations. // for migrations.
final Money openingAccountBalance = Money.zero(this.currency); Money openingAccountBalance = null;
// Check global configurations and 'pivot' date is null
if (backdatedTxnsAllowedTill) {
openingAccountBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
} else {
openingAccountBalance = Money.zero(this.currency);
}
// update existing transactions so derived balance fields are // update existing transactions so derived balance fields are
// correct. // correct.
recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate); recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate, backdatedTxnsAllowedTill);
// 1. default to calculate interest based on entire history OR // 1. default to calculate interest based on entire history OR
// 2. determine latest 'posting period' and find interest credited to // 2. determine latest 'posting period' and find interest credited to
@@ -779,7 +879,13 @@ public class SavingsAccount extends AbstractPersistableCustom {
isTransferInterestToOtherAccount()); isTransferInterestToOtherAccount());
this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods); this.summary.updateFromInterestPeriodSummaries(this.currency, allPostingPeriods);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
if (backdatedTxnsAllowedTill) {
this.summary.updateSummaryWithPivotConfig(this.currency, this.savingsAccountTransactionSummaryWrapper, null,
this.savingsAccountTransactions);
} else {
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
}
return allPostingPeriods; return allPostingPeriods;
} }
@@ -808,6 +914,29 @@ public class SavingsAccount extends AbstractPersistableCustom {
return orderedNonInterestPostingTransactions; return orderedNonInterestPostingTransactions;
} }
protected List<SavingsAccountTransaction> retreiveOrderedNonInterestPostingSavingsTransactionsWithPivotConfig() {
final List<SavingsAccountTransaction> listOfTransactionsSorted = retrieveSortedTransactions();
final List<SavingsAccountTransaction> orderedNonInterestPostingTransactions = new ArrayList<>();
for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) {
if (!(transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
&& transaction.isNotReversed()) {
orderedNonInterestPostingTransactions.add(transaction);
}
}
orderedNonInterestPostingTransactions.sort(new SavingsAccountTransactionComparator());
return orderedNonInterestPostingTransactions;
}
protected List<SavingsAccountTransaction> retrieveSortedTransactions() {
final List<SavingsAccountTransaction> listOfTransactionsSorted = new ArrayList<>();
listOfTransactionsSorted.addAll(this.savingsAccountTransactions);
final SavingsAccountTransactionComparator transactionComparator = new SavingsAccountTransactionComparator();
Collections.sort(listOfTransactionsSorted, transactionComparator);
return listOfTransactionsSorted;
}
protected List<SavingsAccountTransaction> retreiveListOfTransactions() { protected List<SavingsAccountTransaction> retreiveListOfTransactions() {
final List<SavingsAccountTransaction> listOfTransactionsSorted = new ArrayList<>(); final List<SavingsAccountTransaction> listOfTransactionsSorted = new ArrayList<>();
listOfTransactionsSorted.addAll(this.transactions); listOfTransactionsSorted.addAll(this.transactions);
@@ -817,11 +946,19 @@ public class SavingsAccount extends AbstractPersistableCustom {
return listOfTransactionsSorted; return listOfTransactionsSorted;
} }
protected void recalculateDailyBalances(final Money openingAccountBalance, final LocalDate interestPostingUpToDate) { protected void recalculateDailyBalances(final Money openingAccountBalance, final LocalDate interestPostingUpToDate,
final boolean backdatedTxnsAllowedTill) {
Money runningBalance = openingAccountBalance.copy(); Money runningBalance = openingAccountBalance.copy();
List<SavingsAccountTransaction> accountTransactionsSorted = retreiveListOfTransactions(); List<SavingsAccountTransaction> accountTransactionsSorted = null;
if (backdatedTxnsAllowedTill) {
accountTransactionsSorted = retrieveSortedTransactions();
} else {
accountTransactionsSorted = retreiveListOfTransactions();
}
boolean isTransactionsModified = false; boolean isTransactionsModified = false;
for (final SavingsAccountTransaction transaction : accountTransactionsSorted) { for (final SavingsAccountTransaction transaction : accountTransactionsSorted) {
if (transaction.isReversed()) { if (transaction.isReversed()) {
@@ -867,7 +1004,11 @@ public class SavingsAccount extends AbstractPersistableCustom {
accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount()); accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount());
} }
accountTransaction.updateRunningBalance(runningBalance); accountTransaction.updateRunningBalance(runningBalance);
addTransaction(accountTransaction); if (backdatedTxnsAllowedTill) {
addTransactionToExisting(accountTransaction);
} else {
addTransaction(accountTransaction);
}
isTransactionsModified = true; isTransactionsModified = true;
} }
@@ -875,7 +1016,11 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
if (isTransactionsModified) { if (isTransactionsModified) {
accountTransactionsSorted = retreiveListOfTransactions(); if (backdatedTxnsAllowedTill) {
accountTransactionsSorted = retrieveSortedTransactions();
} else {
accountTransactionsSorted = retreiveListOfTransactions();
}
} }
resetAccountTransactionsEndOfDayBalances(accountTransactionsSorted, interestPostingUpToDate); resetAccountTransactionsEndOfDayBalances(accountTransactionsSorted, interestPostingUpToDate);
} }
@@ -896,16 +1041,17 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
} }
public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO) { public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill) {
return deposit(transactionDTO, SavingsAccountTransactionType.DEPOSIT); return deposit(transactionDTO, SavingsAccountTransactionType.DEPOSIT, backdatedTxnsAllowedTill);
} }
public SavingsAccountTransaction dividendPayout(final SavingsAccountTransactionDTO transactionDTO) { public SavingsAccountTransaction dividendPayout(final SavingsAccountTransactionDTO transactionDTO,
return deposit(transactionDTO, SavingsAccountTransactionType.DIVIDEND_PAYOUT); final boolean backdatedTxnsAllowedTill) {
return deposit(transactionDTO, SavingsAccountTransactionType.DIVIDEND_PAYOUT, backdatedTxnsAllowedTill);
} }
public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO,
final SavingsAccountTransactionType savingsAccountTransactionType) { final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) {
final String resourceTypeName = depositAccountType().resourceName(); final String resourceTypeName = depositAccountType().resourceName();
if (isNotActive()) { if (isNotActive()) {
final String defaultUserMessage = "Transaction is not allowed. Account is not active."; final String defaultUserMessage = "Transaction is not allowed. Account is not active.";
@@ -950,8 +1096,12 @@ public class SavingsAccount extends AbstractPersistableCustom {
final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, office(), transactionDTO.getPaymentDetail(), final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, office(), transactionDTO.getPaymentDetail(),
transactionDTO.getTransactionDate(), amount, transactionDTO.getCreatedDate(), transactionDTO.getAppUser(), transactionDTO.getTransactionDate(), amount, transactionDTO.getCreatedDate(), transactionDTO.getAppUser(),
savingsAccountTransactionType); savingsAccountTransactionType);
addTransaction(transaction);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); if (backdatedTxnsAllowedTill) {
addTransactionToExisting(transaction);
} else {
addTransaction(transaction);
}
if (this.sub_status.equals(SavingsAccountSubStatusEnum.INACTIVE.getValue()) if (this.sub_status.equals(SavingsAccountSubStatusEnum.INACTIVE.getValue())
|| this.sub_status.equals(SavingsAccountSubStatusEnum.DORMANT.getValue())) { || this.sub_status.equals(SavingsAccountSubStatusEnum.DORMANT.getValue())) {
@@ -986,7 +1136,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
return startInterestCalculationLocalDate; return startInterestCalculationLocalDate;
} }
public SavingsAccountTransaction withdraw(final SavingsAccountTransactionDTO transactionDTO, final boolean applyWithdrawFee) { public SavingsAccountTransaction withdraw(final SavingsAccountTransactionDTO transactionDTO, final boolean applyWithdrawFee,
final boolean backdatedTxnsAllowedTill) {
if (!isTransactionsAllowed()) { if (!isTransactionsAllowed()) {
@@ -1042,14 +1193,19 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (applyWithdrawFee) { if (applyWithdrawFee) {
// auto pay withdrawal fee // auto pay withdrawal fee
payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser(), payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser(),
transactionDTO.getPaymentDetail()); transactionDTO.getPaymentDetail(), backdatedTxnsAllowedTill);
} }
final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount()); final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount());
final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(), final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(),
transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney, transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney,
transactionDTO.getCreatedDate(), transactionDTO.getAppUser()); transactionDTO.getCreatedDate(), transactionDTO.getAppUser());
addTransaction(transaction);
if (backdatedTxnsAllowedTill) {
addTransactionToExisting(transaction);
} else {
addTransaction(transaction);
}
if (this.sub_status.equals(SavingsAccountSubStatusEnum.INACTIVE.getValue()) if (this.sub_status.equals(SavingsAccountSubStatusEnum.INACTIVE.getValue())
|| this.sub_status.equals(SavingsAccountSubStatusEnum.DORMANT.getValue())) { || this.sub_status.equals(SavingsAccountSubStatusEnum.DORMANT.getValue())) {
@@ -1071,8 +1227,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDate transactionDate, final AppUser user, private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDate transactionDate, final AppUser user,
final PaymentDetail paymentDetail) { final PaymentDetail paymentDetail, final boolean backdatedTxnsAllowedTill) {
for (SavingsAccountCharge charge : this.charges()) { for (SavingsAccountCharge charge : this.charges()) {
if (charge.isWithdrawalFee() && charge.isActive()) { if (charge.isWithdrawalFee() && charge.isActive()) {
@@ -1089,7 +1244,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
} else if (charge.isEnablePaymentType()) { // normal charge-transaction to specific paymentType } else if (charge.isEnablePaymentType()) { // normal charge-transaction to specific paymentType
if (paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName())) { if (paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName())) {
charge.updateWithdralFeeAmount(transactionAmount); charge.updateWithdralFeeAmount(transactionAmount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user); this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user,
backdatedTxnsAllowedTill);
} }
} else if (!charge.isEnablePaymentType() && charge.isEnableFreeWithdrawal()) { // discount transaction } else if (!charge.isEnablePaymentType() && charge.isEnableFreeWithdrawal()) { // discount transaction
// irrespective of // irrespective of
@@ -1098,8 +1254,10 @@ public class SavingsAccount extends AbstractPersistableCustom {
} else { // normal-withdraw } else { // normal-withdraw
charge.updateWithdralFeeAmount(transactionAmount); charge.updateWithdralFeeAmount(transactionAmount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user); this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user,
backdatedTxnsAllowedTill);
} }
} }
} }
@@ -1160,13 +1318,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
private void countValidation(SavingsAccountCharge charge, final BigDecimal transactionAmount, final LocalDate transactionDate, private void countValidation(SavingsAccountCharge charge, final BigDecimal transactionAmount, final LocalDate transactionDate,
final AppUser user) { final AppUser user) {
boolean backdatedTxnsAllowedTill = false;
if (charge.getFreeWithdrawalCount() < charge.getFrequencyFreeWithdrawalCharge()) { if (charge.getFreeWithdrawalCount() < charge.getFrequencyFreeWithdrawalCharge()) {
final Integer count = charge.getFreeWithdrawalCount() + 1; final Integer count = charge.getFreeWithdrawalCount() + 1;
charge.setFreeWithdrawalCount(count); charge.setFreeWithdrawalCount(count);
charge.updateNoWithdrawalFee(); charge.updateNoWithdrawalFee();
} else { } else {
charge.updateWithdralFeeAmount(transactionAmount); charge.updateWithdralFeeAmount(transactionAmount);
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user); this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, backdatedTxnsAllowedTill);
} }
} }
@@ -1176,25 +1335,47 @@ public class SavingsAccount extends AbstractPersistableCustom {
charge.updateNoWithdrawalFee(); charge.updateNoWithdrawalFee();
} }
public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate) { public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate, final boolean backdatedTxnsAllowedTill) {
boolean transactionBeforeLastInterestPosting = false; boolean transactionBeforeLastInterestPosting = false;
for (final SavingsAccountTransaction transaction : retreiveListOfTransactions()) { if (!backdatedTxnsAllowedTill) {
if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed()) for (final SavingsAccountTransaction transaction : retreiveListOfTransactions()) {
&& transaction.isAfter(transactionDate)) { if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
transactionBeforeLastInterestPosting = true; && transaction.isAfter(transactionDate)) {
break; transactionBeforeLastInterestPosting = true;
break;
}
} }
} else {
if (this.summary.getInterestPostedTillDate() == null) {
return false;
}
transactionBeforeLastInterestPosting = LocalDate
.ofInstant(this.summary.getInterestPostedTillDate().toInstant(), DateUtils.getDateTimeZoneOfTenant())
.isAfter(transactionDate) ? true : false;
} }
return transactionBeforeLastInterestPosting; return transactionBeforeLastInterestPosting;
} }
public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isException, public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isException,
final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions) { final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions, final boolean backdatedTxnsAllowedTill) {
final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
List<SavingsAccountTransaction> transactionsSortedByDate = null;
if (backdatedTxnsAllowedTill) {
transactionsSortedByDate = retrieveSortedTransactions();
} else {
transactionsSortedByDate = retreiveListOfTransactions();
}
Money runningBalance = Money.zero(this.currency); Money runningBalance = Money.zero(this.currency);
if (backdatedTxnsAllowedTill) {
runningBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
}
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency()); Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
LocalDate lastSavingsDate = null; LocalDate lastSavingsDate = null;
final BigDecimal withdrawalFee = null; final BigDecimal withdrawalFee = null;
@@ -1255,10 +1436,20 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
public void validateAccountBalanceDoesNotBecomeNegative(final String transactionAction, public void validateAccountBalanceDoesNotBecomeNegative(final String transactionAction,
final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions) { final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions, final boolean backdatedTxnsAllowedTill) {
final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions(); List<SavingsAccountTransaction> transactionsSortedByDate = null;
if (backdatedTxnsAllowedTill) {
transactionsSortedByDate = retrieveSortedTransactions();
} else {
transactionsSortedByDate = retreiveListOfTransactions();
}
Money runningBalance = Money.zero(this.currency); Money runningBalance = Money.zero(this.currency);
if (backdatedTxnsAllowedTill) {
runningBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
}
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency()); Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
LocalDate lastSavingsDate = null; LocalDate lastSavingsDate = null;
for (final SavingsAccountTransaction transaction : transactionsSortedByDate) { for (final SavingsAccountTransaction transaction : transactionsSortedByDate) {
@@ -1586,7 +1777,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final Set<Long> existingTransactionIds, public Map<String, Object> deriveAccountingBridgeData(final CurrencyData currencyData, final Set<Long> existingTransactionIds,
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) { final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer, final boolean backdatedTxnsAllowedTill) {
final Map<String, Object> accountingBridgeData = new LinkedHashMap<>(); final Map<String, Object> accountingBridgeData = new LinkedHashMap<>();
accountingBridgeData.put("savingsId", getId()); accountingBridgeData.put("savingsId", getId());
@@ -1598,7 +1789,16 @@ public class SavingsAccount extends AbstractPersistableCustom {
accountingBridgeData.put("isAccountTransfer", isAccountTransfer); accountingBridgeData.put("isAccountTransfer", isAccountTransfer);
final List<Map<String, Object>> newSavingsTransactions = new ArrayList<>(); final List<Map<String, Object>> newSavingsTransactions = new ArrayList<>();
List<SavingsAccountTransaction> trans = getTransactions();
List<SavingsAccountTransaction> trans = null;
if (backdatedTxnsAllowedTill) {
trans = getSavingsAccountTransactionsWithPivotConfig();
} else {
trans = getTransactions();
}
// Adding new transactions to the array
for (final SavingsAccountTransaction transaction : trans) { for (final SavingsAccountTransaction transaction : trans) {
if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) { if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) {
newSavingsTransactions.add(transaction.toMapData(currencyData)); newSavingsTransactions.add(transaction.toMapData(currencyData));
@@ -1612,13 +1812,21 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
public Collection<Long> findExistingTransactionIds() { public Collection<Long> findExistingTransactionIds() {
final Collection<Long> ids = new ArrayList<>(); final Collection<Long> ids = new ArrayList<>();
List<SavingsAccountTransaction> trans = getTransactions(); List<SavingsAccountTransaction> trans = getTransactions();
for (final SavingsAccountTransaction transaction : trans) { for (final SavingsAccountTransaction transaction : trans) {
ids.add(transaction.getId()); ids.add(transaction.getId());
} }
return ids;
}
public Collection<Long> findCurrentTransactionIdsWithPivotDateConfig() {
final Collection<Long> ids = new ArrayList<>();
List<SavingsAccountTransaction> trans = getSavingsAccountTransactionsWithPivotConfig();
for (final SavingsAccountTransaction transaction : trans) {
ids.add(transaction.getId());
}
return ids; return ids;
} }
@@ -1626,6 +1834,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
final Collection<Long> ids = new ArrayList<>(); final Collection<Long> ids = new ArrayList<>();
List<SavingsAccountTransaction> trans = getTransactions(); List<SavingsAccountTransaction> trans = getTransactions();
// time consuming
for (final SavingsAccountTransaction transaction : trans) { for (final SavingsAccountTransaction transaction : trans) {
if (transaction.isReversed()) { if (transaction.isReversed()) {
ids.add(transaction.getId()); ids.add(transaction.getId());
@@ -1635,6 +1844,18 @@ public class SavingsAccount extends AbstractPersistableCustom {
return ids; return ids;
} }
public Collection<Long> findCurrentReversedTransactionIdsWithPivotDateConfig() {
final Collection<Long> ids = new ArrayList<>();
List<SavingsAccountTransaction> trans = getSavingsAccountTransactionsWithPivotConfig();
// time consuming
for (final SavingsAccountTransaction transaction : trans) {
if (transaction.isReversed()) {
ids.add(transaction.getId());
}
}
return ids;
}
public void update(final Client client) { public void update(final Client client) {
this.client = client; this.client = client;
} }
@@ -2090,6 +2311,36 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
} }
public void undoSavingsTransaction(final Long transactionId) {
SavingsAccountTransaction transactionToUndo = null;
for (final SavingsAccountTransaction transaction : this.savingsAccountTransactions) {
if (transaction.isIdentifiedBy(transactionId)) {
transactionToUndo = transaction;
}
}
if (transactionToUndo == null) {
throw new SavingsAccountTransactionNotFoundException(this.getId(), transactionId);
}
validateAttemptToUndoTransferRelatedTransactions(transactionToUndo);
validateActivityNotBeforeClientOrGroupTransferDate(SavingsEvent.SAVINGS_UNDO_TRANSACTION, transactionToUndo.transactionLocalDate());
transactionToUndo.reverse();
if (transactionToUndo.isChargeTransaction() || transactionToUndo.isWaiveCharge()) {
// undo charge
final Set<SavingsAccountChargePaidBy> chargesPaidBy = transactionToUndo.getSavingsAccountChargesPaid();
for (final SavingsAccountChargePaidBy savingsAccountChargePaidBy : chargesPaidBy) {
final SavingsAccountCharge chargeToUndo = savingsAccountChargePaidBy.getSavingsAccountCharge();
if (transactionToUndo.isChargeTransaction()) {
chargeToUndo.undoPayment(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
} else if (transactionToUndo.isWaiveCharge()) {
chargeToUndo.undoWaiver(this.getCurrency(), transactionToUndo.getAmount(this.getCurrency()));
}
}
}
}
private Date findLatestAnnualFeeTransactionDueDate() { private Date findLatestAnnualFeeTransactionDueDate() {
Date nextDueDate = null; Date nextDueDate = null;
@@ -2341,8 +2592,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
charge.updateToNextDueDateFrom(getActivationLocalDate()); charge.updateToNextDueDateFrom(getActivationLocalDate());
} }
// auto pay the activation time charges // auto pay the activation time charges (No need of checking the pivot date config)
this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user); this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user, false);
// TODO : AA add activation charges to actual changes list // TODO : AA add activation charges to actual changes list
} }
@@ -2366,12 +2617,13 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
private void payActivationCharges(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, private void payActivationCharges(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final AppUser user) { final AppUser user, final boolean backdatedTxnsAllowedTill) {
boolean isSavingsChargeApplied = false; boolean isSavingsChargeApplied = false;
for (SavingsAccountCharge savingsAccountCharge : this.charges()) { for (SavingsAccountCharge savingsAccountCharge : this.charges()) {
if (savingsAccountCharge.isSavingsActivation()) { if (savingsAccountCharge.isSavingsActivation()) {
isSavingsChargeApplied = true; isSavingsChargeApplied = true;
payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate(), user); payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate(), user,
backdatedTxnsAllowedTill);
} }
} }
@@ -2379,14 +2631,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestAsOnDate = null; LocalDate postInterestAsOnDate = null;
if (this.isBeforeLastPostingPeriod(getActivationLocalDate())) { if (this.isBeforeLastPostingPeriod(getActivationLocalDate(), backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestAsOnDate); postInterestAsOnDate, backdatedTxnsAllowedTill);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
this.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, this.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestAsOnDate); financialYearBeginningMonth, postInterestAsOnDate, backdatedTxnsAllowedTill);
} }
} }
} }
@@ -2535,6 +2787,10 @@ public class SavingsAccount extends AbstractPersistableCustom {
this.transactions.add(transaction); this.transactions.add(transaction);
} }
public void addTransactionToExisting(final SavingsAccountTransaction transaction) {
this.savingsAccountTransactions.add(transaction);
}
public void setStatus(final Integer status) { public void setStatus(final Integer status) {
this.status = status; this.status = status;
} }
@@ -2588,7 +2844,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
this.charges.remove(charge); this.charges.remove(charge);
} }
public void waiveCharge(final Long savingsAccountChargeId, final AppUser user) { public void waiveCharge(final Long savingsAccountChargeId, final AppUser user, final boolean backdatedTxnsAllowedTill) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
@@ -2639,7 +2895,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
// waive charge // waive charge
final Money amountWaived = savingsAccountCharge.waive(getCurrency()); final Money amountWaived = savingsAccountCharge.waive(getCurrency());
handleWaiverChargeTransactions(savingsAccountCharge, amountWaived, user); handleWaiverChargeTransactions(savingsAccountCharge, amountWaived, user, backdatedTxnsAllowedTill);
} }
@@ -2722,7 +2978,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid,
final LocalDate transactionDate, final DateTimeFormatter formatter, final AppUser user) { final LocalDate transactionDate, final DateTimeFormatter formatter, final AppUser user,
final boolean backdatedTxnsAllowedTill) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
@@ -2810,17 +3067,17 @@ public class SavingsAccount extends AbstractPersistableCustom {
} }
} }
return this.payCharge(savingsAccountCharge, chargePaid, transactionDate, user); return this.payCharge(savingsAccountCharge, chargePaid, transactionDate, user, backdatedTxnsAllowedTill);
} }
public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid,
final LocalDate transactionDate, final AppUser user) { final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill) {
savingsAccountCharge.pay(getCurrency(), amountPaid); savingsAccountCharge.pay(getCurrency(), amountPaid);
return handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user); return handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user, backdatedTxnsAllowedTill);
} }
private SavingsAccountTransaction handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, private SavingsAccountTransaction handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount,
final LocalDate transactionDate, final AppUser user) { final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill) {
SavingsAccountTransaction chargeTransaction = null; SavingsAccountTransaction chargeTransaction = null;
if (savingsAccountCharge.isWithdrawalFee()) { if (savingsAccountCharge.isWithdrawalFee()) {
@@ -2831,23 +3088,31 @@ public class SavingsAccount extends AbstractPersistableCustom {
chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount, user); chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount, user);
} }
handleChargeTransactions(savingsAccountCharge, chargeTransaction); handleChargeTransactions(savingsAccountCharge, chargeTransaction, backdatedTxnsAllowedTill);
return chargeTransaction; return chargeTransaction;
} }
private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, AppUser user) { private void handleWaiverChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, AppUser user,
boolean backdatedTxnsAllowedTill) {
final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.waiver(this, office(), final SavingsAccountTransaction chargeTransaction = SavingsAccountTransaction.waiver(this, office(),
DateUtils.getLocalDateOfTenant(), transactionAmount, user); DateUtils.getLocalDateOfTenant(), transactionAmount, user);
handleChargeTransactions(savingsAccountCharge, chargeTransaction); handleChargeTransactions(savingsAccountCharge, chargeTransaction, backdatedTxnsAllowedTill);
} }
private void handleChargeTransactions(final SavingsAccountCharge savingsAccountCharge, final SavingsAccountTransaction transaction) { private void handleChargeTransactions(final SavingsAccountCharge savingsAccountCharge, final SavingsAccountTransaction transaction,
final boolean backdatedTxnsAllowedTill) {
// Provide a link between transaction and savings charge for which // Provide a link between transaction and savings charge for which
// amount is waived. // amount is waived.
final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(transaction, savingsAccountCharge, final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(transaction, savingsAccountCharge,
transaction.getAmount(this.getCurrency()).getAmount()); transaction.getAmount(this.getCurrency()).getAmount());
transaction.getSavingsAccountChargesPaid().add(chargePaidBy); transaction.getSavingsAccountChargesPaid().add(chargePaidBy);
this.transactions.add(transaction); if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactions.add(transaction);
this.summary.updateSummaryWithPivotConfig(this.currency, this.savingsAccountTransactionSummaryWrapper, transaction,
this.savingsAccountTransactions);
} else {
this.transactions.add(transaction);
}
} }
private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) { private SavingsAccountCharge getCharge(final Long savingsAccountChargeId) {
@@ -3069,13 +3334,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
this.withHoldTax = withHoldTax; this.withHoldTax = withHoldTax;
} }
protected boolean applyWithholdTaxForDepositAccounts(final LocalDate interestPostingUpToDate, boolean recalucateDailyBalance) { protected boolean applyWithholdTaxForDepositAccounts(final LocalDate interestPostingUpToDate, boolean recalucateDailyBalance,
final boolean backdatedTxnsAllowedTill) {
final List<SavingsAccountTransaction> withholdTransactions = findWithHoldTransactions(); final List<SavingsAccountTransaction> withholdTransactions = findWithHoldTransactions();
SavingsAccountTransaction withholdTransaction = findTransactionFor(interestPostingUpToDate, withholdTransactions); SavingsAccountTransaction withholdTransaction = findTransactionFor(interestPostingUpToDate, withholdTransactions);
final BigDecimal totalInterestPosted = this.savingsAccountTransactionSummaryWrapper.calculateTotalInterestPosted(this.currency, final BigDecimal totalInterestPosted = this.savingsAccountTransactionSummaryWrapper.calculateTotalInterestPosted(this.currency,
this.transactions); this.transactions);
if (withholdTransaction == null && this.withHoldTax()) { if (withholdTransaction == null && this.withHoldTax()) {
boolean isWithholdTaxAdded = createWithHoldTransaction(totalInterestPosted, interestPostingUpToDate); boolean isWithholdTaxAdded = createWithHoldTransaction(totalInterestPosted, interestPostingUpToDate, backdatedTxnsAllowedTill);
recalucateDailyBalance = recalucateDailyBalance || isWithholdTaxAdded; recalucateDailyBalance = recalucateDailyBalance || isWithholdTaxAdded;
} else { } else {
boolean isWithholdTaxAdded = updateWithHoldTransaction(totalInterestPosted, withholdTransaction); boolean isWithholdTaxAdded = updateWithHoldTransaction(totalInterestPosted, withholdTransaction);
@@ -3085,16 +3351,16 @@ public class SavingsAccount extends AbstractPersistableCustom {
return recalucateDailyBalance; return recalucateDailyBalance;
} }
public void setSubStatusInactive(AppUser appUser) { public void setSubStatusInactive(AppUser appUser, final boolean backdatedTxnsAllowedTill) {
this.sub_status = SavingsAccountSubStatusEnum.INACTIVE.getValue(); this.sub_status = SavingsAccountSubStatusEnum.INACTIVE.getValue();
LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
for (SavingsAccountCharge charge : this.charges()) { for (SavingsAccountCharge charge : this.charges()) {
if (charge.isSavingsNoActivity() && charge.isActive()) { if (charge.isSavingsNoActivity() && charge.isActive()) {
charge.updateWithdralFeeAmount(this.getAccountBalance()); charge.updateWithdralFeeAmount(this.getAccountBalance());
this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, appUser); this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, appUser, backdatedTxnsAllowedTill);
} }
} }
recalculateDailyBalances(Money.zero(this.currency), transactionDate); recalculateDailyBalances(Money.zero(this.currency), transactionDate, backdatedTxnsAllowedTill);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
} }
@@ -3113,10 +3379,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, appUser, postInterestAsOnDate); SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, appUser, postInterestAsOnDate);
this.transactions.add(transaction); this.transactions.add(transaction);
} }
recalculateDailyBalances(Money.zero(this.currency), transactionDate); recalculateDailyBalances(Money.zero(this.currency), transactionDate, false);
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
} }
public void loadTransactions() {
transactions.size();
}
public void loadLazyCollections() { public void loadLazyCollections() {
transactions.size(); transactions.size();
charges.size(); charges.size();
@@ -3355,6 +3625,19 @@ public class SavingsAccount extends AbstractPersistableCustom {
return lastransactionDate; return lastransactionDate;
} }
public LocalDate retrieveLastTransactionDateWithPivotConfig() {
final List<SavingsAccountTransaction> transactionsSortedByDate = retrieveSortedTransactions();
SavingsAccountTransaction lastTransaction = null;
if (transactionsSortedByDate.size() > 0) {
lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1);
}
LocalDate lastransactionDate = null;
if (lastTransaction != null) {
lastransactionDate = lastTransaction.transactionLocalDate();
}
return lastransactionDate;
}
public BigDecimal getSavingsHoldAmount() { public BigDecimal getSavingsHoldAmount() {
return this.savingsOnHoldAmount == null ? BigDecimal.ZERO : this.savingsOnHoldAmount; return this.savingsOnHoldAmount == null ? BigDecimal.ZERO : this.savingsOnHoldAmount;
} }
@@ -47,11 +47,15 @@ import com.google.gson.JsonElement;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
import org.apache.fineract.organisation.staff.domain.Staff; import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper; import org.apache.fineract.organisation.staff.domain.StaffRepositoryWrapper;
import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService; import org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
@@ -74,6 +78,7 @@ import org.apache.fineract.useradministration.domain.AppUser;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@Service @Service
@@ -89,6 +94,8 @@ public class SavingsAccountAssembler {
private final SavingsAccountRepositoryWrapper savingsAccountRepository; private final SavingsAccountRepositoryWrapper savingsAccountRepository;
private final SavingsAccountChargeAssembler savingsAccountChargeAssembler; private final SavingsAccountChargeAssembler savingsAccountChargeAssembler;
private final FromJsonHelper fromApiJsonHelper; private final FromJsonHelper fromApiJsonHelper;
private final JdbcTemplate jdbcTemplate;
private final ConfigurationDomainService configurationDomainService;
@Autowired @Autowired
public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
@@ -96,7 +103,8 @@ public class SavingsAccountAssembler {
final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository, final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository,
final SavingsAccountRepositoryWrapper savingsAccountRepository, final SavingsAccountRepositoryWrapper savingsAccountRepository,
final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper, final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper,
final AccountTransfersReadPlatformService accountTransfersReadPlatformService) { final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final RoutingDataSource dataSource,
final ConfigurationDomainService configurationDomainService) {
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
this.clientRepository = clientRepository; this.clientRepository = clientRepository;
this.groupRepository = groupRepository; this.groupRepository = groupRepository;
@@ -106,6 +114,8 @@ public class SavingsAccountAssembler {
this.savingsAccountChargeAssembler = savingsAccountChargeAssembler; this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
savingsHelper = new SavingsHelper(accountTransfersReadPlatformService); savingsHelper = new SavingsHelper(accountTransfersReadPlatformService);
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.configurationDomainService = configurationDomainService;
} }
/** /**
@@ -307,12 +317,58 @@ public class SavingsAccountAssembler {
return account; return account;
} }
public SavingsAccount assembleFrom(final Long savingsId) { public SavingsAccount assembleFrom(final Long savingsId, final boolean backdatedTxnsAllowedTill) {
final SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId); SavingsAccount account = this.savingsAccountRepository.findSavingsWithNotFoundDetection(savingsId, backdatedTxnsAllowedTill);
return loadTransactionsToSavingsAccount(account, backdatedTxnsAllowedTill);
}
public SavingsAccount loadTransactionsToSavingsAccount(final SavingsAccount account, final boolean backdatedTxnsAllowedTill) {
List<SavingsAccountTransaction> savingsAccountTransactions = null;
if (backdatedTxnsAllowedTill) {
Date pivotDate = account.getSummary().getInterestPostedTillDate();
boolean isNotPresent = pivotDate == null ? true : false;
if (!isNotPresent) {
// Get savings account transactions
if (isRelaxingDaysConfigForPivotDateEnabled()) {
final Long relaxingDaysForPivotDate = this.configurationDomainService.retrieveRelaxingDaysConfigForPivotDate();
LocalDate interestPostedTillDate = LocalDate.ofInstant(account.getSummary().getInterestPostedTillDate().toInstant(),
DateUtils.getDateTimeZoneOfTenant());
savingsAccountTransactions = this.savingsAccountRepository.findTransactionsAfterPivotDate(account,
Date.from(interestPostedTillDate.minusDays(relaxingDaysForPivotDate)
.atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()));
} else {
savingsAccountTransactions = this.savingsAccountRepository.findTransactionsAfterPivotDate(account,
account.getSummary().getInterestPostedTillDate());
}
if (savingsAccountTransactions != null && savingsAccountTransactions.size() > 0) {
// Update transient variable
account.setSavingsAccountTransactions(savingsAccountTransactions);
}
} else {
savingsAccountTransactions = this.savingsAccountRepository.findAllTransactions(account);
account.setSavingsAccountTransactions(savingsAccountTransactions);
}
}
// Update last running balance on account level
if (savingsAccountTransactions != null) {
account.getSummary().setRunningBalanceOnPivotDate(savingsAccountTransactions.get(savingsAccountTransactions.size() - 1)
.getRunningBalance(account.getCurrency()).getAmount());
}
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
return account; return account;
} }
public boolean getPivotConfigStatus() {
return this.configurationDomainService.retrievePivotDateConfig();
}
public boolean isRelaxingDaysConfigForPivotDateEnabled() {
return this.configurationDomainService.isRelaxingDaysConfigForPivotDateEnabled();
}
public void setHelpers(final SavingsAccount account) { public void setHelpers(final SavingsAccount account) {
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
} }
@@ -28,13 +28,16 @@ import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
public interface SavingsAccountDomainService { public interface SavingsAccountDomainService {
SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, SavingsAccountTransaction handleWithdrawal(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, SavingsTransactionBooleanValues transactionBooleanValues); BigDecimal transactionAmount, PaymentDetail paymentDetail, SavingsTransactionBooleanValues transactionBooleanValues,
boolean backdatedTxnsAllowedTill);
SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate, SavingsAccountTransaction handleDeposit(SavingsAccount account, DateTimeFormatter fmt, LocalDate transactionDate,
BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction); BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer, boolean isRegularTransaction,
boolean backdatedTxnsAllowedTill);
void postJournalEntries(SavingsAccount savingsAccount, Set<Long> existingTransactionIds, Set<Long> existingReversedTransactionIds); void postJournalEntries(SavingsAccount savingsAccount, Set<Long> existingTransactionIds, Set<Long> existingReversedTransactionIds,
boolean backdatedTxnsAllowedTill);
SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount);
SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount,
boolean backdatedTxnsAllowedTill);
} }
@@ -82,7 +82,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
@Override @Override
public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt, public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
final SavingsTransactionBooleanValues transactionBooleanValues) { final SavingsTransactionBooleanValues transactionBooleanValues, final boolean backdatedTxnsAllowedTill) {
AppUser user = getAppUserIfPresent(); AppUser user = getAppUserIfPresent();
account.validateForAccountBlock(); account.validateForAccountBlock();
@@ -96,32 +96,46 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final LocalDate postInterestOnDate = null; final LocalDate postInterestOnDate = null;
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
if (backdatedTxnsAllowedTill) {
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
} else {
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
}
Integer accountType = null; Integer accountType = null;
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
paymentDetail, new Date(), user, accountType); paymentDetail, new Date(), user, accountType);
final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee()); final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee(),
backdatedTxnsAllowedTill);
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd, account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(), account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(),
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate); isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
} }
account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(), account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(),
depositAccountOnHoldTransactions); depositAccountOnHoldTransactions, backdatedTxnsAllowedTill);
saveTransactionToGenerateTransactionId(withdrawal); saveTransactionToGenerateTransactionId(withdrawal);
this.savingsAccountRepository.save(account); this.savingsAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer()); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer(),
backdatedTxnsAllowedTill);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_WITHDRAWAL, this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_WITHDRAWAL,
constructEntityMap(BusinessEntity.SAVINGS_TRANSACTION, withdrawal)); constructEntityMap(BusinessEntity.SAVINGS_TRANSACTION, withdrawal));
return withdrawal; return withdrawal;
@@ -139,19 +153,21 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
@Override @Override
public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
final boolean isAccountTransfer, final boolean isRegularTransaction) { final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean backdatedTxnsAllowedTill) {
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT; final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DEPOSIT;
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
savingsAccountTransactionType); savingsAccountTransactionType, backdatedTxnsAllowedTill);
} }
private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt, private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
final boolean isAccountTransfer, final boolean isRegularTransaction, final boolean isAccountTransfer, final boolean isRegularTransaction,
final SavingsAccountTransactionType savingsAccountTransactionType) { final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) {
AppUser user = getAppUserIfPresent(); AppUser user = getAppUserIfPresent();
account.validateForAccountBlock(); account.validateForAccountBlock();
account.validateForCreditBlock(); account.validateForCreditBlock();
// Global configurations
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd(); .isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
@@ -162,27 +178,40 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
if (backdatedTxnsAllowedTill) {
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
} else {
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
}
Integer accountType = null; Integer accountType = null;
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
paymentDetail, new Date(), user, accountType); paymentDetail, new Date(), user, accountType);
final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType); final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType, backdatedTxnsAllowedTill);
final LocalDate postInterestOnDate = null; final LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, backdatedTxnsAllowedTill);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
} }
saveTransactionToGenerateTransactionId(deposit); saveTransactionToGenerateTransactionId(deposit);
if (backdatedTxnsAllowedTill) {
// Update transactions separately
saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingsAccountRepository.saveAndFlush(account); this.savingsAccountRepository.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_DEPOSIT, this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_DEPOSIT,
constructEntityMap(BusinessEntity.SAVINGS_TRANSACTION, deposit)); constructEntityMap(BusinessEntity.SAVINGS_TRANSACTION, deposit));
return deposit; return deposit;
@@ -190,19 +219,14 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
@Override @Override
public SavingsAccountTransaction handleDividendPayout(final SavingsAccount account, final LocalDate transactionDate, public SavingsAccountTransaction handleDividendPayout(final SavingsAccount account, final LocalDate transactionDate,
final BigDecimal transactionAmount) { final BigDecimal transactionAmount, final boolean backdatedTxnsAllowedTill) {
final DateTimeFormatter fmt = null; final DateTimeFormatter fmt = null;
final PaymentDetail paymentDetail = null; final PaymentDetail paymentDetail = null;
final boolean isAccountTransfer = false; final boolean isAccountTransfer = false;
final boolean isRegularTransaction = true; final boolean isRegularTransaction = true;
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DIVIDEND_PAYOUT; final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DIVIDEND_PAYOUT;
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
savingsAccountTransactionType); savingsAccountTransactionType, backdatedTxnsAllowedTill);
}
private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
this.savingsAccountTransactionRepository.save(transaction);
return transaction.getId();
} }
private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds, private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
@@ -211,24 +235,39 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
} }
private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
this.savingsAccountTransactionRepository.save(transaction);
return transaction.getId();
}
private void saveUpdatedTransactionsOfSavingsAccount(final List<SavingsAccountTransaction> savingsAccountTransactions) {
this.savingsAccountTransactionRepository.saveAll(savingsAccountTransactions);
}
private void updateTransactionDetailsWithPivotConfig(final SavingsAccount account, Set<Long> existingTransactionIds,
Set<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig());
existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig());
}
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds, private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) { final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer, final boolean backdatedTxnsAllowedTill) {
final MonetaryCurrency currency = savingsAccount.getCurrency(); final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
} }
@Transactional @Transactional
@Override @Override
public void postJournalEntries(final SavingsAccount account, final Set<Long> existingTransactionIds, public void postJournalEntries(final SavingsAccount account, final Set<Long> existingTransactionIds,
final Set<Long> existingReversedTransactionIds) { final Set<Long> existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) {
final boolean isAccountTransfer = false; final boolean isAccountTransfer = false;
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
} }
private Map<BusinessEntity, Object> constructEntityMap(final BusinessEntity entityEvent, Object entity) { private Map<BusinessEntity, Object> constructEntityMap(final BusinessEntity entityEvent, Object entity) {
@@ -19,10 +19,12 @@
package org.apache.fineract.portfolio.savings.domain; package org.apache.fineract.portfolio.savings.domain;
import java.util.List; import java.util.List;
import javax.persistence.LockModeType;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
@@ -32,6 +34,10 @@ public interface SavingsAccountRepository extends JpaRepository<SavingsAccount,
@Query("select s_acc from SavingsAccount s_acc where s_acc.client.id = :clientId") @Query("select s_acc from SavingsAccount s_acc where s_acc.client.id = :clientId")
List<SavingsAccount> findSavingAccountByClientId(@Param("clientId") Long clientId); List<SavingsAccount> findSavingAccountByClientId(@Param("clientId") Long clientId);
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select sa from SavingsAccount sa where sa.id = :savingsId")
SavingsAccount findOneLocked(@Param("savingsId") Long id);
@Query("select s_acc from SavingsAccount s_acc where s_acc.gsim.id = :gsimId") @Query("select s_acc from SavingsAccount s_acc where s_acc.gsim.id = :gsimId")
List<SavingsAccount> findSavingAccountByGsimId(@Param("gsimId") Long gsimId); List<SavingsAccount> findSavingAccountByGsimId(@Param("gsimId") Long gsimId);
@@ -18,13 +18,16 @@
*/ */
package org.apache.fineract.portfolio.savings.domain; package org.apache.fineract.portfolio.savings.domain;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.core.service.RoutingDataSource;
import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException; import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -43,10 +46,15 @@ import org.springframework.transaction.annotation.Transactional;
public class SavingsAccountRepositoryWrapper { public class SavingsAccountRepositoryWrapper {
private final SavingsAccountRepository repository; private final SavingsAccountRepository repository;
private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
private final JdbcTemplate jdbcTemplate;
@Autowired @Autowired
public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository) { public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository,
final SavingsAccountTransactionRepository savingsAccountTransactionRepository, final RoutingDataSource dataSource) {
this.repository = repository; this.repository = repository;
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
this.jdbcTemplate = new JdbcTemplate(dataSource);
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
@@ -57,6 +65,21 @@ public class SavingsAccountRepositoryWrapper {
return account; return account;
} }
@Transactional
public SavingsAccount findSavingsWithNotFoundDetection(final Long savingsId, final boolean backdatedTxnsAllowedTill) {
SavingsAccount account = null;
if (!backdatedTxnsAllowedTill) {
account = findOneWithNotFoundDetection(savingsId);
} else {
account = this.repository.findOneLocked(savingsId);
if (account == null) {
throw new SavingsAccountNotFoundException(savingsId);
}
}
return account;
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
public SavingsAccount findOneWithNotFoundDetection(final Long savingsId, final DepositAccountType depositAccountType) { public SavingsAccount findOneWithNotFoundDetection(final Long savingsId, final DepositAccountType depositAccountType) {
final SavingsAccount account = this.repository.findByIdAndDepositAccountType(savingsId, depositAccountType.getValue()); final SavingsAccount account = this.repository.findByIdAndDepositAccountType(savingsId, depositAccountType.getValue());
@@ -106,6 +129,17 @@ public class SavingsAccountRepositoryWrapper {
return this.repository.findSavingAccountByGsimId(gsimId); return this.repository.findSavingAccountByGsimId(gsimId);
} }
@Transactional
public List<SavingsAccountTransaction> findTransactionsAfterPivotDate(@Param("savingsAccount") SavingsAccount savingsAccount,
@Param("date") Date date) {
return this.savingsAccountTransactionRepository.findTransactionsAfterPivotDate(savingsAccount, date);
}
@Transactional
public List<SavingsAccountTransaction> findAllTransactions(@Param("savingsAccount") SavingsAccount savingsAccount) {
return this.savingsAccountTransactionRepository.findBySavingsAccount(savingsAccount);
}
// Root Entity is enough // Root Entity is enough
public SavingsAccount findNonClosedAccountByAccountNumber(@Param("accountNumber") String accountNumber) { public SavingsAccount findNonClosedAccountByAccountNumber(@Param("accountNumber") String accountNumber) {
return this.repository.findNonClosedAccountByAccountNumber(accountNumber); return this.repository.findNonClosedAccountByAccountNumber(accountNumber);
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
@@ -31,6 +32,7 @@ import javax.persistence.Transient;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod; import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
/** /**
@@ -83,6 +85,14 @@ public final class SavingsAccountSummary {
@Column(name = "last_interest_calculation_date") @Column(name = "last_interest_calculation_date")
private Date lastInterestCalculationDate; private Date lastInterestCalculationDate;
// Currently this represents the last interest posting date
@Temporal(TemporalType.DATE)
@Column(name = "interest_posted_till_date")
private Date interestPostedTillDate;
@Transient
private BigDecimal runningBalanceOnInterestPostingTillDate = BigDecimal.ZERO;
SavingsAccountSummary() { SavingsAccountSummary() {
// //
} }
@@ -102,11 +112,130 @@ public final class SavingsAccountSummary {
this.totalOverdraftInterestDerived = wrapper.calculateTotalOverdraftInterest(currency, transactions); this.totalOverdraftInterestDerived = wrapper.calculateTotalOverdraftInterest(currency, transactions);
this.totalWithholdTax = wrapper.calculateTotalWithholdTaxWithdrawal(currency, transactions); this.totalWithholdTax = wrapper.calculateTotalWithholdTaxWithdrawal(currency, transactions);
// boolean isUpdated = false;
updateRunningBalanceAndPivotDate(false, transactions, null, null, currency);
this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals)
.minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge) .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge)
.minus(totalOverdraftInterestDerived).minus(totalWithholdTax).getAmount(); .minus(totalOverdraftInterestDerived).minus(totalWithholdTax).getAmount();
} }
public void updateSummaryWithPivotConfig(final MonetaryCurrency currency, final SavingsAccountTransactionSummaryWrapper wrapper,
final SavingsAccountTransaction transaction, final List<SavingsAccountTransaction> savingsAccountTransactions) {
if (transaction != null) {
Money transactionAmount = Money.of(currency, transaction.getAmount());
switch (SavingsAccountTransactionType.fromInt(transaction.getTypeOf())) {
case DEPOSIT:
if (transaction.isDepositAndNotReversed() || transaction.isDividendPayoutAndNotReversed()) {
this.totalDeposits = Money.of(currency, this.totalDeposits).plus(transactionAmount).getAmount();
this.accountBalance = Money.of(currency, this.accountBalance).plus(transactionAmount).getAmount();
}
break;
case WITHDRAWAL:
if (transaction.isWithdrawal() && transaction.isNotReversed()) {
this.totalWithdrawals = Money.of(currency, this.totalWithdrawals).plus(transactionAmount).getAmount();
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
}
break;
case WITHDRAWAL_FEE:
if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed()) {
this.totalWithdrawalFees = Money.of(currency, this.totalWithdrawalFees).plus(transactionAmount).getAmount();
this.totalFeeCharge = Money.of(currency, this.totalFeeCharge).plus(transactionAmount).getAmount();
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
}
break;
case ANNUAL_FEE:
if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed()) {
this.totalAnnualFees = Money.of(currency, this.totalAnnualFees).plus(transactionAmount).getAmount();
this.totalFeeCharge = Money.of(currency, this.totalFeeCharge).plus(transactionAmount).getAmount();
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
}
break;
case WAIVE_CHARGES:
if (transaction.isWaiveFeeChargeAndNotReversed()) {
this.totalFeeChargesWaived = Money.of(currency, this.totalFeeChargesWaived).plus(transactionAmount.getAmount())
.getAmount();
} else if (transaction.isWaivePenaltyChargeAndNotReversed()) {
this.totalPenaltyChargesWaived = Money.of(currency, this.totalPenaltyChargesWaived)
.plus(transactionAmount.getAmount()).getAmount();
}
break;
case PAY_CHARGE:
if (transaction.isFeeChargeAndNotReversed()) {
this.totalFeeCharge = Money.of(currency, this.totalFeeCharge).plus(transactionAmount).getAmount();
} else if (transaction.isPenaltyChargeAndNotReversed()) {
this.totalPenaltyCharge = Money.of(currency, this.totalPenaltyCharge).plus(transactionAmount).getAmount();
}
if (transaction.isFeeChargeAndNotReversed() || transaction.isPenaltyChargeAndNotReversed()) {
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
}
break;
case OVERDRAFT_INTEREST:
if (transaction.isOverdraftInterestAndNotReversed()) {
this.totalOverdraftInterestDerived = Money.of(currency, this.totalOverdraftInterestDerived).plus(transactionAmount)
.getAmount();
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
}
break;
case WITHHOLD_TAX:
if (transaction.isWithHoldTaxAndNotReversed()) {
this.totalWithholdTax = Money.of(currency, this.totalWithholdTax).plus(transactionAmount).getAmount();
this.accountBalance = Money.of(currency, this.accountBalance).minus(transactionAmount).getAmount();
}
break;
default:
break;
}
} else {
// boolean isUpdated = false;
Money interestTotal = Money.of(currency, this.totalInterestPosted);
Money withHoldTaxTotal = Money.of(currency, this.totalWithholdTax);
final HashMap<String, Money> map = updateRunningBalanceAndPivotDate(true, savingsAccountTransactions, interestTotal,
withHoldTaxTotal, currency);
interestTotal = map.get("interestTotal");
withHoldTaxTotal = map.get("withHoldTax");
this.totalInterestPosted = interestTotal.getAmountDefaultedToNullIfZero();
this.totalWithholdTax = withHoldTaxTotal.getAmountDefaultedToNullIfZero();
this.accountBalance = Money.of(currency, this.accountBalance).plus(this.totalInterestPosted).minus(this.totalWithholdTax)
.getAmount();
}
}
@SuppressWarnings("unchecked")
private HashMap<String, Money> updateRunningBalanceAndPivotDate(final boolean backdatedTxnsAllowedTill,
final List<SavingsAccountTransaction> savingsAccountTransactions, Money interestTotal, Money withHoldTaxTotal,
MonetaryCurrency currency) {
boolean isUpdated = false;
HashMap<String, Money> map = new HashMap<>();
for (int i = savingsAccountTransactions.size() - 1; i >= 0; i--) {
final SavingsAccountTransaction savingsAccountTransaction = savingsAccountTransactions.get(i);
if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed() && !isUpdated) {
setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance(currency).getAmount());
setInterestPostedTillDate(savingsAccountTransaction.getLastTransactionDate());
isUpdated = true;
if (!backdatedTxnsAllowedTill) {
break;
}
}
if (backdatedTxnsAllowedTill) {
if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed()) {
interestTotal = interestTotal.plus(savingsAccountTransaction.getAmount(currency));
}
if (savingsAccountTransaction.isWithHoldTaxAndNotReversed()) {
withHoldTaxTotal = withHoldTaxTotal.plus(savingsAccountTransaction.getAmount(currency));
}
}
}
if (backdatedTxnsAllowedTill) {
map.put("interestTotal", interestTotal);
map.put("withHoldTax", withHoldTaxTotal);
}
return map;
}
public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List<PostingPeriod> allPostingPeriods) { public void updateFromInterestPeriodSummaries(final MonetaryCurrency currency, final List<PostingPeriod> allPostingPeriods) {
Money totalEarned = Money.zero(currency); Money totalEarned = Money.zero(currency);
@@ -137,4 +266,24 @@ public final class SavingsAccountSummary {
return this.totalInterestPosted; return this.totalInterestPosted;
} }
public Date getLastInterestCalculationDate() {
return this.lastInterestCalculationDate;
}
public void setInterestPostedTillDate(final Date date) {
this.interestPostedTillDate = date;
}
public Date getInterestPostedTillDate() {
return this.interestPostedTillDate;
}
public void setRunningBalanceOnPivotDate(final BigDecimal runningBalanceOnPivotDate) {
this.runningBalanceOnInterestPostingTillDate = runningBalanceOnPivotDate;
}
public BigDecimal getRunningBalanceOnPivotDate() {
return this.runningBalanceOnInterestPostingTillDate;
}
} }
@@ -445,6 +445,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom {
return LocalDate.ofInstant(this.dateOf.toInstant(), DateUtils.getDateTimeZoneOfTenant()); return LocalDate.ofInstant(this.dateOf.toInstant(), DateUtils.getDateTimeZoneOfTenant());
} }
public Date getLastTransactionDate() {
return this.dateOf;
}
public LocalDate getEndOfBalanceLocalDate() { public LocalDate getEndOfBalanceLocalDate() {
return balanceEndDate == null ? null : LocalDate.ofInstant(balanceEndDate.toInstant(), DateUtils.getDateTimeZoneOfTenant()); return balanceEndDate == null ? null : LocalDate.ofInstant(balanceEndDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
} }
@@ -18,8 +18,12 @@
*/ */
package org.apache.fineract.portfolio.savings.domain; package org.apache.fineract.portfolio.savings.domain;
import java.util.Date;
import java.util.List;
import javax.persistence.LockModeType;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
@@ -30,4 +34,12 @@ public interface SavingsAccountTransactionRepository
SavingsAccountTransaction findOneByIdAndSavingsAccountId(@Param("transactionId") Long transactionId, SavingsAccountTransaction findOneByIdAndSavingsAccountId(@Param("transactionId") Long transactionId,
@Param("savingsId") Long savingsId); @Param("savingsId") Long savingsId);
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select st from SavingsAccountTransaction st where st.savingsAccount = :savingsAccount and st.dateOf >= :date order by st.dateOf,st.createdDate,st.id")
List<SavingsAccountTransaction> findTransactionsAfterPivotDate(@Param("savingsAccount") SavingsAccount savingsAccount,
@Param("date") Date date);
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<SavingsAccountTransaction> findBySavingsAccount(@Param("savingsAccount") SavingsAccount savingsAccount);
} }
@@ -61,6 +61,17 @@ public final class SavingsAccountTransactionSummaryWrapper {
return total.getAmountDefaultedToNullIfZero(); return total.getAmountDefaultedToNullIfZero();
} }
public BigDecimal calculateTotalInterestPosted(final MonetaryCurrency currency, final BigDecimal currentInterestPosted,
final List<SavingsAccountTransaction> savingsAccountTransactions) {
Money total = Money.of(currency, currentInterestPosted);
for (final SavingsAccountTransaction transaction : savingsAccountTransactions) {
if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) {
total = total.plus(transaction.getAmount(currency));
}
}
return total.getAmountDefaultedToNullIfZero();
}
public BigDecimal calculateTotalWithdrawalFees(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) { public BigDecimal calculateTotalWithdrawalFees(final MonetaryCurrency currency, final List<SavingsAccountTransaction> transactions) {
Money total = Money.zero(currency); Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) { for (final SavingsAccountTransaction transaction : transactions) {
@@ -122,6 +133,17 @@ public final class SavingsAccountTransactionSummaryWrapper {
return total.getAmountDefaultedToNullIfZero(); return total.getAmountDefaultedToNullIfZero();
} }
public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency, BigDecimal overdraftPosted,
List<SavingsAccountTransaction> transactions) {
Money total = Money.of(currency, overdraftPosted);
for (final SavingsAccountTransaction transaction : transactions) {
if (transaction.isOverdraftInterestAndNotReversed()) {
total = total.plus(transaction.getAmount(currency));
}
}
return total.getAmountDefaultedToNullIfZero();
}
public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency, List<SavingsAccountTransaction> transactions) { public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency, List<SavingsAccountTransaction> transactions) {
Money total = Money.zero(currency); Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) { for (final SavingsAccountTransaction transaction : transactions) {
@@ -23,6 +23,7 @@ import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval; import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -176,4 +177,9 @@ public final class SavingsHelper {
public Collection<Long> fetchPostInterestTransactionIds(Long accountId) { public Collection<Long> fetchPostInterestTransactionIds(Long accountId) {
return this.accountTransfersReadPlatformService.fetchPostInterestTransactionIds(accountId); return this.accountTransfersReadPlatformService.fetchPostInterestTransactionIds(accountId);
} }
public Collection<Long> fetchPostInterestTransactionIds(Long accountId, Date pivotDate) {
return this.accountTransfersReadPlatformService.fetchPostInterestTransactionIdsWithPivotDate(accountId, pivotDate);
}
} }
@@ -0,0 +1,31 @@
/**
* 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.exception;
import java.time.LocalDate;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class TransactionBeforePivotDateNotAllowed extends AbstractPlatformDomainRuleException {
public TransactionBeforePivotDateNotAllowed(final LocalDate date, final LocalDate interestPostingDate) {
super("error.msg.savings.transaction.is.not.allowed",
"Savings Account transaction date " + date + " should be after the last interest posting date " + interestPostingDate);
}
}
@@ -587,6 +587,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, "); selectFieldsSqlBuilder.append("sa.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
selectFieldsSqlBuilder.append("sa.total_deposits_derived as totalDeposits, "); selectFieldsSqlBuilder.append("sa.total_deposits_derived as totalDeposits, ");
selectFieldsSqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, "); selectFieldsSqlBuilder.append("sa.total_withdrawals_derived as totalWithdrawals, ");
selectFieldsSqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, ");
selectFieldsSqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, "); selectFieldsSqlBuilder.append("sa.total_withdrawal_fees_derived as totalWithdrawalFees, ");
selectFieldsSqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, "); selectFieldsSqlBuilder.append("sa.total_annual_fees_derived as totalAnnualFees, ");
selectFieldsSqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, "); selectFieldsSqlBuilder.append("sa.total_interest_earned_derived as totalInterestEarned, ");
@@ -739,6 +740,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge"); final BigDecimal totalPenaltyCharge = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalPenaltyCharge");
final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax"); final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax");
final BigDecimal totalOverdraftInterestDerived = null; final BigDecimal totalOverdraftInterestDerived = null;
final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate");
final boolean withHoldTax = rs.getBoolean("withHoldTax"); final boolean withHoldTax = rs.getBoolean("withHoldTax");
final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId");
@@ -751,7 +753,8 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final BigDecimal availableBalance = null; final BigDecimal availableBalance = null;
final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, null, null, availableBalance); totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, null, null, availableBalance,
interestPostedTillDate);
return DepositAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName, return DepositAccountData.instance(id, accountNo, externalId, groupId, groupName, clientId, clientName, productId, productName,
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
@@ -244,14 +244,14 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
if (activationChargeAmount.isGreaterThanZero()) { if (activationChargeAmount.isGreaterThanZero()) {
payActivationCharge(account, user); payActivationCharge(account, user);
} }
if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) { if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate(), false)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} }
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -266,7 +266,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(), account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),
depositAccountOnHoldTransactions); depositAccountOnHoldTransactions, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
} }
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
@@ -295,7 +295,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
for (SavingsAccountCharge savingsAccountCharge : account.charges()) { for (SavingsAccountCharge savingsAccountCharge : account.charges()) {
if (savingsAccountCharge.isSavingsActivation()) { if (savingsAccountCharge.isSavingsActivation()) {
account.payCharge(savingsAccountCharge, savingsAccountCharge.getAmount(account.getCurrency()), account.payCharge(savingsAccountCharge, savingsAccountCharge.getAmount(account.getCurrency()),
account.getActivationLocalDate(), user); account.getActivationLocalDate(), user, false);
} }
} }
} }
@@ -368,14 +368,14 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
account.updateOverduePayments(overdueUptoDate); account.updateOverduePayments(overdueUptoDate);
final boolean isInterestTransfer = false; final boolean isInterestTransfer = false;
final LocalDate postInterestOnDate = null; final LocalDate postInterestOnDate = null;
if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) { if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate(), false)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, false);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -384,7 +384,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(), account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),
depositAccountOnHoldTransactions); depositAccountOnHoldTransactions, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
} }
@@ -527,7 +527,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
this.savingAccountRepositoryWrapper.save(account); this.savingAccountRepositoryWrapper.save(account);
@@ -571,7 +571,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
@@ -645,12 +645,12 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
if (savingsAccountTransaction.isPostInterestCalculationRequired() if (savingsAccountTransaction.isPostInterestCalculationRequired()
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) {
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, false);
} else { } else {
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -658,7 +658,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions); account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions,
false);
// account.activateAccountBasedOnBalance(); // account.activateAccountBasedOnBalance();
final boolean isPreMatureClosure = false; final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
@@ -745,22 +746,22 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
if (savingsAccountTransaction.isDeposit()) { if (savingsAccountTransaction.isDeposit()) {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
paymentDetail, savingsAccountTransaction.createdDate(), user, accountType); paymentDetail, savingsAccountTransaction.createdDate(), user, accountType);
transaction = account.deposit(transactionDTO); transaction = account.deposit(transactionDTO, false);
} else { } else {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
paymentDetail, savingsAccountTransaction.createdDate(), user, accountType); paymentDetail, savingsAccountTransaction.createdDate(), user, accountType);
transaction = account.withdraw(transactionDTO, true); transaction = account.withdraw(transactionDTO, true, false);
} }
final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction);
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
final LocalDate postInterestOnDate = null; final LocalDate postInterestOnDate = null;
if (account.isBeforeLastPostingPeriod(transactionDate) if (account.isBeforeLastPostingPeriod(transactionDate, false)
|| account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) {
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, false);
} else { } else {
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -768,7 +769,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions); account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions,
false);
account.activateAccountBasedOnBalance(); account.activateAccountBasedOnBalance();
if (savingsAccountTransaction.isDeposit()) { if (savingsAccountTransaction.isDeposit()) {
@@ -982,7 +984,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(newTransferTransaction); this.savingsAccountTransactionRepository.save(newTransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
@@ -1016,7 +1018,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(withdrawtransferTransaction); this.savingsAccountTransactionRepository.save(withdrawtransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
@@ -1060,7 +1062,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(acceptTransferTransaction); this.savingsAccountTransactionRepository.save(acceptTransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
@@ -1209,18 +1211,18 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.waiveCharge(savingsAccountChargeId, user); account.waiveCharge(savingsAccountChargeId, user, false);
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), false)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, false);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -1229,7 +1231,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction, account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction,
depositAccountOnHoldTransactions); depositAccountOnHoldTransactions, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
@@ -1348,18 +1350,18 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user); account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user, false);
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) { if (account.isBeforeLastPostingPeriod(transactionDate, false)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, false);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -1368,7 +1370,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
} }
account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(), account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(),
depositAccountOnHoldTransactions); depositAccountOnHoldTransactions, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
@@ -1436,7 +1438,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, false);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
} }
@@ -313,6 +313,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("sa.withhold_tax as withHoldTax, "); sqlBuilder.append("sa.withhold_tax as withHoldTax, ");
sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, "); sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, ");
sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, "); sqlBuilder.append("sa.last_interest_calculation_date as lastInterestCalculationDate, ");
sqlBuilder.append("sa.interest_posted_till_date as interestPostedTillDate, ");
sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, "); sqlBuilder.append("sa.total_savings_amount_on_hold as onHoldAmount, ");
sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, "); sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, ");
sqlBuilder.append("(select IFNULL(max(sat.transaction_date),sa.activatedon_date) "); sqlBuilder.append("(select IFNULL(max(sat.transaction_date),sa.activatedon_date) ");
@@ -508,6 +509,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, final BigDecimal totalOverdraftInterestDerived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
"totalOverdraftInterestDerived"); "totalOverdraftInterestDerived");
final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax"); final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax");
final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate");
final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
"minBalanceForInterestCalculation"); "minBalanceForInterestCalculation");
@@ -536,7 +538,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals, final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge, totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, interestNotPosted, lastInterestCalculationDate, totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, interestNotPosted, lastInterestCalculationDate,
availableBalance); availableBalance, interestPostedTillDate);
final boolean withHoldTax = rs.getBoolean("withHoldTax"); final boolean withHoldTax = rs.getBoolean("withHoldTax");
final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId"); final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId");
@@ -85,7 +85,7 @@ public interface SavingsAccountWritePlatformService {
CommandProcessingResult postInterest(JsonCommand command); CommandProcessingResult postInterest(JsonCommand command);
void postInterest(SavingsAccount account, boolean postInterestAs, LocalDate transactionDate); void postInterest(SavingsAccount account, boolean postInterestAs, LocalDate transactionDate, boolean backdatedTxnsAllowedTill);
CommandProcessingResult blockAccount(Long savingsId); CommandProcessingResult blockAccount(Long savingsId);
@@ -239,11 +239,14 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountTransactionDataValidator.validateActivation(command); this.savingsAccountTransactionDataValidator.validateActivation(command);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final Map<String, Object> changes = account.activate(user, command, DateUtils.getLocalDateOfTenant()); final Map<String, Object> changes = account.activate(user, command, DateUtils.getLocalDateOfTenant());
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(), entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
@@ -253,11 +256,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
processPostActiveActions(account, fmt, existingTransactionIds, existingReversedTransactionIds); processPostActiveActions(account, fmt, existingTransactionIds, existingReversedTransactionIds);
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
} }
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_ACTIVATE, this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_ACTIVATE,
constructEntityMap(BusinessEntity.SAVING, account)); constructEntityMap(BusinessEntity.SAVING, account));
@@ -286,17 +288,20 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (amountForDeposit.isGreaterThanZero()) { if (amountForDeposit.isGreaterThanZero()) {
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
this.savingsAccountDomainService.handleDeposit(account, fmt, account.getActivationLocalDate(), amountForDeposit.getAmount(), this.savingsAccountDomainService.handleDeposit(account, fmt, account.getActivationLocalDate(), amountForDeposit.getAmount(),
null, isAccountTransfer, isRegularTransaction); null, isAccountTransfer, isRegularTransaction, false);
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
} }
account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user); account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user);
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(), account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),
depositAccountOnHoldTransactions); depositAccountOnHoldTransactions, false);
} }
@Transactional @Transactional
@@ -330,25 +335,30 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountTransactionDataValidator.validate(command); this.savingsAccountTransactionDataValidator.validate(command);
boolean isGsim = false; boolean isGsim = false;
if (this.savingAccountRepositoryWrapper.findOneWithNotFoundDetection(savingsId).getGsim() != null) { final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill);
if (account.getGsim() != null) {
isGsim = true; isGsim = true;
LOG.info("is gsim"); LOG.info("is gsim");
} }
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account);
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
boolean isRegularTransaction = true; boolean isRegularTransaction = true;
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate,
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction); transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
if (isGsim && (deposit.getId() != null)) { if (isGsim && (deposit.getId() != null)) {
@@ -394,9 +404,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountTransactionDataValidator.validate(command); this.savingsAccountTransactionDataValidator.validate(command);
boolean isGsim = false; boolean isGsim = false;
if (this.savingAccountRepositoryWrapper.findOneWithNotFoundDetection(savingsId).getGsim() != null) {
isGsim = true;
}
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
@@ -407,8 +414,17 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill);
if (account.getGsim() != null) {
isGsim = true;
}
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account);
final boolean isAccountTransfer = false; final boolean isAccountTransfer = false;
final boolean isRegularTransaction = true; final boolean isRegularTransaction = true;
final boolean isApplyWithdrawFee = true; final boolean isApplyWithdrawFee = true;
@@ -417,7 +433,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer, final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance); isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance);
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate, final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate,
transactionAmount, paymentDetail, transactionBooleanValues); transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill);
if (isGsim && (withdrawal.getId() != null)) { if (isGsim && (withdrawal.getId() != null)) {
GroupSavingsIndividualMonitoring gsim = gsimRepository.findById(account.getGsim().getId()).get(); GroupSavingsIndividualMonitoring gsim = gsimRepository.findById(account.getGsim().getId()).get();
@@ -456,7 +472,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MM yyyy").withZone(DateUtils.getDateTimeZoneOfTenant()); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MM yyyy").withZone(DateUtils.getDateTimeZoneOfTenant());
while (todaysDate.isAfter(savingsAccountCharge.getDueLocalDate())) { while (todaysDate.isAfter(savingsAccountCharge.getDueLocalDate())) {
this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt, user); this.payCharge(savingsAccountCharge, savingsAccountCharge.getDueLocalDate(), savingsAccountCharge.amount(), fmt, user, false);
} }
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
@@ -476,14 +492,21 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.isSavingsInterestPostingAtCurrentPeriodEnd(); .isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode());
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
final LocalDate postInterestOnDate = null; final LocalDate postInterestOnDate = null;
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.save(account); this.savingAccountRepositoryWrapper.save(account);
@@ -502,8 +525,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
Long savingsId = command.getSavingsId(); Long savingsId = command.getSavingsId();
final boolean postInterestAs = command.booleanPrimitiveValueOfParameterNamed("isPostInterestAsOn"); final boolean postInterestAs = command.booleanPrimitiveValueOfParameterNamed("isPostInterestAsOn");
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account);
if (postInterestAs == true) { if (postInterestAs == true) {
if (transactionDate == null) { if (transactionDate == null) {
@@ -513,7 +541,14 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (transactionDate.isBefore(account.accountSubmittedOrActivationDate())) { if (transactionDate.isBefore(account.accountSubmittedOrActivationDate())) {
throw new PostInterestAsOnDateException(PostInterestAsOnExceptionType.ACTIVATION_DATE); throw new PostInterestAsOnDateException(PostInterestAsOnExceptionType.ACTIVATION_DATE);
} }
List<SavingsAccountTransaction> savingTransactions = account.getTransactions();
List<SavingsAccountTransaction> savingTransactions = null;
if (backdatedTxnsAllowedTill) {
savingTransactions = account.getSavingsAccountTransactionsWithPivotConfig();
} else {
savingTransactions = account.getTransactions();
}
for (SavingsAccountTransaction savingTransaction : savingTransactions) { for (SavingsAccountTransaction savingTransaction : savingTransactions) {
if (Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant()).before(savingTransaction.getDateOf())) { if (Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant()).before(savingTransaction.getDateOf())) {
throw new PostInterestAsOnDateException(PostInterestAsOnExceptionType.LAST_TRANSACTION_DATE); throw new PostInterestAsOnDateException(PostInterestAsOnExceptionType.LAST_TRANSACTION_DATE);
@@ -526,7 +561,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
} }
} }
postInterest(account, postInterestAs, transactionDate); postInterest(account, postInterestAs, transactionDate, backdatedTxnsAllowedTill);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_POST_INTEREST, this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_POST_INTEREST,
constructEntityMap(BusinessEntity.SAVING, account)); constructEntityMap(BusinessEntity.SAVING, account));
@@ -541,7 +576,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Transactional @Transactional
@Override @Override
public void postInterest(final SavingsAccount account, final boolean postInterestAs, final LocalDate transactionDate) { public void postInterest(final SavingsAccount account, final boolean postInterestAs, final LocalDate transactionDate,
final boolean backdatedTxnsAllowedTill) {
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd(); .isSavingsInterestPostingAtCurrentPeriodEnd();
@@ -550,7 +586,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|| (account.allowOverdraft() && account.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) { || (account.allowOverdraft() && account.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) {
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
if (backdatedTxnsAllowedTill) {
updateSavingsTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
} else {
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
}
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode()); final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode());
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
@@ -558,19 +600,26 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (postInterestAs) { if (postInterestAs) {
postInterestOnDate = transactionDate; postInterestOnDate = transactionDate;
} }
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, backdatedTxnsAllowedTill);
// for generating transaction id's
List<SavingsAccountTransaction> transactions = account.getTransactions(); if (!backdatedTxnsAllowedTill) {
for (SavingsAccountTransaction accountTransaction : transactions) { List<SavingsAccountTransaction> transactions = account.getTransactions();
if (accountTransaction.getId() == null) { for (SavingsAccountTransaction accountTransaction : transactions) {
this.savingsAccountTransactionRepository.save(accountTransaction); if (accountTransaction.getId() == null) {
this.savingsAccountTransactionRepository.save(accountTransaction);
}
} }
} }
if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, backdatedTxnsAllowedTill);
} }
} }
@@ -582,7 +631,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.isSavingsInterestPostingAtCurrentPeriodEnd(); .isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -593,6 +642,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId); throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId);
} }
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(savingsAccountTransaction.getTransactionLocalDate(),
account);
if (!allowAccountTransferModification if (!allowAccountTransferModification
&& this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)) { && this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)) {
throw new PlatformServiceUnavailableException("error.msg.saving.account.transfer.transaction.update.not.allowed", throw new PlatformServiceUnavailableException("error.msg.saving.account.transfer.transaction.update.not.allowed",
@@ -626,22 +678,23 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
if (savingsAccountTransaction.isPostInterestCalculationRequired() if (savingsAccountTransaction.isPostInterestCalculationRequired()
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) {
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, false);
} else { } else {
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions); account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions,
false);
account.activateAccountBasedOnBalance(); account.activateAccountBasedOnBalance();
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withEntityId(savingsId) // .withEntityId(savingsId) //
.withOfficeId(account.officeId()) // .withOfficeId(account.officeId()) //
@@ -681,7 +734,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
if (account.isNotActive()) { if (account.isNotActive()) {
throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction); throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction);
} }
@@ -691,6 +745,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
} }
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
@@ -716,29 +771,32 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
paymentDetail, savingsAccountTransaction.createdDate(), user, accountType); paymentDetail, savingsAccountTransaction.createdDate(), user, accountType);
if (savingsAccountTransaction.isDeposit()) { if (savingsAccountTransaction.isDeposit()) {
transaction = account.deposit(transactionDTO); transaction = account.deposit(transactionDTO, false);
} else { } else {
transaction = account.withdraw(transactionDTO, true); transaction = account.withdraw(transactionDTO, true, false);
} }
final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction);
final LocalDate postInterestOnDate = null; final LocalDate postInterestOnDate = null;
if (account.isBeforeLastPostingPeriod(transactionDate) if (account.isBeforeLastPostingPeriod(transactionDate, false)
|| account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) { || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) {
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, false);
} else { } else {
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account); .findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions);
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions,
false);
account.activateAccountBasedOnBalance(); account.activateAccountBasedOnBalance();
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withEntityId(newtransactionId) // .withEntityId(newtransactionId) //
.withOfficeId(account.officeId()) // .withOfficeId(account.officeId()) //
@@ -802,7 +860,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult close(final Long savingsId, final JsonCommand command) { public CommandProcessingResult close(final Long savingsId, final JsonCommand command) {
final AppUser user = this.context.authenticatedUser(); final AppUser user = this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
this.savingsAccountTransactionDataValidator.validateClosing(command, account); this.savingsAccountTransactionDataValidator.validateClosing(command, account);
final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId); final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId);
@@ -854,7 +912,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance); isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance);
this.savingsAccountDomainService.handleWithdrawal(account, fmt, closedDate, transactionAmount, paymentDetail, this.savingsAccountDomainService.handleWithdrawal(account, fmt, closedDate, transactionAmount, paymentDetail,
transactionBooleanValues); transactionBooleanValues, false);
} }
@@ -909,12 +967,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(newTransferTransaction); this.savingsAccountTransactionRepository.save(newTransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds, false);
return newTransferTransaction; return newTransferTransaction;
} }
@@ -940,12 +998,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(withdrawtransferTransaction); this.savingsAccountTransactionRepository.save(withdrawtransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds, false);
return withdrawtransferTransaction; return withdrawtransferTransaction;
} }
@@ -982,12 +1040,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(acceptTransferTransaction); this.savingsAccountTransactionRepository.save(acceptTransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds, false);
return acceptTransferTransaction; return acceptTransferTransaction;
} }
@@ -1004,7 +1062,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Long savingsAccountId = command.getSavingsId(); final Long savingsAccountId = command.getSavingsId();
this.savingsAccountChargeDataValidator.validateAdd(command.json()); this.savingsAccountChargeDataValidator.validateAdd(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId, false);
checkClientOrGroupActive(savingsAccount); checkClientOrGroupActive(savingsAccount);
final Locale locale = command.extractLocale(); final Locale locale = command.extractLocale();
@@ -1073,7 +1131,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
// SavingsAccount Charge entity // SavingsAccount Charge entity
final Long savingsChargeId = command.entityId(); final Long savingsChargeId = command.entityId();
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId, false);
checkClientOrGroupActive(savingsAccount); checkClientOrGroupActive(savingsAccount);
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(savingsChargeId, final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(savingsChargeId,
@@ -1132,25 +1190,35 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
// Get Savings account from savings charge // Get Savings account from savings charge
final SavingsAccount account = savingsAccountCharge.savingsAccount(); final SavingsAccount account = savingsAccountCharge.savingsAccount();
this.savingAccountAssembler.assignSavingAccountHelpers(account);
final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus();
this.savingAccountAssembler.loadTransactionsToSavingsAccount(account, backdatedTxnsAllowedTill);
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.waiveCharge(savingsAccountChargeId, user); if (backdatedTxnsAllowedTill) {
updateSavingsTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
} else {
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
}
account.waiveCharge(savingsAccountChargeId, user, backdatedTxnsAllowedTill);
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) { if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, backdatedTxnsAllowedTill);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
@@ -1158,11 +1226,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
} }
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction, account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction,
depositAccountOnHoldTransactions); depositAccountOnHoldTransactions, backdatedTxnsAllowedTill);
if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, backdatedTxnsAllowedTill);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withEntityId(savingsAccountChargeId) // .withEntityId(savingsAccountChargeId) //
.withOfficeId(account.officeId()) // .withOfficeId(account.officeId()) //
@@ -1178,7 +1250,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@SuppressWarnings("unused") final JsonCommand command) { @SuppressWarnings("unused") final JsonCommand command) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId); final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId, false);
checkClientOrGroupActive(savingsAccount); checkClientOrGroupActive(savingsAccount);
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
.findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId); .findOneWithNotFoundDetection(savingsAccountChargeId, savingsAccountId);
@@ -1232,7 +1304,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
} }
} }
SavingsAccountTransaction chargeTransaction = this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt, user); final boolean backdatedTxnsAllowedTill = false;
SavingsAccountTransaction chargeTransaction = this.payCharge(savingsAccountCharge, transactionDate, amountPaid, fmt, user,
backdatedTxnsAllowedTill);
final String noteText = command.stringValueOfParameterNamed("note"); final String noteText = command.stringValueOfParameterNamed("note");
if (StringUtils.isNotBlank(noteText)) { if (StringUtils.isNotBlank(noteText)) {
@@ -1263,13 +1338,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MM yyyy").withZone(DateUtils.getDateTimeZoneOfTenant()); final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MM yyyy").withZone(DateUtils.getDateTimeZoneOfTenant());
while (transactionDate.isAfter(savingsAccountCharge.getDueLocalDate()) && savingsAccountCharge.isNotFullyPaid()) { while (transactionDate.isAfter(savingsAccountCharge.getDueLocalDate()) && savingsAccountCharge.isNotFullyPaid()) {
payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt, user); payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt, user, false);
} }
} }
@Transactional @Transactional
private SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate, private SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final LocalDate transactionDate,
final BigDecimal amountPaid, final DateTimeFormatter formatter, final AppUser user) { final BigDecimal amountPaid, final DateTimeFormatter formatter, final AppUser user, final boolean backdatedTxnsAllowedTill) {
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd(); .isSavingsInterestPostingAtCurrentPeriodEnd();
@@ -1281,18 +1356,19 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
SavingsAccountTransaction chargeTransaction = account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user); SavingsAccountTransaction chargeTransaction = account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user,
backdatedTxnsAllowedTill);
boolean isInterestTransfer = false; boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null; LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64; final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) { if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate); postInterestOnDate, isInterestTransfer);
} else { } else {
final LocalDate today = DateUtils.getLocalDateOfTenant(); final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate); financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
} }
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null; List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -1301,13 +1377,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
} }
account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(), account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(),
depositAccountOnHoldTransactions); depositAccountOnHoldTransactions, backdatedTxnsAllowedTill);
saveTransactionToGenerateTransactionId(chargeTransaction); saveTransactionToGenerateTransactionId(chargeTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, backdatedTxnsAllowedTill);
return chargeTransaction; return chargeTransaction;
} }
@@ -1318,14 +1394,20 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(account.findExistingReversedTransactionIds());
} }
private void updateSavingsTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
Set<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(account.findCurrentTransactionIdsWithPivotDateConfig());
existingReversedTransactionIds.addAll(account.findCurrentReversedTransactionIdsWithPivotDateConfig());
}
private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds, private void postJournalEntries(final SavingsAccount savingsAccount, final Set<Long> existingTransactionIds,
final Set<Long> existingReversedTransactionIds) { final Set<Long> existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) {
final MonetaryCurrency currency = savingsAccount.getCurrency(); final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
boolean isAccountTransfer = false; boolean isAccountTransfer = false;
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(), final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData); this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
} }
@@ -1502,19 +1584,19 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Override @Override
@Transactional @Transactional
public void setSubStatusInactive(Long savingsId) { public void setSubStatusInactive(Long savingsId) {
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.setSubStatusInactive(appuserRepository.fetchSystemUser()); account.setSubStatusInactive(appuserRepository.fetchSystemUser(), false);
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
} }
@Override @Override
@Transactional @Transactional
public void setSubStatusDormant(Long savingsId) { public void setSubStatusDormant(Long savingsId) {
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
account.setSubStatusDormant(); account.setSubStatusDormant();
this.savingAccountRepositoryWrapper.save(account); this.savingAccountRepositoryWrapper.save(account);
} }
@@ -1522,13 +1604,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Override @Override
@Transactional @Transactional
public void escheat(Long savingsId) { public void escheat(Long savingsId) {
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
final Set<Long> existingTransactionIds = new HashSet<>(); final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>(); final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.escheat(appuserRepository.fetchSystemUser()); account.escheat(appuserRepository.fetchSystemUser());
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
} }
private AppUser getAppUserIfPresent() { private AppUser getAppUserIfPresent() {
@@ -1573,7 +1655,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final Map<String, Object> changes = account.block(); final Map<String, Object> changes = account.block();
@@ -1589,7 +1671,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult unblockAccount(final Long savingsId) { public CommandProcessingResult unblockAccount(final Long savingsId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final Map<String, Object> changes = account.unblock(); final Map<String, Object> changes = account.unblock();
@@ -1606,13 +1688,23 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult holdAmount(final Long savingsId, final JsonCommand command) { public CommandProcessingResult holdAmount(final Long savingsId, final JsonCommand command) {
final AppUser submittedBy = this.context.authenticatedUser(); final AppUser submittedBy = this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
SavingsAccountTransaction transacton = this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(), SavingsAccountTransaction transacton = this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(),
account, submittedBy); account, submittedBy, backdatedTxnsAllowedTill);
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transacton.getTransactionLocalDate(), account);
this.savingsAccountTransactionRepository.save(transacton); this.savingsAccountTransactionRepository.save(transacton);
if (backdatedTxnsAllowedTill) {
// Check again whether transactions are modified
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.saveAndFlush(account); this.savingAccountRepositoryWrapper.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(transacton.getId()).withOfficeId(account.officeId()) return new CommandProcessingResultBuilder().withEntityId(transacton.getId()).withOfficeId(account.officeId())
@@ -1629,12 +1721,21 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountTransaction transaction = this.savingsAccountTransactionDataValidator final SavingsAccountTransaction transaction = this.savingsAccountTransactionDataValidator
.validateReleaseAmountAndAssembleForm(holdTransaction, submittedBy); .validateReleaseAmountAndAssembleForm(holdTransaction, submittedBy);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transaction.getTransactionLocalDate(), account);
account.releaseOnHoldAmount(transaction.getAmount()); account.releaseOnHoldAmount(transaction.getAmount());
this.savingsAccountTransactionRepository.save(transaction); this.savingsAccountTransactionRepository.save(transaction);
holdTransaction.updateReleaseId(transaction.getId()); holdTransaction.updateReleaseId(transaction.getId());
if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.save(account); this.savingAccountRepositoryWrapper.save(account);
return new CommandProcessingResultBuilder().withEntityId(transaction.getId()).withOfficeId(account.officeId()) return new CommandProcessingResultBuilder().withEntityId(transaction.getId()).withOfficeId(account.officeId())
@@ -1645,7 +1746,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult blockCredits(final Long savingsId) { public CommandProcessingResult blockCredits(final Long savingsId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final Map<String, Object> changes = account.blockCredits(account.getSubStatus()); final Map<String, Object> changes = account.blockCredits(account.getSubStatus());
@@ -1661,7 +1762,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult unblockCredits(final Long savingsId) { public CommandProcessingResult unblockCredits(final Long savingsId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final Map<String, Object> changes = account.unblockCredits(); final Map<String, Object> changes = account.unblockCredits();
@@ -1677,7 +1778,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult blockDebits(final Long savingsId) { public CommandProcessingResult blockDebits(final Long savingsId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final Map<String, Object> changes = account.blockDebits(account.getSubStatus()); final Map<String, Object> changes = account.blockDebits(account.getSubStatus());
@@ -1693,7 +1794,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult unblockDebits(final Long savingsId) { public CommandProcessingResult unblockDebits(final Long savingsId) {
this.context.authenticatedUser(); this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
final Map<String, Object> changes = account.unblockDebits(); final Map<String, Object> changes = account.unblockDebits();
@@ -359,7 +359,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
final Map<String, Object> changes = new LinkedHashMap<>(20); final Map<String, Object> changes = new LinkedHashMap<>(20);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
account.modifyApplication(command, changes); account.modifyApplication(command, changes);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME); account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME);
@@ -451,7 +451,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
@Override @Override
public CommandProcessingResult deleteApplication(final Long savingsId) { public CommandProcessingResult deleteApplication(final Long savingsId) {
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account); checkClientOrGroupActive(account);
if (account.isNotSubmittedAndPendingApproval()) { if (account.isNotSubmittedAndPendingApproval()) {
@@ -515,7 +515,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator.validateApproval(command.json()); this.savingsAccountApplicationTransitionApiJsonValidator.validateApproval(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount); checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(), entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
@@ -581,7 +581,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator.validateForUndo(command.json()); this.savingsAccountApplicationTransitionApiJsonValidator.validateForUndo(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount); checkClientOrGroupActive(savingsAccount);
final Map<String, Object> changes = savingsAccount.undoApplicationApproval(); final Map<String, Object> changes = savingsAccount.undoApplicationApproval();
@@ -639,7 +639,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator.validateRejection(command.json()); this.savingsAccountApplicationTransitionApiJsonValidator.validateRejection(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount); checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(), entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
@@ -677,7 +677,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json()); this.savingsAccountApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId); final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount); checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(), entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
@@ -752,7 +752,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
generateAccountNumber(account); generateAccountNumber(account);
// post journal entries for activation charges // post journal entries for activation charges
this.savingsAccountDomainService.postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); this.savingsAccountDomainService.postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withSavingsId(account.getId()) // .withSavingsId(account.getId()) //
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant; import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException; import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
@@ -54,6 +55,7 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
private SavingsAccountRepositoryWrapper savingsAccountRepository; private SavingsAccountRepositoryWrapper savingsAccountRepository;
private SavingsAccountAssembler savingAccountAssembler; private SavingsAccountAssembler savingAccountAssembler;
private FineractPlatformTenant tenant; private FineractPlatformTenant tenant;
private ConfigurationDomainService configurationDomainService;
public void setSavingsIds(Collection<Long> savingsIds) { public void setSavingsIds(Collection<Long> savingsIds) {
this.savingsIds = savingsIds; this.savingsIds = savingsIds;
@@ -82,7 +84,7 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
ThreadLocalContextUtil.setTenant(tenant); ThreadLocalContextUtil.setTenant(tenant);
Integer maxNumberOfRetries = tenant.getConnection().getMaxRetriesOnDeadlock(); Integer maxNumberOfRetries = tenant.getConnection().getMaxRetriesOnDeadlock();
Integer maxIntervalBetweenRetries = tenant.getConnection().getMaxIntervalBetweenRetries(); Integer maxIntervalBetweenRetries = tenant.getConnection().getMaxIntervalBetweenRetries();
final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig();
int i = 0; int i = 0;
if (!savingsIds.isEmpty()) { if (!savingsIds.isEmpty()) {
List<Throwable> errors = new ArrayList<>(); List<Throwable> errors = new ArrayList<>();
@@ -95,7 +97,8 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
this.savingAccountAssembler.assignSavingAccountHelpers(savingsAccount); this.savingAccountAssembler.assignSavingAccountHelpers(savingsAccount);
boolean postInterestAsOn = false; boolean postInterestAsOn = false;
LocalDate transactionDate = null; LocalDate transactionDate = null;
this.savingsAccountWritePlatformService.postInterest(savingsAccount, postInterestAsOn, transactionDate); this.savingsAccountWritePlatformService.postInterest(savingsAccount, postInterestAsOn, transactionDate,
backdatedTxnsAllowedTill);
numberOfRetries = maxNumberOfRetries + 1; numberOfRetries = maxNumberOfRetries + 1;
} catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) { } catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
@@ -50,9 +50,9 @@ public class ShareAccountSchedularServiceImpl implements ShareAccountSchedularSe
public void postDividend(final Long dividendDetailId, final Long savingsId) { public void postDividend(final Long dividendDetailId, final Long savingsId) {
ShareAccountDividendDetails shareAccountDividendDetails = this.shareAccountDividendRepository.findById(dividendDetailId).get(); ShareAccountDividendDetails shareAccountDividendDetails = this.shareAccountDividendRepository.findById(dividendDetailId).get();
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsId); final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsId, false);
SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountDomainService.handleDividendPayout(savingsAccount, SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountDomainService.handleDividendPayout(savingsAccount,
DateUtils.getLocalDateOfTenant(), shareAccountDividendDetails.getAmount()); DateUtils.getLocalDateOfTenant(), shareAccountDividendDetails.getAmount(), false);
shareAccountDividendDetails.update(ShareAccountDividendStatusType.POSTED.getValue(), savingsAccountTransaction.getId()); shareAccountDividendDetails.update(ShareAccountDividendStatusType.POSTED.getValue(), savingsAccountTransaction.getId());
this.shareAccountDividendRepository.save(shareAccountDividendDetails); this.shareAccountDividendRepository.save(shareAccountDividendDetails);
} }
@@ -0,0 +1,24 @@
--
-- 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.
--
ALTER TABLE m_savings_account ADD COLUMN interest_posted_till_date DATE NULL DEFAULT NULL;
insert into c_configuration(name, enabled, value, description) values('allow-backdated-transaction-before-interest-posting', 1, 0, 'Avoid retrieving all transactions in a savings account');
insert into c_configuration(name, enabled, value, description) values('allow-backdated-transaction-before-interest-posting-date-for-days', 0, 0, 'One time configuration to relax the backdated transactions');
@@ -42,6 +42,7 @@ import java.util.Locale;
import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.CommonConstants; import org.apache.fineract.integrationtests.common.CommonConstants;
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.PaymentTypeHelper; import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
import org.apache.fineract.integrationtests.common.TaxComponentHelper; import org.apache.fineract.integrationtests.common.TaxComponentHelper;
@@ -128,6 +129,115 @@ public class ClientSavingsIntegrationTest {
// verifySavingsInterest(savingsInterest); // verifySavingsInterest(savingsInterest);
} }
@Test
public void testSavingsLastTransactionAndRunningBalanceUpdate() {
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
// Assertions.assertNotNull(clientID);
final String minBalanceForInterestCalculation = null;
final String minRequiredBalance = null;
final String enforceMinRequiredBalance = "false";
final boolean allowOverdraft = false;
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
Assertions.assertNotNull(savingsProductID);
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
Assertions.assertNotNull(savingsProductID);
HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
ACCOUNT_TYPE_INDIVIDUAL);
Assertions.assertTrue(modifications.containsKey("submittedOnDate"));
HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
this.savingsAccountHelper.calculateInterestForSavings(savingsId);
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
assertEquals(summaryBefore, summary);
this.savingsAccountHelper.postInterestForSavings(savingsId);
HashMap summaryAfterPosting = this.savingsAccountHelper.getSavingsSummary(savingsId);
Assertions.assertNotNull(summaryAfterPosting.get("interestPostedTillDate"));
}
@SuppressWarnings("unchecked")
@Test
public void testSavingsBackedDatedTransactionsNotAllowed() {
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
// Assertions.assertNotNull(clientID);
final String minBalanceForInterestCalculation = null;
final String minRequiredBalance = null;
final String enforceMinRequiredBalance = "false";
final boolean allowOverdraft = false;
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft);
Assertions.assertNotNull(savingsProductID);
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
Assertions.assertNotNull(savingsProductID);
HashMap modifications = this.savingsAccountHelper.updateSavingsAccount(clientID, savingsProductID, savingsId,
ACCOUNT_TYPE_INDIVIDUAL);
Assertions.assertTrue(modifications.containsKey("submittedOnDate"));
HashMap savingsStatusHashMap = SavingsStatusChecker.getStatusOfSavings(this.requestSpec, this.responseSpec, savingsId);
SavingsStatusChecker.verifySavingsIsPending(savingsStatusHashMap);
savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
final HashMap summaryBefore = this.savingsAccountHelper.getSavingsSummary(savingsId);
this.savingsAccountHelper.calculateInterestForSavings(savingsId);
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
assertEquals(summaryBefore, summary);
this.savingsAccountHelper.postInterestForSavings(savingsId);
HashMap summaryAfterPosting = this.savingsAccountHelper.getSavingsSummary(savingsId);
Assertions.assertNotNull(summaryAfterPosting.get("interestPostedTillDate"));
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "38", false);
final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build();
final SavingsAccountHelper validationErrorHelper = new SavingsAccountHelper(this.requestSpec, errorResponse);
SimpleDateFormat sdf = new SimpleDateFormat(CommonConstants.DATE_FORMAT, Locale.US);
Calendar cal = Calendar.getInstance();
List dates = (List) summaryAfterPosting.get("interestPostedTillDate");
cal.set(Calendar.YEAR, (Integer) dates.get(0));
cal.set(Calendar.MONTH, (Integer) dates.get(1) - 1);
cal.set(Calendar.DAY_OF_MONTH, (Integer) dates.get(2) - 1);
final String depositDate = sdf.format(cal.getTime());
LOG.info("-------------------- BackDated Transaction -----------------------------");
List<HashMap> error = (List<HashMap>) validationErrorHelper.depositToSavingsAccount(savingsId, "3000", depositDate,
CommonConstants.RESPONSE_ERROR);
GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "38", true);
// LOG.info(savingsAccountErrorData.get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE).toString());
assertEquals("error.msg.savings.transaction.is.not.allowed", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
}
@Test @Test
public void testSavingsAccountWithMinBalanceForInterestCalculation() { public void testSavingsAccountWithMinBalanceForInterestCalculation() {
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
@@ -166,6 +276,10 @@ public class ClientSavingsIntegrationTest {
this.savingsAccountHelper.postInterestForSavings(savingsId); this.savingsAccountHelper.postInterestForSavings(savingsId);
summary = this.savingsAccountHelper.getSavingsSummary(savingsId); summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
// These two fields are getting updated after an interest posting
summary.remove("interestPostedTillDate");
assertEquals(summaryBefore, summary); assertEquals(summaryBefore, summary);
final Object savingsInterest = this.savingsAccountHelper.getSavingsInterest(savingsId); final Object savingsInterest = this.savingsAccountHelper.getSavingsInterest(savingsId);
@@ -76,6 +76,7 @@ public class GlobalConfigurationHelper {
// the future, it needs to be reset here. // the future, it needs to be reset here.
final Integer configDefaultId = (Integer) configDefault.get("id"); final Integer configDefaultId = (Integer) configDefault.get("id");
final Integer configDefaultValue = (Integer) configDefault.get("value"); final Integer configDefaultValue = (Integer) configDefault.get("value");
updateValueForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(), configDefaultValue.toString()); updateValueForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(), configDefaultValue.toString());
updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(), updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(),
(Boolean) configDefault.get("enabled")); (Boolean) configDefault.get("enabled"));
@@ -88,9 +89,9 @@ public class GlobalConfigurationHelper {
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations(); ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec); ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec);
// There are currently 32 global configurations. // There are currently 35 global configurations.
Assertions.assertEquals(33, expectedGlobalConfigurations.size()); Assertions.assertEquals(35, expectedGlobalConfigurations.size());
Assertions.assertEquals(33, actualGlobalConfigurations.size()); Assertions.assertEquals(35, actualGlobalConfigurations.size());
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) { for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
@@ -389,6 +390,23 @@ public class GlobalConfigurationHelper {
isNextDayFixedDepositInterestTransferEnabledForPeriodEnd.put("trapDoor", false); isNextDayFixedDepositInterestTransferEnabledForPeriodEnd.put("trapDoor", false);
defaults.add(isNextDayFixedDepositInterestTransferEnabledForPeriodEnd); defaults.add(isNextDayFixedDepositInterestTransferEnabledForPeriodEnd);
HashMap<String, Object> isAllowedBackDatedTransactionsBeforeInterestPostingDate = new HashMap<>();
isAllowedBackDatedTransactionsBeforeInterestPostingDate.put("id", 38);
isAllowedBackDatedTransactionsBeforeInterestPostingDate.put("name", "allow-backdated-transaction-before-interest-posting");
isAllowedBackDatedTransactionsBeforeInterestPostingDate.put("value", 0);
isAllowedBackDatedTransactionsBeforeInterestPostingDate.put("enabled", true);
isAllowedBackDatedTransactionsBeforeInterestPostingDate.put("trapDoor", false);
defaults.add(isAllowedBackDatedTransactionsBeforeInterestPostingDate);
HashMap<String, Object> isAllowedBackDatedTransactionsBeforeInterestPostingDateForDays = new HashMap<>();
isAllowedBackDatedTransactionsBeforeInterestPostingDateForDays.put("id", 39);
isAllowedBackDatedTransactionsBeforeInterestPostingDateForDays.put("name",
"allow-backdated-transaction-before-interest-posting-date-for-days");
isAllowedBackDatedTransactionsBeforeInterestPostingDateForDays.put("value", 0);
isAllowedBackDatedTransactionsBeforeInterestPostingDateForDays.put("enabled", false);
isAllowedBackDatedTransactionsBeforeInterestPostingDateForDays.put("trapDoor", false);
defaults.add(isAllowedBackDatedTransactionsBeforeInterestPostingDateForDays);
return defaults; return defaults;
} }
@@ -147,8 +147,8 @@ public class SchedulerJobHelper {
* @author Michael Vorburger.ch * @author Michael Vorburger.ch
*/ */
public void executeAndAwaitJob(String jobName) { public void executeAndAwaitJob(String jobName) {
final Duration timeout = Duration.ofMinutes(3); final Duration timeout = Duration.ofMinutes(4);
final Duration pause = Duration.ofSeconds(1); final Duration pause = Duration.ofSeconds(2);
DateTimeFormatter df = DateTimeFormatter.ISO_INSTANT; // FINERACT-926 DateTimeFormatter df = DateTimeFormatter.ISO_INSTANT; // FINERACT-926
Instant beforeExecuteTime = now().truncatedTo(ChronoUnit.SECONDS); Instant beforeExecuteTime = now().truncatedTo(ChronoUnit.SECONDS);