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();
boolean isNextDayFixedDepositInterestTransferEnabledForPeriodEnd();
boolean retrievePivotDateConfig();
boolean isRelaxingDaysConfigForPivotDateEnabled();
Long retrieveRelaxingDaysConfigForPivotDate();
}
@@ -343,6 +343,31 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
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) {
String identifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier();
String key = identifier + "_" + propertyName;
@@ -427,6 +427,7 @@ public class InteropServiceImpl implements InteropService {
LocalDate transactionDate = transactionDateTime.toLocalDate();
DateTimeFormatter fmt = getDateTimeFormatter(command);
SavingsAccountTransaction transaction;
final boolean backdatedTxnsAllowedTill = false;
if (isDebit) {
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);
transaction = savingsAccountService.handleWithdrawal(savingsAccount, fmt, transactionDate, request.getAmount().getAmount(),
instance(findPaymentType(), savingsAccount.getExternalId(), null, getRoutingCode(), transferCode, null),
transactionValues);
transactionValues, backdatedTxnsAllowedTill);
} else {
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();
@@ -64,10 +64,11 @@ public class AccountTransferDetailAssembler {
public AccountTransferDetails assembleSavingsToSavingsTransfer(final JsonCommand command) {
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 SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId);
final SavingsAccount toSavingsAccount = this.savingsAccountAssembler.assembleFrom(toSavingsId, backdatedTxnsAllowedTill);
return assembleSavingsToSavingsTransfer(command, fromSavingsAccount, toSavingsAccount);
@@ -76,7 +77,8 @@ public class AccountTransferDetailAssembler {
public AccountTransferDetails assembleSavingsToLoanTransfer(final JsonCommand command) {
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 Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
@@ -89,9 +91,9 @@ public class AccountTransferDetailAssembler {
final Long fromLoanAccountId = command.longValueOfParameterNamed(fromAccountIdParamName);
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
final boolean backdatedTxnsAllowedTill = false;
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);
}
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.account.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Date;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.portfolio.account.PortfolioAccountType;
@@ -41,6 +42,8 @@ public interface AccountTransfersReadPlatformService {
Collection<Long> fetchPostInterestTransactionIds(Long accountId);
Collection<Long> fetchPostInterestTransactionIdsWithPivotDate(Long accountId, Date pivotDate);
AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType,
Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType);
@@ -26,6 +26,7 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
@@ -265,6 +266,16 @@ public class AccountTransfersReadPlatformServiceImpl implements AccountTransfers
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 final String schemaSql;
@@ -131,22 +131,24 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
boolean isAccountTransfer = true;
Long fromLoanAccountId = null;
boolean isWithdrawBalance = false;
final boolean backdatedTxnsAllowedTill = false;
if (isSavingsToSavingsAccountTransfer(fromAccountType, toAccountType)) {
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,
isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance);
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
transactionDate, transactionAmount, paymentDetail, transactionBooleanValues);
transactionDate, transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill);
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,
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction);
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
if (!fromSavingsAccount.getCurrency().getCode().equals(toSavingsAccount.getCurrency().getCode())) {
throw new DifferentCurrenciesException(fromSavingsAccount.getCurrency().getCode(),
@@ -161,12 +163,13 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
} else if (isSavingsToLoanAccountTransfer(fromAccountType, toAccountType)) {
//
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,
isRegularTransaction, fromSavingsAccount.isWithdrawalFeeApplicableForTransfer(), isInterestTransfer, isWithdrawBalance);
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount, fmt,
transactionDate, transactionAmount, paymentDetail, transactionBooleanValues);
transactionDate, transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill);
final Long toLoanAccountId = command.longValueOfParameterNamed(toAccountIdParamName);
final Loan toLoanAccount = this.loanAccountAssembler.assembleFrom(toLoanAccountId);
@@ -194,10 +197,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
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,
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction);
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command,
fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction);
@@ -288,6 +291,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
Long transferTransactionId = null;
final boolean isAccountTransfer = true;
final boolean isRegularTransaction = accountTransferDTO.isRegularTransaction();
final boolean backdatedTxnsAllowedTill = false;
AccountTransferDetails accountTransferDetails = accountTransferDTO.getAccountTransferDetails();
if (isSavingsToLoanAccountTransfer(accountTransferDTO.getFromAccountType(), accountTransferDTO.getToAccountType())) {
//
@@ -295,7 +299,8 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
Loan toLoanAccount = null;
if (accountTransferDetails == null) {
if (accountTransferDTO.getFromSavingsAccount() == null) {
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId(),
backdatedTxnsAllowedTill);
} else {
fromSavingsAccount = accountTransferDTO.getFromSavingsAccount();
this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
@@ -321,7 +326,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), transactionBooleanValues);
accountTransferDTO.getPaymentDetail(), transactionBooleanValues, backdatedTxnsAllowedTill);
LoanTransaction loanTransaction = null;
@@ -351,13 +356,14 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
SavingsAccount toSavingsAccount = null;
if (accountTransferDetails == null) {
if (accountTransferDTO.getFromSavingsAccount() == null) {
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId());
fromSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getFromAccountId(),
backdatedTxnsAllowedTill);
} else {
fromSavingsAccount = accountTransferDTO.getFromSavingsAccount();
this.savingsAccountAssembler.setHelpers(fromSavingsAccount);
}
if (accountTransferDTO.getToSavingsAccount() == null) {
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId(), false);
} else {
toSavingsAccount = accountTransferDTO.getToSavingsAccount();
this.savingsAccountAssembler.setHelpers(toSavingsAccount);
@@ -383,11 +389,11 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(fromSavingsAccount,
accountTransferDTO.getFmt(), transactionDate, accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), transactionBooleanValues);
accountTransferDTO.getPaymentDetail(), transactionBooleanValues, backdatedTxnsAllowedTill);
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
accountTransferDTO.getFmt(), transactionDate, accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction);
accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
accountTransferDetails = this.accountTransferAssembler.assembleSavingsToSavingsTransfer(accountTransferDTO, fromSavingsAccount,
toSavingsAccount, withdrawal, deposit);
@@ -405,7 +411,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
fromLoanAccount = accountTransferDTO.getLoan();
this.loanAccountAssembler.setHelpers(fromLoanAccount);
}
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId());
toSavingsAccount = this.savingsAccountAssembler.assembleFrom(accountTransferDTO.getToAccountId(), backdatedTxnsAllowedTill);
} else {
fromLoanAccount = accountTransferDetails.fromLoanAccount();
this.loanAccountAssembler.setHelpers(fromLoanAccount);
@@ -426,7 +432,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(toSavingsAccount,
accountTransferDTO.getFmt(), accountTransferDTO.getTransactionDate(), accountTransferDTO.getTransactionAmount(),
accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction);
accountTransferDTO.getPaymentDetail(), isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(accountTransferDTO, fromLoanAccount,
toSavingsAccount, deposit, loanTransaction);
this.accountTransferDetailRepository.saveAndFlush(accountTransferDetails);
@@ -523,6 +529,7 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
final Loan fromLoanAccount = this.loanAccountAssembler.assembleFrom(fromLoanAccountId);
BigDecimal overpaid = this.loanReadPlatformService.retrieveTotalPaidInAdvance(fromLoanAccountId).getPaidInAdvance();
final boolean backdatedTxnsAllowedTill = false;
if (overpaid == null || overpaid.compareTo(BigDecimal.ZERO) == 0 ? Boolean.TRUE
: Boolean.FALSE || transactionAmount.floatValue() > overpaid.floatValue()) {
@@ -536,10 +543,10 @@ public class AccountTransfersWritePlatformServiceImpl implements AccountTransfer
new CommandProcessingResultBuilder(), transactionDate, transactionAmount, paymentDetail, null, null);
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,
transactionAmount, paymentDetail, true, true);
transactionAmount, paymentDetail, true, true, backdatedTxnsAllowedTill);
final AccountTransferDetails accountTransferDetails = this.accountTransferAssembler.assembleLoanToSavingsTransfer(command,
fromLoanAccount, toSavingsAccount, deposit, loanRefundTransaction);
@@ -111,12 +111,14 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
guarantorCommand.validateForCreate();
validateLoanStatus(loan);
final List<GuarantorFundingDetails> guarantorFundingDetails = new ArrayList<>();
final boolean backdatedTxnsAllowedTill = false;
AccountAssociations accountAssociations = 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);
accountAssociations = AccountAssociations.associateSavingsAccount(loan, savingsAccount,
AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue(), true);
AccountAssociationType.GUARANTOR_ACCOUNT_ASSOCIATION.getValue(), backdatedTxnsAllowedTill);
GuarantorFundingDetails fundingDetails = new GuarantorFundingDetails(accountAssociations,
GuarantorFundStatusType.ACTIVE.getValue(), guarantorCommand.getAmount());
@@ -533,6 +533,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
// Save linked account information
SavingsAccount savingsAccount;
final boolean backdatedTxnsAllowedTill = false;
AccountAssociations accountAssociations;
final Long savingsAccountId = command.longValueOfParameterNamed("linkAccountId");
if (savingsAccountId != null) {
@@ -551,8 +552,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
}
if (gsimClientMembers.contains(BigDecimal.valueOf(newLoanApplication.getClientId()))) {
savingsAccount = this.savingsAccountAssembler
.assembleFrom(clientAccountMappings.get(BigDecimal.valueOf(newLoanApplication.getClientId())).longValue());
savingsAccount = this.savingsAccountAssembler.assembleFrom(
clientAccountMappings.get(BigDecimal.valueOf(newLoanApplication.getClientId())).longValue(),
backdatedTxnsAllowedTill);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication);
boolean isActive = true;
@@ -565,7 +567,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
}
} else {
savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId, backdatedTxnsAllowedTill);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, newLoanApplication);
boolean isActive = true;
accountAssociations = AccountAssociations.associateSavingsAccount(newLoanApplication, savingsAccount,
@@ -1156,6 +1158,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
// Save linked account information
final String linkAccountIdParamName = "linkAccountId";
final boolean backdatedTxnsAllowedTill = false;
final Long savingsAccountId = command.longValueOfParameterNamed(linkAccountIdParamName);
AccountAssociations accountAssociations = this.accountAssociationsRepository.findByLoanIdAndType(loanId,
AccountAssociationType.LINKED_ACCOUNT_ASSOCIATION.getValue());
@@ -1181,7 +1184,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
}
}
if (isModified) {
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId);
final SavingsAccount savingsAccount = this.savingsAccountAssembler.assembleFrom(savingsAccountId,
backdatedTxnsAllowedTill);
this.fromApiJsonDeserializer.validatelinkedSavingsAccount(savingsAccount, existingLoanApplication);
if (accountAssociations == null) {
boolean isActive = true;
@@ -86,6 +86,7 @@ public class SavingsApiConstants {
public static final String transactionLowerLimitParamName = "transactionLowerLimit";
public static final String retailEntriesParamName = "retailEntries";
public static final String childAccountIdParamName = "childAccountId";
public static final String interestPostedTillDate = "interestPostedTillDate";
public static final String groupIdParamName = "groupId";
public static final String productIdParamName = "productId";
@@ -57,7 +57,7 @@ public class SavingsApiSetConstants extends SavingsApiConstants {
"interestCompoundingPeriodTypeOptions", "interestPostingPeriodTypeOptions", "interestCalculationTypeOptions",
"interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee",
"annualFee", onHoldFundsParamName, nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
datatables, savingsAmountOnHold, accountMappingForPaymentParamName));
datatables, savingsAmountOnHold, accountMappingForPaymentParamName, interestPostedTillDate));
protected static final Set<String> SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>(
Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, paymentDetailDataParamName,
@@ -44,12 +44,14 @@ public class SavingsAccountSummaryData implements Serializable {
private final BigDecimal interestNotPosted;
private final LocalDate lastInterestCalculationDate;
private final BigDecimal availableBalance;
private final LocalDate interestPostedTillDate;
public SavingsAccountSummaryData(final CurrencyData currency, final BigDecimal totalDeposits, final BigDecimal totalWithdrawals,
final BigDecimal totalWithdrawalFees, final BigDecimal totalAnnualFees, final BigDecimal totalInterestEarned,
final BigDecimal totalInterestPosted, final BigDecimal accountBalance, final BigDecimal totalFeeCharge,
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.totalDeposits = totalDeposits;
this.totalWithdrawals = totalWithdrawals;
@@ -65,5 +67,6 @@ public class SavingsAccountSummaryData implements Serializable {
this.interestNotPosted = interestNotPosted;
this.lastInterestCalculationDate = lastInterestCalculationDate;
this.availableBalance = availableBalance;
this.interestPostedTillDate = interestPostedTillDate;
}
}
@@ -44,6 +44,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
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.data.ApiParameterError;
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.domain.SavingsAccount;
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.springframework.beans.factory.annotation.Autowired;
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<>(
Arrays.asList(transactionDateParamName, SavingsApiConstants.dateFormatParamName, SavingsApiConstants.localeParamName,
transactionAmountParamName));
private final ConfigurationDomainService configurationDomainService;
@Autowired
public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper) {
public SavingsAccountTransactionDataValidator(final FromJsonHelper fromApiJsonHelper,
final ConfigurationDomainService configurationDomainService) {
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) {
@@ -181,8 +204,8 @@ public class SavingsAccountTransactionDataValidator {
}
public SavingsAccountTransaction validateHoldAndAssembleForm(final String json, final SavingsAccount account,
final AppUser createdUser) {
public SavingsAccountTransaction validateHoldAndAssembleForm(final String json, final SavingsAccount account, final AppUser createdUser,
final boolean backdatedTxnsAllowedTill) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
@@ -210,7 +233,15 @@ public class SavingsAccountTransactionDataValidator {
if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) < 0) {
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
if (lastTransactionDate != null && transactionDate.isBefore(lastTransactionDate)) {
baseDataValidator.parameter(SavingsApiConstants.dateParamName).value(lastTransactionDate).failWithCode(
@@ -115,11 +115,11 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
boolean isAccountTransfer = false;
boolean isInterestTransfer = false;
boolean isWithdrawBalance = false;
final boolean backdatedTxnsAllowedTill = false;
SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
isRegularTransaction, applyWithdrawFee, isInterestTransfer, isWithdrawBalance);
return this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate, transactionAmount, paymentDetail,
transactionBooleanValues);
transactionBooleanValues, backdatedTxnsAllowedTill);
}
@Transactional
@@ -128,8 +128,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) {
boolean isAccountTransfer = false;
boolean isRegularTransaction = false;
final boolean backdatedTxnsAllowedTill = false;
return this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail,
isAccountTransfer, isRegularTransaction);
isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
}
@Transactional
@@ -146,8 +147,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final boolean isPreMatureClosure = false;
final MathContext mc = MathContext.DECIMAL64;
account.updateDepositAmount(transactionAmount);
final boolean backdatedTxnsAllowedTill = false;
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> existingReversedTransactionIds = new HashSet<>();
final boolean isAnyActivationChargesDue = isAnyActivationChargesDue(account);
@@ -172,8 +174,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
final boolean isRegularTransaction) {
boolean isAccountTransfer = false;
final boolean backdatedTxnsAllowedTill = false;
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> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -575,10 +578,11 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
final MonetaryCurrency currency = savingsAccount.getCurrency();
final boolean backdatedTxnsAllowedTill = false;
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
}
@@ -527,8 +527,9 @@ public class FixedDepositAccount extends SavingsAccount {
final MathContext mc = MathContext.DECIMAL64;
final boolean isInterestTransfer = false;
final LocalDate postInterestOnDate = null;
final boolean backdatedTxnsAllowedTill = false;
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate);
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
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) {
// update existing transactions so derived balance fields are
// correct.
recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate);
recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill);
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
@@ -585,6 +587,7 @@ public class FixedDepositAccount extends SavingsAccount {
// post remaining interest
final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate);
final boolean backdatedTxnsAllowedTill = false;
if (!remainigInterestToBePosted.isZero()) {
final boolean postInterestAsOn = false;
final SavingsAccountTransaction newPostingTransaction = SavingsAccountTransaction.interestPosting(this, office(),
@@ -593,12 +596,12 @@ public class FixedDepositAccount extends SavingsAccount {
recalucateDailyBalance = true;
}
recalucateDailyBalance = applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance);
recalucateDailyBalance = applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill);
if (recalucateDailyBalance) {
// update existing transactions so derived balance fields are
// correct.
recalculateDailyBalances(Money.zero(this.currency), accountCloseDate);
recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill);
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate);
@@ -639,19 +642,19 @@ public class FixedDepositAccount extends SavingsAccount {
@Override
public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postInterestOnDate) {
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) {
final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
}
@Override
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postAsInterestOn) {
final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill) {
final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postAsInterestOn);
financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill);
}
private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) {
@@ -539,14 +539,15 @@ public class RecurringDepositAccount extends SavingsAccount {
protected void processAccountUponActivation(final DateTimeFormatter fmt, final AppUser user) {
final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance);
final boolean backdatedTxnsAllowedTill = false;
if (minRequiredOpeningBalance.isGreaterThanZero()) {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(),
minRequiredOpeningBalance.getAmount(), null, new Date(), user, accountType);
deposit(transactionDTO);
deposit(transactionDTO, backdatedTxnsAllowedTill);
// update existing transactions so derived balance fields are
// 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;
boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null;
final boolean backdatedTxnsAllowedTill = false;
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate);
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
Money interestPostedToDate = Money.zero(this.currency);
@@ -673,11 +675,11 @@ public class RecurringDepositAccount extends SavingsAccount {
}
}
}
applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails);
applyWithholdTaxForDepositAccounts(interestPostingUpToDate, recalucateDailyBalanceDetails, backdatedTxnsAllowedTill);
if (recalucateDailyBalanceDetails) {
// update existing transactions so derived balance fields are
// correct.
recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate);
recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill);
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
}
@@ -693,6 +695,7 @@ public class RecurringDepositAccount extends SavingsAccount {
financialYearBeginningMonth);
boolean recalucateDailyBalance = false;
final boolean backdatedTxnsAllowedTill = false;
// post remaining interest
final Money remainigInterestToBePosted = interestOnMaturity.minus(interestPostedToDate);
@@ -704,12 +707,12 @@ public class RecurringDepositAccount extends SavingsAccount {
recalucateDailyBalance = true;
}
applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance);
applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill);
if (recalucateDailyBalance) {
// update existing transactions so derived balance fields are
// correct.
recalculateDailyBalances(Money.zero(this.currency), accountCloseDate);
recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill);
}
this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions);
@@ -750,19 +753,19 @@ public class RecurringDepositAccount extends SavingsAccount {
@Override
public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postInterestAson) {
final LocalDate postInterestAson, final boolean backdatedTxnsAllowedTill) {
final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestAson);
financialYearBeginningMonth, postInterestAson, backdatedTxnsAllowedTill);
}
@Override
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postAsInterestOn) {
final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill) {
final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate);
return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postAsInterestOn);
financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill);
}
private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) {
@@ -834,7 +837,7 @@ public class RecurringDepositAccount extends SavingsAccount {
}
@Override
public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO) {
public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill) {
if (isAccountMatured()) {
final String defaultUserMessage = "Transaction is not allowed. Account is matured.";
@@ -872,7 +875,7 @@ public class RecurringDepositAccount extends SavingsAccount {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
final SavingsAccountTransaction transaction = super.deposit(transactionDTO);
final SavingsAccountTransaction transaction = super.deposit(transactionDTO, backdatedTxnsAllowedTill);
return transaction;
}
@@ -316,6 +316,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
protected SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper;
@Transient
protected SavingsHelper savingsHelper;
@Transient
protected List<SavingsAccountTransaction> savingsAccountTransactions = new ArrayList<>();
@Column(name = "deposit_type_enum", insertable = false, updatable = false)
private Integer depositType;
@@ -445,6 +447,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
this.savingsHelper = savingsHelper;
}
public void setSavingsAccountTransactions(final List<SavingsAccountTransaction> savingsAccountTransactions) {
this.savingsAccountTransactions.addAll(savingsAccountTransactions);
}
public List<SavingsAccountTransaction> getSavingsAccountTransactionsWithPivotConfig() {
return this.savingsAccountTransactions;
}
public String getExternalId() {
return externalId;
}
@@ -487,15 +497,25 @@ public class SavingsAccount extends AbstractPersistableCustom {
public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final boolean isInterestTransfer,
final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth,
final LocalDate postInterestOnDate) {
final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) {
final List<PostingPeriod> postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer,
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate);
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
Money interestPostedToDate = Money.zero(this.currency);
if (backdatedTxnsAllowedTill) {
interestPostedToDate = Money.of(this.currency, this.summary.getTotalInterestPosted());
}
boolean recalucateDailyBalanceDetails = false;
boolean applyWithHoldTax = isWithHoldTaxApplicableForInterestPosting();
final List<SavingsAccountTransaction> withholdTransactions = new ArrayList<>();
withholdTransactions.addAll(findWithHoldTransactions());
if (backdatedTxnsAllowedTill) {
withholdTransactions.addAll(findWithHoldSavingsTransactionsWithPivotConfig());
} else {
withholdTransactions.addAll(findWithHoldTransactions());
}
for (final PostingPeriod interestPostingPeriod : postingPeriods) {
@@ -516,9 +536,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
newPostingTransaction = SavingsAccountTransaction.overdraftInterest(this, office(), interestPostingTransactionDate,
interestEarnedToBePostedForPeriod.negated(), interestPostingPeriod.isUserPosting());
}
addTransaction(newPostingTransaction);
if (backdatedTxnsAllowedTill) {
addTransactionToExisting(newPostingTransaction);
} else {
addTransaction(newPostingTransaction);
}
if (applyWithHoldTax) {
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate);
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate,
backdatedTxnsAllowedTill);
}
recalucateDailyBalanceDetails = true;
} else {
@@ -547,9 +572,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
interestPostingTransactionDate, interestEarnedToBePostedForPeriod.negated(),
interestPostingPeriod.isUserPosting());
}
addTransaction(newPostingTransaction);
if (backdatedTxnsAllowedTill) {
addTransactionToExisting(newPostingTransaction);
} else {
addTransaction(newPostingTransaction);
}
if (applyWithHoldTaxForOldTransaction) {
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate);
createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate,
backdatedTxnsAllowedTill);
}
recalucateDailyBalanceDetails = true;
}
@@ -561,14 +591,27 @@ public class SavingsAccount extends AbstractPersistableCustom {
// no openingBalance concept supported yet but probably will to
// allow
// 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
// 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() {
@@ -582,6 +625,17 @@ public class SavingsAccount extends AbstractPersistableCustom {
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() {
return this.withHoldTax() && this.depositAccountType().isSavingsDeposit();
}
@@ -599,6 +653,19 @@ public class SavingsAccount extends AbstractPersistableCustom {
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,
final List<SavingsAccountTransaction> transactions) {
SavingsAccountTransaction transaction = null;
@@ -611,7 +678,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
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;
if (this.taxGroup != null && amount.compareTo(BigDecimal.ZERO) > 0) {
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) {
SavingsAccountTransaction withholdTransaction = SavingsAccountTransaction.withHoldTax(this, office(), date,
Money.of(currency, totalTax), taxSplit);
addTransaction(withholdTransaction);
if (backdatedTxnsAllowedTill) {
addTransactionToExisting(withholdTransaction);
} else {
addTransaction(withholdTransaction);
}
isTaxAdded = true;
}
}
@@ -665,6 +736,18 @@ public class SavingsAccount extends AbstractPersistableCustom {
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() {
List<LocalDate> transactions = new ArrayList<>();
for (SavingsAccountTransaction trans : this.transactions) {
@@ -675,6 +758,16 @@ public class SavingsAccount extends AbstractPersistableCustom {
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.
*
@@ -692,15 +785,22 @@ public class SavingsAccount extends AbstractPersistableCustom {
public List<PostingPeriod> calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate,
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
// 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
// correct.
recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate);
recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate, backdatedTxnsAllowedTill);
// 1. default to calculate interest based on entire history OR
// 2. determine latest 'posting period' and find interest credited to
@@ -779,7 +879,13 @@ public class SavingsAccount extends AbstractPersistableCustom {
isTransferInterestToOtherAccount());
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;
}
@@ -808,6 +914,29 @@ public class SavingsAccount extends AbstractPersistableCustom {
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() {
final List<SavingsAccountTransaction> listOfTransactionsSorted = new ArrayList<>();
listOfTransactionsSorted.addAll(this.transactions);
@@ -817,11 +946,19 @@ public class SavingsAccount extends AbstractPersistableCustom {
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();
List<SavingsAccountTransaction> accountTransactionsSorted = retreiveListOfTransactions();
List<SavingsAccountTransaction> accountTransactionsSorted = null;
if (backdatedTxnsAllowedTill) {
accountTransactionsSorted = retrieveSortedTransactions();
} else {
accountTransactionsSorted = retreiveListOfTransactions();
}
boolean isTransactionsModified = false;
for (final SavingsAccountTransaction transaction : accountTransactionsSorted) {
if (transaction.isReversed()) {
@@ -867,7 +1004,11 @@ public class SavingsAccount extends AbstractPersistableCustom {
accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount());
}
accountTransaction.updateRunningBalance(runningBalance);
addTransaction(accountTransaction);
if (backdatedTxnsAllowedTill) {
addTransactionToExisting(accountTransaction);
} else {
addTransaction(accountTransaction);
}
isTransactionsModified = true;
}
@@ -875,7 +1016,11 @@ public class SavingsAccount extends AbstractPersistableCustom {
}
if (isTransactionsModified) {
accountTransactionsSorted = retreiveListOfTransactions();
if (backdatedTxnsAllowedTill) {
accountTransactionsSorted = retrieveSortedTransactions();
} else {
accountTransactionsSorted = retreiveListOfTransactions();
}
}
resetAccountTransactionsEndOfDayBalances(accountTransactionsSorted, interestPostingUpToDate);
}
@@ -896,16 +1041,17 @@ public class SavingsAccount extends AbstractPersistableCustom {
}
}
public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO) {
return deposit(transactionDTO, SavingsAccountTransactionType.DEPOSIT);
public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill) {
return deposit(transactionDTO, SavingsAccountTransactionType.DEPOSIT, backdatedTxnsAllowedTill);
}
public SavingsAccountTransaction dividendPayout(final SavingsAccountTransactionDTO transactionDTO) {
return deposit(transactionDTO, SavingsAccountTransactionType.DIVIDEND_PAYOUT);
public SavingsAccountTransaction dividendPayout(final SavingsAccountTransactionDTO transactionDTO,
final boolean backdatedTxnsAllowedTill) {
return deposit(transactionDTO, SavingsAccountTransactionType.DIVIDEND_PAYOUT, backdatedTxnsAllowedTill);
}
public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO,
final SavingsAccountTransactionType savingsAccountTransactionType) {
final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) {
final String resourceTypeName = depositAccountType().resourceName();
if (isNotActive()) {
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(),
transactionDTO.getTransactionDate(), amount, transactionDTO.getCreatedDate(), transactionDTO.getAppUser(),
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())
|| this.sub_status.equals(SavingsAccountSubStatusEnum.DORMANT.getValue())) {
@@ -986,7 +1136,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
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()) {
@@ -1042,14 +1193,19 @@ public class SavingsAccount extends AbstractPersistableCustom {
if (applyWithdrawFee) {
// auto pay withdrawal fee
payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser(),
transactionDTO.getPaymentDetail());
transactionDTO.getPaymentDetail(), backdatedTxnsAllowedTill);
}
final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount());
final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(),
transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney,
transactionDTO.getCreatedDate(), transactionDTO.getAppUser());
addTransaction(transaction);
if (backdatedTxnsAllowedTill) {
addTransactionToExisting(transaction);
} else {
addTransaction(transaction);
}
if (this.sub_status.equals(SavingsAccountSubStatusEnum.INACTIVE.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,
final PaymentDetail paymentDetail) {
final PaymentDetail paymentDetail, final boolean backdatedTxnsAllowedTill) {
for (SavingsAccountCharge charge : this.charges()) {
if (charge.isWithdrawalFee() && charge.isActive()) {
@@ -1089,7 +1244,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
} else if (charge.isEnablePaymentType()) { // normal charge-transaction to specific paymentType
if (paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName())) {
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
// irrespective of
@@ -1098,8 +1254,10 @@ public class SavingsAccount extends AbstractPersistableCustom {
} else { // normal-withdraw
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,
final AppUser user) {
boolean backdatedTxnsAllowedTill = false;
if (charge.getFreeWithdrawalCount() < charge.getFrequencyFreeWithdrawalCharge()) {
final Integer count = charge.getFreeWithdrawalCount() + 1;
charge.setFreeWithdrawalCount(count);
charge.updateNoWithdrawalFee();
} else {
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();
}
public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate) {
public boolean isBeforeLastPostingPeriod(final LocalDate transactionDate, final boolean backdatedTxnsAllowedTill) {
boolean transactionBeforeLastInterestPosting = false;
for (final SavingsAccountTransaction transaction : retreiveListOfTransactions()) {
if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
&& transaction.isAfter(transactionDate)) {
transactionBeforeLastInterestPosting = true;
break;
if (!backdatedTxnsAllowedTill) {
for (final SavingsAccountTransaction transaction : retreiveListOfTransactions()) {
if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())
&& transaction.isAfter(transactionDate)) {
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;
}
public void validateAccountBalanceDoesNotBecomeNegative(final BigDecimal transactionAmount, final boolean isException,
final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions) {
final List<SavingsAccountTransaction> transactionsSortedByDate = retreiveListOfTransactions();
final List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions, final boolean backdatedTxnsAllowedTill) {
List<SavingsAccountTransaction> transactionsSortedByDate = null;
if (backdatedTxnsAllowedTill) {
transactionsSortedByDate = retrieveSortedTransactions();
} else {
transactionsSortedByDate = retreiveListOfTransactions();
}
Money runningBalance = Money.zero(this.currency);
if (backdatedTxnsAllowedTill) {
runningBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
}
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
LocalDate lastSavingsDate = null;
final BigDecimal withdrawalFee = null;
@@ -1255,10 +1436,20 @@ public class SavingsAccount extends AbstractPersistableCustom {
}
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);
if (backdatedTxnsAllowedTill) {
runningBalance = Money.of(this.currency, this.summary.getRunningBalanceOnPivotDate());
}
Money minRequiredBalance = minRequiredBalanceDerived(getCurrency());
LocalDate lastSavingsDate = null;
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,
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer, final boolean backdatedTxnsAllowedTill) {
final Map<String, Object> accountingBridgeData = new LinkedHashMap<>();
accountingBridgeData.put("savingsId", getId());
@@ -1598,7 +1789,16 @@ public class SavingsAccount extends AbstractPersistableCustom {
accountingBridgeData.put("isAccountTransfer", isAccountTransfer);
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) {
if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) {
newSavingsTransactions.add(transaction.toMapData(currencyData));
@@ -1612,13 +1812,21 @@ public class SavingsAccount extends AbstractPersistableCustom {
}
public Collection<Long> findExistingTransactionIds() {
final Collection<Long> ids = new ArrayList<>();
List<SavingsAccountTransaction> trans = getTransactions();
for (final SavingsAccountTransaction transaction : trans) {
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;
}
@@ -1626,6 +1834,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
final Collection<Long> ids = new ArrayList<>();
List<SavingsAccountTransaction> trans = getTransactions();
// time consuming
for (final SavingsAccountTransaction transaction : trans) {
if (transaction.isReversed()) {
ids.add(transaction.getId());
@@ -1635,6 +1844,18 @@ public class SavingsAccount extends AbstractPersistableCustom {
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) {
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() {
Date nextDueDate = null;
@@ -2341,8 +2592,8 @@ public class SavingsAccount extends AbstractPersistableCustom {
charge.updateToNextDueDateFrom(getActivationLocalDate());
}
// auto pay the activation time charges
this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user);
// auto pay the activation time charges (No need of checking the pivot date config)
this.payActivationCharges(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user, false);
// 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,
final AppUser user) {
final AppUser user, final boolean backdatedTxnsAllowedTill) {
boolean isSavingsChargeApplied = false;
for (SavingsAccountCharge savingsAccountCharge : this.charges()) {
if (savingsAccountCharge.isSavingsActivation()) {
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;
boolean isInterestTransfer = false;
LocalDate postInterestAsOnDate = null;
if (this.isBeforeLastPostingPeriod(getActivationLocalDate())) {
if (this.isBeforeLastPostingPeriod(getActivationLocalDate(), backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestAsOnDate);
postInterestAsOnDate, backdatedTxnsAllowedTill);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
this.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestAsOnDate);
financialYearBeginningMonth, postInterestAsOnDate, backdatedTxnsAllowedTill);
}
}
}
@@ -2535,6 +2787,10 @@ public class SavingsAccount extends AbstractPersistableCustom {
this.transactions.add(transaction);
}
public void addTransactionToExisting(final SavingsAccountTransaction transaction) {
this.savingsAccountTransactions.add(transaction);
}
public void setStatus(final Integer status) {
this.status = status;
}
@@ -2588,7 +2844,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
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 DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
@@ -2639,7 +2895,7 @@ public class SavingsAccount extends AbstractPersistableCustom {
// waive charge
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,
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 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,
final LocalDate transactionDate, final AppUser user) {
final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill) {
savingsAccountCharge.pay(getCurrency(), amountPaid);
return handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user);
return handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user, backdatedTxnsAllowedTill);
}
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;
if (savingsAccountCharge.isWithdrawalFee()) {
@@ -2831,23 +3088,31 @@ public class SavingsAccount extends AbstractPersistableCustom {
chargeTransaction = SavingsAccountTransaction.charge(this, office(), transactionDate, transactionAmount, user);
}
handleChargeTransactions(savingsAccountCharge, chargeTransaction);
handleChargeTransactions(savingsAccountCharge, chargeTransaction, backdatedTxnsAllowedTill);
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(),
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
// amount is waived.
final SavingsAccountChargePaidBy chargePaidBy = SavingsAccountChargePaidBy.instance(transaction, savingsAccountCharge,
transaction.getAmount(this.getCurrency()).getAmount());
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) {
@@ -3069,13 +3334,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
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();
SavingsAccountTransaction withholdTransaction = findTransactionFor(interestPostingUpToDate, withholdTransactions);
final BigDecimal totalInterestPosted = this.savingsAccountTransactionSummaryWrapper.calculateTotalInterestPosted(this.currency,
this.transactions);
if (withholdTransaction == null && this.withHoldTax()) {
boolean isWithholdTaxAdded = createWithHoldTransaction(totalInterestPosted, interestPostingUpToDate);
boolean isWithholdTaxAdded = createWithHoldTransaction(totalInterestPosted, interestPostingUpToDate, backdatedTxnsAllowedTill);
recalucateDailyBalance = recalucateDailyBalance || isWithholdTaxAdded;
} else {
boolean isWithholdTaxAdded = updateWithHoldTransaction(totalInterestPosted, withholdTransaction);
@@ -3085,16 +3351,16 @@ public class SavingsAccount extends AbstractPersistableCustom {
return recalucateDailyBalance;
}
public void setSubStatusInactive(AppUser appUser) {
public void setSubStatusInactive(AppUser appUser, final boolean backdatedTxnsAllowedTill) {
this.sub_status = SavingsAccountSubStatusEnum.INACTIVE.getValue();
LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
for (SavingsAccountCharge charge : this.charges()) {
if (charge.isSavingsNoActivity() && charge.isActive()) {
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);
}
@@ -3113,10 +3379,14 @@ public class SavingsAccount extends AbstractPersistableCustom {
SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, appUser, postInterestAsOnDate);
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);
}
public void loadTransactions() {
transactions.size();
}
public void loadLazyCollections() {
transactions.size();
charges.size();
@@ -3355,6 +3625,19 @@ public class SavingsAccount extends AbstractPersistableCustom {
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() {
return this.savingsOnHoldAmount == null ? BigDecimal.ZERO : this.savingsOnHoldAmount;
}
@@ -47,11 +47,15 @@ import com.google.gson.JsonElement;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
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.exception.UnsupportedParameterException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
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.StaffRepositoryWrapper;
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.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
@@ -89,6 +94,8 @@ public class SavingsAccountAssembler {
private final SavingsAccountRepositoryWrapper savingsAccountRepository;
private final SavingsAccountChargeAssembler savingsAccountChargeAssembler;
private final FromJsonHelper fromApiJsonHelper;
private final JdbcTemplate jdbcTemplate;
private final ConfigurationDomainService configurationDomainService;
@Autowired
public SavingsAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
@@ -96,7 +103,8 @@ public class SavingsAccountAssembler {
final StaffRepositoryWrapper staffRepository, final SavingsProductRepository savingProductRepository,
final SavingsAccountRepositoryWrapper savingsAccountRepository,
final SavingsAccountChargeAssembler savingsAccountChargeAssembler, final FromJsonHelper fromApiJsonHelper,
final AccountTransfersReadPlatformService accountTransfersReadPlatformService) {
final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final RoutingDataSource dataSource,
final ConfigurationDomainService configurationDomainService) {
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
this.clientRepository = clientRepository;
this.groupRepository = groupRepository;
@@ -106,6 +114,8 @@ public class SavingsAccountAssembler {
this.savingsAccountChargeAssembler = savingsAccountChargeAssembler;
this.fromApiJsonHelper = fromApiJsonHelper;
savingsHelper = new SavingsHelper(accountTransfersReadPlatformService);
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.configurationDomainService = configurationDomainService;
}
/**
@@ -307,12 +317,58 @@ public class SavingsAccountAssembler {
return account;
}
public SavingsAccount assembleFrom(final Long savingsId) {
final SavingsAccount account = this.savingsAccountRepository.findOneWithNotFoundDetection(savingsId);
public SavingsAccount assembleFrom(final Long savingsId, final boolean backdatedTxnsAllowedTill) {
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);
return account;
}
public boolean getPivotConfigStatus() {
return this.configurationDomainService.retrievePivotDateConfig();
}
public boolean isRelaxingDaysConfigForPivotDateEnabled() {
return this.configurationDomainService.isRelaxingDaysConfigForPivotDateEnabled();
}
public void setHelpers(final SavingsAccount account) {
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
}
@@ -28,13 +28,16 @@ import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
public interface SavingsAccountDomainService {
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,
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);
SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount);
void postJournalEntries(SavingsAccount savingsAccount, Set<Long> existingTransactionIds, Set<Long> existingReversedTransactionIds,
boolean backdatedTxnsAllowedTill);
SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount,
boolean backdatedTxnsAllowedTill);
}
@@ -82,7 +82,7 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
@Override
public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
final SavingsTransactionBooleanValues transactionBooleanValues) {
final SavingsTransactionBooleanValues transactionBooleanValues, final boolean backdatedTxnsAllowedTill) {
AppUser user = getAppUserIfPresent();
account.validateForAccountBlock();
@@ -96,32 +96,46 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
final Set<Long> existingTransactionIds = new HashSet<>();
final LocalDate postInterestOnDate = null;
final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
if (backdatedTxnsAllowedTill) {
updateTransactionDetailsWithPivotConfig(account, existingTransactionIds, existingReversedTransactionIds);
} else {
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
}
Integer accountType = null;
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
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;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(),
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate);
isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
}
account.validateAccountBalanceDoesNotBecomeNegative(transactionAmount, transactionBooleanValues.isExceptionForBalanceCheck(),
depositAccountOnHoldTransactions);
depositAccountOnHoldTransactions, backdatedTxnsAllowedTill);
saveTransactionToGenerateTransactionId(withdrawal);
this.savingsAccountRepository.save(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer());
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, transactionBooleanValues.isAccountTransfer(),
backdatedTxnsAllowedTill);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_WITHDRAWAL,
constructEntityMap(BusinessEntity.SAVINGS_TRANSACTION, withdrawal));
return withdrawal;
@@ -139,19 +153,21 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
@Override
public SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
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;
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
savingsAccountTransactionType);
savingsAccountTransactionType, backdatedTxnsAllowedTill);
}
private SavingsAccountTransaction handleDeposit(final SavingsAccount account, final DateTimeFormatter fmt,
final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail,
final boolean isAccountTransfer, final boolean isRegularTransaction,
final SavingsAccountTransactionType savingsAccountTransactionType) {
final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) {
AppUser user = getAppUserIfPresent();
account.validateForAccountBlock();
account.validateForCreditBlock();
// Global configurations
final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService
.isSavingsInterestPostingAtCurrentPeriodEnd();
final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth();
@@ -162,27 +178,40 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
boolean isInterestTransfer = false;
final Set<Long> existingTransactionIds = 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;
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
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 MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, backdatedTxnsAllowedTill);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
}
saveTransactionToGenerateTransactionId(deposit);
if (backdatedTxnsAllowedTill) {
// Update transactions separately
saveUpdatedTransactionsOfSavingsAccount(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingsAccountRepository.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_DEPOSIT,
constructEntityMap(BusinessEntity.SAVINGS_TRANSACTION, deposit));
return deposit;
@@ -190,19 +219,14 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
@Override
public SavingsAccountTransaction handleDividendPayout(final SavingsAccount account, final LocalDate transactionDate,
final BigDecimal transactionAmount) {
final BigDecimal transactionAmount, final boolean backdatedTxnsAllowedTill) {
final DateTimeFormatter fmt = null;
final PaymentDetail paymentDetail = null;
final boolean isAccountTransfer = false;
final boolean isRegularTransaction = true;
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.DIVIDEND_PAYOUT;
return handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction,
savingsAccountTransactionType);
}
private Long saveTransactionToGenerateTransactionId(final SavingsAccountTransaction transaction) {
this.savingsAccountTransactionRepository.save(transaction);
return transaction.getId();
savingsAccountTransactionType, backdatedTxnsAllowedTill);
}
private void updateExistingTransactionsDetails(SavingsAccount account, Set<Long> existingTransactionIds,
@@ -211,24 +235,39 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi
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,
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
final Set<Long> existingReversedTransactionIds, boolean isAccountTransfer, final boolean backdatedTxnsAllowedTill) {
final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
}
@Transactional
@Override
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;
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
}
private Map<BusinessEntity, Object> constructEntityMap(final BusinessEntity entityEvent, Object entity) {
@@ -19,10 +19,12 @@
package org.apache.fineract.portfolio.savings.domain;
import java.util.List;
import javax.persistence.LockModeType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
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.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")
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")
List<SavingsAccount> findSavingAccountByGsimId(@Param("gsimId") Long gsimId);
@@ -18,13 +18,16 @@
*/
package org.apache.fineract.portfolio.savings.domain;
import java.util.Date;
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.exception.SavingsAccountNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.query.Param;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -43,10 +46,15 @@ import org.springframework.transaction.annotation.Transactional;
public class SavingsAccountRepositoryWrapper {
private final SavingsAccountRepository repository;
private final SavingsAccountTransactionRepository savingsAccountTransactionRepository;
private final JdbcTemplate jdbcTemplate;
@Autowired
public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository) {
public SavingsAccountRepositoryWrapper(final SavingsAccountRepository repository,
final SavingsAccountTransactionRepository savingsAccountTransactionRepository, final RoutingDataSource dataSource) {
this.repository = repository;
this.savingsAccountTransactionRepository = savingsAccountTransactionRepository;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Transactional(readOnly = true)
@@ -57,6 +65,21 @@ public class SavingsAccountRepositoryWrapper {
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)
public SavingsAccount findOneWithNotFoundDetection(final Long savingsId, final DepositAccountType depositAccountType) {
final SavingsAccount account = this.repository.findByIdAndDepositAccountType(savingsId, depositAccountType.getValue());
@@ -106,6 +129,17 @@ public class SavingsAccountRepositoryWrapper {
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
public SavingsAccount findNonClosedAccountByAccountNumber(@Param("accountNumber") String accountNumber) {
return this.repository.findNonClosedAccountByAccountNumber(accountNumber);
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@@ -31,6 +32,7 @@ import javax.persistence.Transient;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
import org.apache.fineract.portfolio.savings.domain.interest.PostingPeriod;
/**
@@ -83,6 +85,14 @@ public final class SavingsAccountSummary {
@Column(name = "last_interest_calculation_date")
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() {
//
}
@@ -102,11 +112,130 @@ public final class SavingsAccountSummary {
this.totalOverdraftInterestDerived = wrapper.calculateTotalOverdraftInterest(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)
.minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge)
.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) {
Money totalEarned = Money.zero(currency);
@@ -137,4 +266,24 @@ public final class SavingsAccountSummary {
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());
}
public Date getLastTransactionDate() {
return this.dateOf;
}
public LocalDate getEndOfBalanceLocalDate() {
return balanceEndDate == null ? null : LocalDate.ofInstant(balanceEndDate.toInstant(), DateUtils.getDateTimeZoneOfTenant());
}
@@ -18,8 +18,12 @@
*/
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.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
@@ -30,4 +34,12 @@ public interface SavingsAccountTransactionRepository
SavingsAccountTransaction findOneByIdAndSavingsAccountId(@Param("transactionId") Long transactionId,
@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();
}
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) {
Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) {
@@ -122,6 +133,17 @@ public final class SavingsAccountTransactionSummaryWrapper {
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) {
Money total = Money.zero(currency);
for (final SavingsAccountTransaction transaction : transactions) {
@@ -23,6 +23,7 @@ import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -176,4 +177,9 @@ public final class SavingsHelper {
public Collection<Long> fetchPostInterestTransactionIds(Long 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.total_deposits_derived as totalDeposits, ");
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_annual_fees_derived as totalAnnualFees, ");
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 totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax");
final BigDecimal totalOverdraftInterestDerived = null;
final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate");
final boolean withHoldTax = rs.getBoolean("withHoldTax");
final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId");
@@ -751,7 +753,8 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
final BigDecimal availableBalance = null;
final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
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,
fieldOfficerId, fieldOfficerName, status, timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType,
@@ -244,14 +244,14 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
if (activationChargeAmount.isGreaterThanZero()) {
payActivationCharge(account, user);
}
if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) {
if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate(), false)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
}
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -266,7 +266,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),
depositAccountOnHoldTransactions);
depositAccountOnHoldTransactions, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
}
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
@@ -295,7 +295,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
for (SavingsAccountCharge savingsAccountCharge : account.charges()) {
if (savingsAccountCharge.isSavingsActivation()) {
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);
final boolean isInterestTransfer = false;
final LocalDate postInterestOnDate = null;
if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate())) {
if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate(), false)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, false);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -384,7 +384,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),
depositAccountOnHoldTransactions);
depositAccountOnHoldTransactions, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
}
@@ -527,7 +527,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null;
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
this.savingAccountRepositoryWrapper.save(account);
@@ -571,7 +571,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null;
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
@@ -645,12 +645,12 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
LocalDate postInterestOnDate = null;
checkClientOrGroupActive(account);
if (savingsAccountTransaction.isPostInterestCalculationRequired()
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) {
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, false);
} else {
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -658,7 +658,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions);
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions,
false);
// account.activateAccountBasedOnBalance();
final boolean isPreMatureClosure = false;
account.updateMaturityDateAndAmount(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
@@ -745,22 +746,22 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
if (savingsAccountTransaction.isDeposit()) {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
paymentDetail, savingsAccountTransaction.createdDate(), user, accountType);
transaction = account.deposit(transactionDTO);
transaction = account.deposit(transactionDTO, false);
} else {
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
paymentDetail, savingsAccountTransaction.createdDate(), user, accountType);
transaction = account.withdraw(transactionDTO, true);
transaction = account.withdraw(transactionDTO, true, false);
}
final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction);
boolean isInterestTransfer = false;
final LocalDate postInterestOnDate = null;
if (account.isBeforeLastPostingPeriod(transactionDate)
|| account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
if (account.isBeforeLastPostingPeriod(transactionDate, false)
|| account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) {
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, false);
} else {
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -768,7 +769,8 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions);
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions,
false);
account.activateAccountBasedOnBalance();
if (savingsAccountTransaction.isDeposit()) {
@@ -982,7 +984,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final MathContext mc = MathContext.DECIMAL64;
boolean isInterestTransfer = false;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(newTransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
@@ -1016,7 +1018,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(withdrawtransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
@@ -1060,7 +1062,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(acceptTransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
@@ -1209,18 +1211,18 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.waiveCharge(savingsAccountChargeId, user);
account.waiveCharge(savingsAccountChargeId, user, false);
boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), false)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, false);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -1229,7 +1231,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction,
depositAccountOnHoldTransactions);
depositAccountOnHoldTransactions, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
@@ -1348,18 +1350,18 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user);
account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user, false);
boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
if (account.isBeforeLastPostingPeriod(transactionDate, false)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, false);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -1368,7 +1370,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
}
account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(),
depositAccountOnHoldTransactions);
depositAccountOnHoldTransactions, false);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
@@ -1436,7 +1438,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
boolean isAccountTransfer = false;
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, false);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
}
@@ -313,6 +313,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("sa.withhold_tax as withHoldTax, ");
sqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax, ");
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("tg.id as taxGroupId, tg.name as taxGroupName, ");
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,
"totalOverdraftInterestDerived");
final BigDecimal totalWithholdTax = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "totalWithholdTax");
final LocalDate interestPostedTillDate = JdbcSupport.getLocalDate(rs, "interestPostedTillDate");
final BigDecimal minBalanceForInterestCalculation = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs,
"minBalanceForInterestCalculation");
@@ -536,7 +538,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
final SavingsAccountSummaryData summary = new SavingsAccountSummaryData(currency, totalDeposits, totalWithdrawals,
totalWithdrawalFees, totalAnnualFees, totalInterestEarned, totalInterestPosted, accountBalance, totalFeeCharge,
totalPenaltyCharge, totalOverdraftInterestDerived, totalWithholdTax, interestNotPosted, lastInterestCalculationDate,
availableBalance);
availableBalance, interestPostedTillDate);
final boolean withHoldTax = rs.getBoolean("withHoldTax");
final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId");
@@ -85,7 +85,7 @@ public interface SavingsAccountWritePlatformService {
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);
@@ -239,11 +239,14 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountTransactionDataValidator.validateActivation(command);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account);
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final Map<String, Object> changes = account.activate(user, command, DateUtils.getLocalDateOfTenant());
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
@@ -253,11 +256,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
processPostActiveActions(account, fmt, existingTransactionIds, existingReversedTransactionIds);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
}
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
this.businessEventNotifierService.notifyBusinessEventWasExecuted(BusinessEvents.SAVINGS_ACTIVATE,
constructEntityMap(BusinessEntity.SAVING, account));
@@ -286,17 +288,20 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (amountForDeposit.isGreaterThanZero()) {
boolean isAccountTransfer = false;
this.savingsAccountDomainService.handleDeposit(account, fmt, account.getActivationLocalDate(), amountForDeposit.getAmount(),
null, isAccountTransfer, isRegularTransaction);
null, isAccountTransfer, isRegularTransaction, false);
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
}
account.processAccountUponActivation(isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, user);
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsAccountTransactionType.PAY_CHARGE.name(),
depositAccountOnHoldTransactions);
depositAccountOnHoldTransactions, false);
}
@Transactional
@@ -330,25 +335,30 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountTransactionDataValidator.validate(command);
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;
LOG.info("is gsim");
}
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
checkClientOrGroupActive(account);
final Locale locale = command.extractLocale();
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account);
final Map<String, Object> changes = new LinkedHashMap<>();
final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
boolean isAccountTransfer = false;
boolean isRegularTransaction = true;
final SavingsAccountTransaction deposit = this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate,
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction);
transactionAmount, paymentDetail, isAccountTransfer, isRegularTransaction, backdatedTxnsAllowedTill);
if (isGsim && (deposit.getId() != null)) {
@@ -394,9 +404,6 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.savingsAccountTransactionDataValidator.validate(command);
boolean isGsim = false;
if (this.savingAccountRepositoryWrapper.findOneWithNotFoundDetection(savingsId).getGsim() != null) {
isGsim = true;
}
final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
@@ -407,8 +414,17 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Map<String, Object> changes = new LinkedHashMap<>();
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);
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account);
final boolean isAccountTransfer = false;
final boolean isRegularTransaction = true;
final boolean isApplyWithdrawFee = true;
@@ -417,7 +433,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsTransactionBooleanValues transactionBooleanValues = new SavingsTransactionBooleanValues(isAccountTransfer,
isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance);
final SavingsAccountTransaction withdrawal = this.savingsAccountDomainService.handleWithdrawal(account, fmt, transactionDate,
transactionAmount, paymentDetail, transactionBooleanValues);
transactionAmount, paymentDetail, transactionBooleanValues, backdatedTxnsAllowedTill);
if (isGsim && (withdrawal.getId() != null)) {
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());
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() //
@@ -476,14 +492,21 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.isSavingsInterestPostingAtCurrentPeriodEnd();
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);
final LocalDate today = DateUtils.getLocalDateOfTenant();
final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode());
boolean isInterestTransfer = false;
final LocalDate postInterestOnDate = null;
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.save(account);
@@ -502,8 +525,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
Long savingsId = command.getSavingsId();
final boolean postInterestAs = command.booleanPrimitiveValueOfParameterNamed("isPostInterestAsOn");
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);
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transactionDate, account);
if (postInterestAs == true) {
if (transactionDate == null) {
@@ -513,7 +541,14 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (transactionDate.isBefore(account.accountSubmittedOrActivationDate())) {
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) {
if (Date.from(transactionDate.atStartOfDay(ZoneId.systemDefault()).toInstant()).before(savingTransaction.getDateOf())) {
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,
constructEntityMap(BusinessEntity.SAVING, account));
@@ -541,7 +576,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Transactional
@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
.isSavingsInterestPostingAtCurrentPeriodEnd();
@@ -550,7 +586,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|| (account.allowOverdraft() && account.getNominalAnnualInterestRateOverdraft().compareTo(BigDecimal.ZERO) > 0)) {
final Set<Long> existingTransactionIds = 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 MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode());
boolean isInterestTransfer = false;
@@ -558,19 +600,26 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
if (postInterestAs) {
postInterestOnDate = transactionDate;
}
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
// for generating transaction id's
List<SavingsAccountTransaction> transactions = account.getTransactions();
for (SavingsAccountTransaction accountTransaction : transactions) {
if (accountTransaction.getId() == null) {
this.savingsAccountTransactionRepository.save(accountTransaction);
postInterestOnDate, backdatedTxnsAllowedTill);
if (!backdatedTxnsAllowedTill) {
List<SavingsAccountTransaction> transactions = account.getTransactions();
for (SavingsAccountTransaction accountTransaction : transactions) {
if (accountTransaction.getId() == null) {
this.savingsAccountTransactionRepository.save(accountTransaction);
}
}
}
if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, backdatedTxnsAllowedTill);
}
}
@@ -582,7 +631,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
.isSavingsInterestPostingAtCurrentPeriodEnd();
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> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
@@ -593,6 +642,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
throw new SavingsAccountTransactionNotFoundException(savingsId, transactionId);
}
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(savingsAccountTransaction.getTransactionLocalDate(),
account);
if (!allowAccountTransferModification
&& this.accountTransfersReadPlatformService.isAccountTransfer(transactionId, PortfolioAccountType.SAVINGS)) {
throw new PlatformServiceUnavailableException("error.msg.saving.account.transfer.transaction.update.not.allowed",
@@ -626,22 +678,23 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
LocalDate postInterestOnDate = null;
checkClientOrGroupActive(account);
if (savingsAccountTransaction.isPostInterestCalculationRequired()
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
&& account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) {
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, false);
} else {
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions);
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.undoTransactionAction, depositAccountOnHoldTransactions,
false);
account.activateAccountBasedOnBalance();
this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
return new CommandProcessingResultBuilder() //
.withEntityId(savingsId) //
.withOfficeId(account.officeId()) //
@@ -681,7 +734,8 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final LocalDate today = DateUtils.getLocalDateOfTenant();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
if (account.isNotActive()) {
throwValidationForActiveStatus(SavingsApiConstants.adjustTransactionAction);
}
@@ -691,6 +745,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
}
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
final Locale locale = command.extractLocale();
@@ -716,29 +771,32 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount,
paymentDetail, savingsAccountTransaction.createdDate(), user, accountType);
if (savingsAccountTransaction.isDeposit()) {
transaction = account.deposit(transactionDTO);
transaction = account.deposit(transactionDTO, false);
} else {
transaction = account.withdraw(transactionDTO, true);
transaction = account.withdraw(transactionDTO, true, false);
}
final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction);
final LocalDate postInterestOnDate = null;
if (account.isBeforeLastPostingPeriod(transactionDate)
|| account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate())) {
if (account.isBeforeLastPostingPeriod(transactionDate, false)
|| account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) {
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, false);
} else {
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
.findBySavingsAccountAndReversedFalseOrderByCreatedDateAsc(account);
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions);
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.adjustTransactionAction, depositAccountOnHoldTransactions,
false);
account.activateAccountBasedOnBalance();
this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
return new CommandProcessingResultBuilder() //
.withEntityId(newtransactionId) //
.withOfficeId(account.officeId()) //
@@ -802,7 +860,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult close(final Long savingsId, final JsonCommand command) {
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);
final boolean isLinkedWithAnyActiveLoan = this.accountAssociationsReadPlatformService.isLinkedWithAnyActiveAccount(savingsId);
@@ -854,7 +912,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
isRegularTransaction, isApplyWithdrawFee, isInterestTransfer, isWithdrawBalance);
this.savingsAccountDomainService.handleWithdrawal(account, fmt, closedDate, transactionAmount, paymentDetail,
transactionBooleanValues);
transactionBooleanValues, false);
}
@@ -909,12 +967,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(newTransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds, false);
return newTransferTransaction;
}
@@ -940,12 +998,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
boolean isInterestTransfer = false;
LocalDate postInterestOnDate = null;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(withdrawtransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds, false);
return withdrawtransferTransaction;
}
@@ -982,12 +1040,12 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final MathContext mc = MathContext.DECIMAL64;
LocalDate postInterestOnDate = null;
savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, false);
this.savingsAccountTransactionRepository.save(acceptTransferTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(savingsAccount, existingTransactionIds, existingReversedTransactionIds, false);
return acceptTransferTransaction;
}
@@ -1004,7 +1062,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Long savingsAccountId = command.getSavingsId();
this.savingsAccountChargeDataValidator.validateAdd(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId, false);
checkClientOrGroupActive(savingsAccount);
final Locale locale = command.extractLocale();
@@ -1073,7 +1131,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
// SavingsAccount Charge entity
final Long savingsChargeId = command.entityId();
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId, false);
checkClientOrGroupActive(savingsAccount);
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository.findOneWithNotFoundDetection(savingsChargeId,
@@ -1132,25 +1190,35 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
// Get Savings account from savings charge
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> 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;
LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate())) {
if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, backdatedTxnsAllowedTill);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
depositAccountOnHoldTransactions = this.depositAccountOnHoldTransactionRepository
@@ -1158,11 +1226,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
}
account.validateAccountBalanceDoesNotBecomeNegative(SavingsApiConstants.waiveChargeTransactionAction,
depositAccountOnHoldTransactions);
depositAccountOnHoldTransactions, backdatedTxnsAllowedTill);
if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, backdatedTxnsAllowedTill);
return new CommandProcessingResultBuilder() //
.withEntityId(savingsAccountChargeId) //
.withOfficeId(account.officeId()) //
@@ -1178,7 +1250,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@SuppressWarnings("unused") final JsonCommand command) {
this.context.authenticatedUser();
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsAccountId, false);
checkClientOrGroupActive(savingsAccount);
final SavingsAccountCharge savingsAccountCharge = this.savingsAccountChargeRepository
.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");
if (StringUtils.isNotBlank(noteText)) {
@@ -1263,13 +1338,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MM yyyy").withZone(DateUtils.getDateTimeZoneOfTenant());
while (transactionDate.isAfter(savingsAccountCharge.getDueLocalDate()) && savingsAccountCharge.isNotFullyPaid()) {
payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt, user);
payCharge(savingsAccountCharge, transactionDate, savingsAccountCharge.amoutOutstanding(), fmt, user, false);
}
}
@Transactional
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
.isSavingsInterestPostingAtCurrentPeriodEnd();
@@ -1281,18 +1356,19 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final Set<Long> existingTransactionIds = new HashSet<>();
final Set<Long> existingReversedTransactionIds = new HashSet<>();
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;
LocalDate postInterestOnDate = null;
final MathContext mc = MathContext.DECIMAL64;
if (account.isBeforeLastPostingPeriod(transactionDate)) {
if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth,
postInterestOnDate);
postInterestOnDate, isInterestTransfer);
} else {
final LocalDate today = DateUtils.getLocalDateOfTenant();
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
financialYearBeginningMonth, postInterestOnDate);
financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill);
}
List<DepositAccountOnHoldTransaction> depositAccountOnHoldTransactions = null;
if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) {
@@ -1301,13 +1377,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
}
account.validateAccountBalanceDoesNotBecomeNegative("." + SavingsAccountTransactionType.PAY_CHARGE.getCode(),
depositAccountOnHoldTransactions);
depositAccountOnHoldTransactions, backdatedTxnsAllowedTill);
saveTransactionToGenerateTransactionId(chargeTransaction);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, backdatedTxnsAllowedTill);
return chargeTransaction;
}
@@ -1318,14 +1394,20 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
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,
final Set<Long> existingReversedTransactionIds) {
final Set<Long> existingReversedTransactionIds, final boolean backdatedTxnsAllowedTill) {
final MonetaryCurrency currency = savingsAccount.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepositoryWrapper.findOneWithNotFoundDetection(currency);
boolean isAccountTransfer = false;
final Map<String, Object> accountingBridgeData = savingsAccount.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, backdatedTxnsAllowedTill);
this.journalEntryWritePlatformService.createJournalEntriesForSavings(accountingBridgeData);
}
@@ -1502,19 +1584,19 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Override
@Transactional
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> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.setSubStatusInactive(appuserRepository.fetchSystemUser());
account.setSubStatusInactive(appuserRepository.fetchSystemUser(), false);
this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
}
@Override
@Transactional
public void setSubStatusDormant(Long savingsId) {
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
account.setSubStatusDormant();
this.savingAccountRepositoryWrapper.save(account);
}
@@ -1522,13 +1604,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
@Override
@Transactional
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> existingReversedTransactionIds = new HashSet<>();
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
account.escheat(appuserRepository.fetchSystemUser());
this.savingAccountRepositoryWrapper.saveAndFlush(account);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
}
private AppUser getAppUserIfPresent() {
@@ -1573,7 +1655,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account);
final Map<String, Object> changes = account.block();
@@ -1589,7 +1671,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult unblockAccount(final Long savingsId) {
this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account);
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) {
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);
SavingsAccountTransaction transacton = this.savingsAccountTransactionDataValidator.validateHoldAndAssembleForm(command.json(),
account, submittedBy);
account, submittedBy, backdatedTxnsAllowedTill);
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transacton.getTransactionLocalDate(), account);
this.savingsAccountTransactionRepository.save(transacton);
if (backdatedTxnsAllowedTill) {
// Check again whether transactions are modified
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(transacton.getId()).withOfficeId(account.officeId())
@@ -1629,12 +1721,21 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsAccountTransaction transaction = this.savingsAccountTransactionDataValidator
.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);
this.savingsAccountTransactionDataValidator.validateTransactionWithPivotDate(transaction.getTransactionLocalDate(), account);
account.releaseOnHoldAmount(transaction.getAmount());
this.savingsAccountTransactionRepository.save(transaction);
holdTransaction.updateReleaseId(transaction.getId());
if (backdatedTxnsAllowedTill) {
this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig());
}
this.savingAccountRepositoryWrapper.save(account);
return new CommandProcessingResultBuilder().withEntityId(transaction.getId()).withOfficeId(account.officeId())
@@ -1645,7 +1746,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult blockCredits(final Long savingsId) {
this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account);
final Map<String, Object> changes = account.blockCredits(account.getSubStatus());
@@ -1661,7 +1762,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult unblockCredits(final Long savingsId) {
this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account);
final Map<String, Object> changes = account.unblockCredits();
@@ -1677,7 +1778,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult blockDebits(final Long savingsId) {
this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account);
final Map<String, Object> changes = account.blockDebits(account.getSubStatus());
@@ -1693,7 +1794,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
public CommandProcessingResult unblockDebits(final Long savingsId) {
this.context.authenticatedUser();
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account);
final Map<String, Object> changes = account.unblockDebits();
@@ -359,7 +359,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
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);
account.modifyApplication(command, changes);
account.validateNewApplicationState(DateUtils.getLocalDateOfTenant(), SAVINGS_ACCOUNT_RESOURCE_NAME);
@@ -451,7 +451,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
@Override
public CommandProcessingResult deleteApplication(final Long savingsId) {
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(account);
if (account.isNotSubmittedAndPendingApproval()) {
@@ -515,7 +515,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator.validateApproval(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
@@ -581,7 +581,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator.validateForUndo(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount);
final Map<String, Object> changes = savingsAccount.undoApplicationApproval();
@@ -639,7 +639,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator.validateRejection(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
@@ -677,7 +677,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
this.savingsAccountApplicationTransitionApiJsonValidator.validateApplicantWithdrawal(command.json());
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId);
final SavingsAccount savingsAccount = this.savingAccountAssembler.assembleFrom(savingsId, false);
checkClientOrGroupActive(savingsAccount);
entityDatatableChecksWritePlatformService.runTheCheckForProduct(savingsId, EntityTables.SAVING.getName(),
@@ -752,7 +752,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
generateAccountNumber(account);
// post journal entries for activation charges
this.savingsAccountDomainService.postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds);
this.savingsAccountDomainService.postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false);
return new CommandProcessingResultBuilder() //
.withSavingsId(account.getId()) //
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
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.service.ThreadLocalContextUtil;
import org.apache.fineract.infrastructure.jobs.exception.JobExecutionException;
@@ -54,6 +55,7 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
private SavingsAccountRepositoryWrapper savingsAccountRepository;
private SavingsAccountAssembler savingAccountAssembler;
private FineractPlatformTenant tenant;
private ConfigurationDomainService configurationDomainService;
public void setSavingsIds(Collection<Long> savingsIds) {
this.savingsIds = savingsIds;
@@ -82,7 +84,7 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
ThreadLocalContextUtil.setTenant(tenant);
Integer maxNumberOfRetries = tenant.getConnection().getMaxRetriesOnDeadlock();
Integer maxIntervalBetweenRetries = tenant.getConnection().getMaxIntervalBetweenRetries();
final boolean backdatedTxnsAllowedTill = this.configurationDomainService.retrievePivotDateConfig();
int i = 0;
if (!savingsIds.isEmpty()) {
List<Throwable> errors = new ArrayList<>();
@@ -95,7 +97,8 @@ public class SavingsSchedularInterestPoster implements Callable<Void> {
this.savingAccountAssembler.assignSavingAccountHelpers(savingsAccount);
boolean postInterestAsOn = false;
LocalDate transactionDate = null;
this.savingsAccountWritePlatformService.postInterest(savingsAccount, postInterestAsOn, transactionDate);
this.savingsAccountWritePlatformService.postInterest(savingsAccount, postInterestAsOn, transactionDate,
backdatedTxnsAllowedTill);
numberOfRetries = maxNumberOfRetries + 1;
} catch (CannotAcquireLockException | ObjectOptimisticLockingFailureException exception) {
@@ -50,9 +50,9 @@ public class ShareAccountSchedularServiceImpl implements ShareAccountSchedularSe
public void postDividend(final Long dividendDetailId, final Long savingsId) {
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,
DateUtils.getLocalDateOfTenant(), shareAccountDividendDetails.getAmount());
DateUtils.getLocalDateOfTenant(), shareAccountDividendDetails.getAmount(), false);
shareAccountDividendDetails.update(ShareAccountDividendStatusType.POSTED.getValue(), savingsAccountTransaction.getId());
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.integrationtests.common.ClientHelper;
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.SchedulerJobHelper;
import org.apache.fineract.integrationtests.common.TaxComponentHelper;
@@ -128,6 +129,115 @@ public class ClientSavingsIntegrationTest {
// 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
public void testSavingsAccountWithMinBalanceForInterestCalculation() {
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
@@ -166,6 +276,10 @@ public class ClientSavingsIntegrationTest {
this.savingsAccountHelper.postInterestForSavings(savingsId);
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
// These two fields are getting updated after an interest posting
summary.remove("interestPostedTillDate");
assertEquals(summaryBefore, summary);
final Object savingsInterest = this.savingsAccountHelper.getSavingsInterest(savingsId);
@@ -76,6 +76,7 @@ public class GlobalConfigurationHelper {
// the future, it needs to be reset here.
final Integer configDefaultId = (Integer) configDefault.get("id");
final Integer configDefaultValue = (Integer) configDefault.get("value");
updateValueForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(), configDefaultValue.toString());
updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, configDefaultId.toString(),
(Boolean) configDefault.get("enabled"));
@@ -88,9 +89,9 @@ public class GlobalConfigurationHelper {
ArrayList<HashMap> expectedGlobalConfigurations = getAllDefaultGlobalConfigurations();
ArrayList<HashMap> actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec);
// There are currently 32 global configurations.
Assertions.assertEquals(33, expectedGlobalConfigurations.size());
Assertions.assertEquals(33, actualGlobalConfigurations.size());
// There are currently 35 global configurations.
Assertions.assertEquals(35, expectedGlobalConfigurations.size());
Assertions.assertEquals(35, actualGlobalConfigurations.size());
for (int i = 0; i < expectedGlobalConfigurations.size(); i++) {
@@ -389,6 +390,23 @@ public class GlobalConfigurationHelper {
isNextDayFixedDepositInterestTransferEnabledForPeriodEnd.put("trapDoor", false);
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;
}
@@ -147,8 +147,8 @@ public class SchedulerJobHelper {
* @author Michael Vorburger.ch
*/
public void executeAndAwaitJob(String jobName) {
final Duration timeout = Duration.ofMinutes(3);
final Duration pause = Duration.ofSeconds(1);
final Duration timeout = Duration.ofMinutes(4);
final Duration pause = Duration.ofSeconds(2);
DateTimeFormatter df = DateTimeFormatter.ISO_INSTANT; // FINERACT-926
Instant beforeExecuteTime = now().truncatedTo(ChronoUnit.SECONDS);