FINERACT-1931: Savings Account with overdraft - cannot deposit after negative balance

This commit is contained in:
jmarta
2023-09-18 15:19:57 +02:00
committed by Adam Saghy
parent ad3e518291
commit 1f107b4e81
4 changed files with 282 additions and 381 deletions
@@ -30,7 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.Getter;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.domain.LocalDateInterval;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
@@ -68,7 +67,7 @@ public final class SavingsAccountTransactionData implements Serializable {
private BigDecimal runningBalance;
private boolean reversed;
private final AccountTransferData transfer;
private LocalDate submittedOnDate;
private final LocalDate submittedOnDate;
private final boolean interestedPostedAsOn;
private final String submittedByUsername;
private final String note;
@@ -78,10 +77,10 @@ public final class SavingsAccountTransactionData implements Serializable {
private final Boolean lienTransaction;
private final Long releaseTransactionId;
private final String reasonForBlock;
private Set<SavingsAccountChargesPaidByData> chargesPaidByData = new HashSet<>();
private final Set<SavingsAccountChargesPaidByData> chargesPaidByData = new HashSet<>();
// templates
final Collection<PaymentTypeData> paymentTypeOptions;
private final Collection<PaymentTypeData> paymentTypeOptions;
// import fields
private transient Integer rowIndex;
@@ -96,45 +95,217 @@ public final class SavingsAccountTransactionData implements Serializable {
private String routingCode;
private String receiptNumber;
private String bankNumber;
private BigDecimal cumulativeBalance;
private LocalDate balanceEndDate;
private transient List<TaxDetailsData> taxDetails = new ArrayList<>();
private final transient List<TaxDetailsData> taxDetails = new ArrayList<>();
private Integer balanceNumberOfDays;
private BigDecimal overdraftAmount;
private transient Long modifiedId;
private transient String refNo;
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate transactionDate,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions,
final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final String submittedByUsername, final String note,
final Boolean isReversal, final Long originalTransactionId, boolean isManualTransaction, final Boolean lienTransaction,
final Long releaseTransactionId, final String reasonForBlock) {
this.id = id;
this.transactionType = transactionType;
TransactionEntryType entryType = null;
if (transactionType != null) {
entryType = transactionType.getEntryType();
entryType = entryType != null && Boolean.TRUE.equals(isReversal) ? entryType.getReversal() : entryType;
}
this.entryType = entryType;
// duplicated fields
this.accountId = savingsId;
this.accountNo = savingsAccountNo;
this.date = transactionDate;
this.amount = amount;
this.paymentDetailData = paymentDetailData;
this.currency = currency;
this.outstandingChargeAmount = outstandingChargeAmount;
this.runningBalance = runningBalance;
this.reversed = reversed;
this.transfer = transfer;
this.paymentTypeOptions = paymentTypeOptions;
this.submittedOnDate = submittedOnDate;
this.interestedPostedAsOn = interestedPostedAsOn;
this.submittedByUsername = submittedByUsername;
this.note = note;
this.isManualTransaction = isManualTransaction;
this.isReversal = isReversal;
this.originalTransactionId = originalTransactionId;
this.lienTransaction = lienTransaction;
this.releaseTransactionId = releaseTransactionId;
this.reasonForBlock = reasonForBlock;
}
private static SavingsAccountTransactionData createData(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long accountId, final String accountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions,
final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final String submittedByUsername, final String note,
final Boolean lienTransaction) {
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, accountId, accountNo, date, currency, amount,
outstandingChargeAmount, runningBalance, reversed, transfer, paymentTypeOptions, submittedOnDate, interestedPostedAsOn,
submittedByUsername, note, null, null, false, lienTransaction, null, null);
}
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final AccountTransferData transfer, final LocalDate submittedOnDate, final boolean interestedPostedAsOn,
final String submittedByUsername, final String note, final Boolean isReversal, final Long originalTransactionId,
final Boolean lienTransaction, final Long releaseTransactionId, final String reasonForBlock) {
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
amount, outstandingChargeAmount, runningBalance, reversed, transfer, null, submittedOnDate, interestedPostedAsOn,
submittedByUsername, note, isReversal, originalTransactionId, false, lienTransaction, releaseTransactionId, reasonForBlock);
}
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final AccountTransferData transfer, final boolean interestedPostedAsOn,
final String submittedByUsername, final String note, final LocalDate submittedOnDate) {
return createData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount,
outstandingChargeAmount, runningBalance, reversed, transfer, null, submittedOnDate, interestedPostedAsOn,
submittedByUsername, note, false);
}
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance,
final LocalDate balanceEndDate) {
SavingsAccountTransactionData data = createData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
amount, outstandingChargeAmount, runningBalance, reversed, null, null, submittedOnDate, interestedPostedAsOn, null, null,
false);
data.transactionDate = date;
data.cumulativeBalance = cumulativeBalance;
data.balanceEndDate = balanceEndDate;
return data;
}
public static SavingsAccountTransactionData create(final Long id) {
return createData(id, null, null, null, null, null, null, null, null, null, false, null, null, null, false, null, null, false);
}
public static SavingsAccountTransactionData withWithDrawalTransactionDetails(
final SavingsAccountTransactionData savingsAccountTransactionData) {
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations
.transactionType(SavingsAccountTransactionType.WITHDRAWAL.getValue());
return createData(savingsAccountTransactionData.getId(), transactionType, savingsAccountTransactionData.getPaymentDetailData(),
savingsAccountTransactionData.getAccountId(), savingsAccountTransactionData.getAccountNo(), currentDate,
savingsAccountTransactionData.getCurrency(), savingsAccountTransactionData.getAmount(),
savingsAccountTransactionData.getOutstandingChargeAmount(), savingsAccountTransactionData.getRunningBalance(),
savingsAccountTransactionData.isReversed(), savingsAccountTransactionData.getTransfer(),
savingsAccountTransactionData.getPaymentTypeOptions(), savingsAccountTransactionData.getSubmittedOnDate(),
savingsAccountTransactionData.isInterestedPostedAsOn(), savingsAccountTransactionData.getSubmittedByUsername(),
savingsAccountTransactionData.getNote(), savingsAccountTransactionData.getLienTransaction());
}
public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo,
final LocalDate defaultLocalDate, final CurrencyData currency) {
return createData(null, null, null, savingsId, savingsAccountNo, defaultLocalDate, currency, null, null, null, false, null, null,
defaultLocalDate, false, null, null, false);
}
public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData,
final Collection<PaymentTypeData> paymentTypeOptions) {
return createData(savingsAccountTransactionData.getId(), savingsAccountTransactionData.getTransactionType(),
savingsAccountTransactionData.getPaymentDetailData(), savingsAccountTransactionData.getAccountId(),
savingsAccountTransactionData.getAccountNo(), savingsAccountTransactionData.getDate(),
savingsAccountTransactionData.getCurrency(), savingsAccountTransactionData.getAmount(),
savingsAccountTransactionData.getOutstandingChargeAmount(), savingsAccountTransactionData.getRunningBalance(),
savingsAccountTransactionData.isReversed(), savingsAccountTransactionData.getTransfer(), paymentTypeOptions,
savingsAccountTransactionData.getSubmittedOnDate(), savingsAccountTransactionData.isInterestedPostedAsOn(),
savingsAccountTransactionData.getSubmittedByUsername(), savingsAccountTransactionData.getNote(),
savingsAccountTransactionData.getLienTransaction());
}
private static SavingsAccountTransactionData createImport(final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsAccountId, final String accountNumber,
final LocalDate transactionDate, final BigDecimal transactionAmount, final boolean reversed, final LocalDate submittedOnDate,
boolean isManualTransaction, final Boolean lienTransaction) {
SavingsAccountTransactionData data = new SavingsAccountTransactionData(null, transactionType, paymentDetailData, savingsAccountId,
accountNumber, transactionDate, null, transactionAmount, null, null, reversed, null, null, submittedOnDate, false, null,
null, null, null, isManualTransaction, lienTransaction, null, null);
// duplicated import fields
data.savingsAccountId = savingsAccountId;
data.accountNumber = accountNumber;
data.transactionDate = transactionDate;
data.transactionAmount = transactionAmount;
return data;
}
public static SavingsAccountTransactionData copyTransaction(SavingsAccountTransactionData accountTransaction) {
return createImport(accountTransaction.getTransactionType(), accountTransaction.getPaymentDetailData(),
accountTransaction.getSavingsAccountId(), null, accountTransaction.getTransactionDate(), accountTransaction.getAmount(),
accountTransaction.isReversed(), accountTransaction.getSubmittedOnDate(), accountTransaction.isManualTransaction(),
accountTransaction.getLienTransaction());
}
public static SavingsAccountTransactionData importInstance(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId,
String accountNumber, String checkNumber, String routingCode, String receiptNumber, String bankNumber, Long savingsAccountId,
SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat) {
return new SavingsAccountTransactionData(transactionAmount, transactionDate, paymentTypeId, accountNumber, checkNumber, routingCode,
receiptNumber, bankNumber, savingsAccountId, transactionType, rowIndex, locale, dateFormat, false);
SavingsAccountTransactionData data = createImport(transactionType, null, savingsAccountId, accountNumber, transactionDate,
transactionAmount, false, transactionDate, false, false);
data.rowIndex = rowIndex;
data.paymentTypeId = paymentTypeId;
data.checkNumber = checkNumber;
data.routingCode = routingCode;
data.receiptNumber = receiptNumber;
data.bankNumber = bankNumber;
data.locale = locale;
data.dateFormat = dateFormat;
return data;
}
private static SavingsAccountTransactionData createImport(SavingsAccountTransactionEnumData transactionType, Long savingsAccountId,
LocalDate transactionDate, BigDecimal transactionAmount, final LocalDate submittedOnDate, boolean isManualTransaction) {
// import transaction
return createImport(transactionType, null, savingsAccountId, null, transactionDate, transactionAmount, false, submittedOnDate,
isManualTransaction, false);
}
public static SavingsAccountTransactionData interestPosting(final SavingsAccountData savingsAccount, final LocalDate date,
final Money amount, final boolean isManualTransaction) {
final boolean isReversed = false;
final Boolean lienTransaction = false;
final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate();
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.INTEREST_POSTING;
SavingsAccountTransactionEnumData savingsAccountTransactionEnumData = new SavingsAccountTransactionEnumData(
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
savingsAccountTransactionType.getValue().toString());
return new SavingsAccountTransactionData(amount.getAmount(), date, savingsAccount.getId(), savingsAccountTransactionEnumData,
isReversed, null, isManualTransaction, lienTransaction, submittedOnDate);
return createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(), submittedOnDate, isManualTransaction);
}
public static SavingsAccountTransactionData overdraftInterest(final SavingsAccountData savingsAccount, final LocalDate date,
final Money amount, final boolean isManualTransaction) {
final boolean isReversed = false;
final Boolean lienTransaction = false;
final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate();
final SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.OVERDRAFT_INTEREST;
SavingsAccountTransactionEnumData savingsAccountTransactionEnumData = new SavingsAccountTransactionEnumData(
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
savingsAccountTransactionType.getValue().toString());
return new SavingsAccountTransactionData(amount.getAmount(), date, savingsAccount.getId(), savingsAccountTransactionEnumData,
isReversed, null, isManualTransaction, lienTransaction, submittedOnDate);
return createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(), submittedOnDate, isManualTransaction);
}
public static SavingsAccountTransactionData withHoldTax(final SavingsAccountData savingsAccount, final LocalDate date,
final Money amount, final Map<TaxComponentData, BigDecimal> taxDetails) {
final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate();
SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.WITHHOLD_TAX;
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
savingsAccountTransactionType.getValue().toString());
SavingsAccountTransactionData accountTransaction = createImport(transactionType, savingsAccount.getId(), date, amount.getAmount(),
submittedOnDate, false);
accountTransaction.addTaxDetails(taxDetails);
return accountTransaction;
}
public boolean isInterestPostingAndNotReversed() {
@@ -150,15 +321,15 @@ public final class SavingsAccountTransactionData implements Serializable {
}
public boolean isCredit() {
return transactionType.getTransactionTypeEnum().isCredit() && isNotReversed() && !isReversalTransaction();
return transactionType.isCredit() && isNotReversed() && !isReversalTransaction();
}
public boolean isDebit() {
return transactionType.getTransactionTypeEnum().isDebit() && isNotReversed() && !isReversalTransaction();
return transactionType.isDebit() && isNotReversed() && !isReversalTransaction();
}
public boolean isWithdrawalFeeAndNotReversed() {
return this.transactionType.isFeeDeduction() && isNotReversed();
return transactionType.isFeeDeduction() && isNotReversed();
}
public boolean isPayCharge() {
@@ -298,55 +469,6 @@ public final class SavingsAccountTransactionData implements Serializable {
return this.transactionType.isDeposit();
}
public static SavingsAccountTransactionData copyTransaction(SavingsAccountTransactionData accountTransaction) {
return new SavingsAccountTransactionData(accountTransaction.getSavingsAccountId(), null, accountTransaction.getPaymentDetailData(),
accountTransaction.getTransactionType(), accountTransaction.getTransactionDate(), accountTransaction.getSubmittedOnDate(),
accountTransaction.getAmount(), accountTransaction.isReversed(), null, accountTransaction.isManualTransaction(),
accountTransaction.lienTransaction);
}
private SavingsAccountTransactionData(final Long savingsId, final Long officeId, final PaymentDetailData paymentDetailData,
final SavingsAccountTransactionEnumData savingsAccountTransactionType, final LocalDate transactionDate,
final LocalDate submittedOnDate, final BigDecimal amount, final boolean isReversed, final Long userId,
final boolean isManualTransaction, final Boolean lienTransaction) {
this.savingsAccountId = savingsId;
this.paymentDetailData = paymentDetailData;
this.transactionType = savingsAccountTransactionType;
this.entryType = transactionType == null ? null : transactionType.getTransactionTypeEnum().getEntryType();
this.transactionDate = transactionDate;
this.submittedOnDate = submittedOnDate;
this.amount = amount;
this.isManualTransaction = isManualTransaction;
this.lienTransaction = lienTransaction;
this.id = null;
this.accountId = null;
this.accountNo = null;
this.date = null;
this.currency = null;
this.outstandingChargeAmount = null;
this.runningBalance = null;
this.reversed = isReversed;
this.transfer = null;
this.interestedPostedAsOn = false;
this.rowIndex = null;
this.dateFormat = null;
this.locale = null;
this.transactionAmount = null;
this.paymentTypeId = null;
this.accountNumber = null;
this.checkNumber = null;
this.routingCode = null;
this.receiptNumber = null;
this.bankNumber = null;
this.paymentTypeOptions = null;
this.submittedByUsername = null;
this.note = null;
this.isReversal = null;
this.originalTransactionId = null;
this.releaseTransactionId = null;
this.reasonForBlock = null;
}
public boolean isChargeTransaction() {
return this.transactionType.isChargeTransaction();
}
@@ -432,27 +554,11 @@ public final class SavingsAccountTransactionData implements Serializable {
return Money.of(currency, this.amount);
}
public static SavingsAccountTransactionData withHoldTax(final SavingsAccountData savingsAccount, final LocalDate date,
final Money amount, final Map<TaxComponentData, BigDecimal> taxDetails) {
final boolean isReversed = false;
final boolean isManualTransaction = false;
final Boolean lienTransaction = false;
final LocalDate submittedOnDate = DateUtils.getBusinessLocalDate();
SavingsAccountTransactionType savingsAccountTransactionType = SavingsAccountTransactionType.WITHHOLD_TAX;
SavingsAccountTransactionEnumData transactionType = new SavingsAccountTransactionEnumData(
savingsAccountTransactionType.getValue().longValue(), savingsAccountTransactionType.getCode(),
savingsAccountTransactionType.getValue().toString());
SavingsAccountTransactionData accountTransaction = new SavingsAccountTransactionData(amount.getAmount(), date,
savingsAccount.getId(), transactionType, isReversed, null, isManualTransaction, lienTransaction, submittedOnDate);
updateTaxDetails(taxDetails, accountTransaction);
return accountTransaction;
}
public static void updateTaxDetails(final Map<TaxComponentData, BigDecimal> taxDetails,
final SavingsAccountTransactionData accountTransaction) {
public void addTaxDetails(final Map<TaxComponentData, BigDecimal> taxDetails) {
if (taxDetails != null) {
List<TaxDetailsData> thisTaxDetails = getTaxDetails();
for (Map.Entry<TaxComponentData, BigDecimal> mapEntry : taxDetails.entrySet()) {
accountTransaction.getTaxDetails().add(new TaxDetailsData(mapEntry.getKey(), mapEntry.getValue()));
thisTaxDetails.add(new TaxDetailsData(mapEntry.getKey(), mapEntry.getValue()));
}
}
}
@@ -510,89 +616,6 @@ public final class SavingsAccountTransactionData implements Serializable {
return thisTransactionData;
}
private SavingsAccountTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long savingsAccountId,
SavingsAccountTransactionEnumData transactionType, boolean isReversed, String locale, boolean isManualTransaction,
final Boolean lienTransaction, final LocalDate submittedOnDate) {
this.id = null;
this.transactionType = transactionType;
this.entryType = transactionType == null ? null : transactionType.getTransactionTypeEnum().getEntryType();
this.accountId = null;
this.accountNo = null;
this.date = transactionDate;
this.currency = null;
this.paymentDetailData = null;
this.amount = transactionAmount;
this.outstandingChargeAmount = null;
this.runningBalance = null;
this.reversed = isReversed;
this.transfer = null;
this.submittedOnDate = submittedOnDate;
this.interestedPostedAsOn = false;
this.rowIndex = null;
this.savingsAccountId = savingsAccountId;
this.dateFormat = null;
this.locale = locale;
this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount;
this.paymentTypeId = null;
this.accountNumber = null;
this.checkNumber = null;
this.routingCode = null;
this.receiptNumber = null;
this.bankNumber = null;
this.paymentTypeOptions = null;
this.submittedByUsername = null;
this.note = null;
this.isManualTransaction = isManualTransaction;
this.isReversal = null;
this.originalTransactionId = null;
this.lienTransaction = lienTransaction;
this.releaseTransactionId = null;
this.reasonForBlock = null;
}
private SavingsAccountTransactionData(BigDecimal transactionAmount, LocalDate transactionDate, Long paymentTypeId, String accountNumber,
String checkNumber, String routingCode, String receiptNumber, String bankNumber, Long savingsAccountId,
SavingsAccountTransactionEnumData transactionType, Integer rowIndex, String locale, String dateFormat,
final Boolean lienTransaction) {
this.id = null;
this.transactionType = transactionType;
this.entryType = transactionType == null ? null : transactionType.getTransactionTypeEnum().getEntryType();
this.accountId = null;
this.accountNo = null;
this.date = null;
this.currency = null;
this.paymentDetailData = null;
this.amount = null;
this.outstandingChargeAmount = null;
this.runningBalance = null;
this.reversed = false;
this.transfer = null;
this.submittedOnDate = null;
this.interestedPostedAsOn = false;
this.rowIndex = rowIndex;
this.savingsAccountId = savingsAccountId;
this.dateFormat = dateFormat;
this.locale = locale;
this.transactionDate = transactionDate;
this.transactionAmount = transactionAmount;
this.paymentTypeId = paymentTypeId;
this.accountNumber = accountNumber;
this.checkNumber = checkNumber;
this.routingCode = routingCode;
this.receiptNumber = receiptNumber;
this.bankNumber = bankNumber;
this.paymentTypeOptions = null;
this.submittedByUsername = null;
this.note = null;
this.isManualTransaction = false;
this.isReversal = null;
this.originalTransactionId = null;
this.lienTransaction = lienTransaction;
this.releaseTransactionId = null;
this.reasonForBlock = null;
}
public boolean isWithdrawal() {
return this.transactionType.isWithdrawal();
}
@@ -614,195 +637,6 @@ public final class SavingsAccountTransactionData implements Serializable {
return balanceInterval.containsPortionOf(periodInterval);
}
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance,
final LocalDate balanceEndDate) {
final Collection<PaymentTypeData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
amount, outstandingChargeAmount, runningBalance, reversed, submittedOnDate, paymentTypeOptions, interestedPostedAsOn,
cumulativeBalance, balanceEndDate, false);
}
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final LocalDate submittedOnDate, final Collection<PaymentTypeData> paymentTypeOptions,
final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate,
final Boolean lienTransaction) {
this(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount,
runningBalance, reversed, paymentTypeOptions, submittedOnDate, interestedPostedAsOn, cumulativeBalance, balanceEndDate,
lienTransaction);
}
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final Collection<PaymentTypeData> paymentTypeOptions, final LocalDate submittedOnDate,
final boolean interestedPostedAsOn, final BigDecimal cumulativeBalance, final LocalDate balanceEndDate,
final Boolean lienTransaction) {
this.id = id;
this.transactionDate = date;
this.transactionType = transactionType;
this.entryType = transactionType == null ? null : transactionType.getTransactionTypeEnum().getEntryType();
this.paymentDetailData = paymentDetailData;
this.accountId = savingsId;
this.accountNo = savingsAccountNo;
this.date = date;
this.currency = currency;
this.amount = amount;
this.outstandingChargeAmount = outstandingChargeAmount;
this.runningBalance = runningBalance;
this.reversed = reversed;
this.paymentTypeOptions = paymentTypeOptions;
this.submittedOnDate = submittedOnDate;
this.interestedPostedAsOn = interestedPostedAsOn;
this.cumulativeBalance = cumulativeBalance;
this.transfer = null;
this.submittedByUsername = null;
this.note = null;
this.balanceEndDate = balanceEndDate;
this.isManualTransaction = false;
this.isReversal = null;
this.originalTransactionId = null;
this.lienTransaction = lienTransaction;
this.releaseTransactionId = null;
this.reasonForBlock = null;
}
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final AccountTransferData transfer, final boolean interestedPostedAsOn,
final String submittedByUsername, final String note, final LocalDate submittedOnDate) {
final Collection<PaymentTypeData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
amount, outstandingChargeAmount, runningBalance, reversed, transfer, paymentTypeOptions, interestedPostedAsOn,
submittedByUsername, note, false, submittedOnDate);
}
public static SavingsAccountTransactionData create(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final AccountTransferData transfer, final LocalDate submittedOnDate, final boolean interestedPostedAsOn,
final String submittedByUsername, final String note, final Boolean isReversal, final Long originalTransactionId,
final Boolean lienTransaction, final Long releaseTransactionId, final String reasonForBlock) {
final Collection<PaymentTypeData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency,
amount, outstandingChargeAmount, runningBalance, reversed, transfer, paymentTypeOptions, submittedOnDate,
interestedPostedAsOn, submittedByUsername, note, isReversal, originalTransactionId, lienTransaction, releaseTransactionId,
reasonForBlock);
}
public static SavingsAccountTransactionData create(final Long id) {
final Collection<PaymentTypeData> paymentTypeOptions = null;
return new SavingsAccountTransactionData(id, null, null, null, null, null, null, null, null, null, false, null, paymentTypeOptions,
null, false, null, null, null, null, false, null, null);
}
public static SavingsAccountTransactionData template(final Long savingsId, final String savingsAccountNo,
final LocalDate defaultLocalDate, final CurrencyData currency) {
final Long id = null;
final SavingsAccountTransactionEnumData transactionType = null;
final BigDecimal amount = null;
final BigDecimal outstandingChargeAmount = null;
final BigDecimal runningBalance = null;
final boolean reversed = false;
final PaymentDetailData paymentDetailData = null;
final Collection<CodeValueData> paymentTypeOptions = null;
final boolean interestedPostedAsOn = false;
final String submittedByUsername = null;
final String note = null;
final Boolean lienTransaction = false;
return new SavingsAccountTransactionData(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, defaultLocalDate,
currency, amount, outstandingChargeAmount, runningBalance, reversed, null, null, interestedPostedAsOn, submittedByUsername,
note, lienTransaction, defaultLocalDate);
}
public static SavingsAccountTransactionData templateOnTop(final SavingsAccountTransactionData savingsAccountTransactionData,
final Collection<PaymentTypeData> paymentTypeOptions) {
return new SavingsAccountTransactionData(savingsAccountTransactionData.getId(), savingsAccountTransactionData.getTransactionType(),
savingsAccountTransactionData.getPaymentDetailData(), savingsAccountTransactionData.getAccountId(),
savingsAccountTransactionData.getAccountNo(), savingsAccountTransactionData.getDate(),
savingsAccountTransactionData.getCurrency(), savingsAccountTransactionData.getAmount(),
savingsAccountTransactionData.getOutstandingChargeAmount(), savingsAccountTransactionData.getRunningBalance(),
savingsAccountTransactionData.isReversed(), savingsAccountTransactionData.getTransfer(), paymentTypeOptions,
savingsAccountTransactionData.isInterestedPostedAsOn(), savingsAccountTransactionData.getSubmittedByUsername(),
savingsAccountTransactionData.getNote(), savingsAccountTransactionData.getLienTransaction(),
savingsAccountTransactionData.getSubmittedOnDate());
}
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions,
final boolean interestedPostedAsOn, final String submittedByUsername, final String note, final Boolean lienTransaction,
final LocalDate submittedOnDate) {
this(id, transactionType, paymentDetailData, savingsId, savingsAccountNo, date, currency, amount, outstandingChargeAmount,
runningBalance, reversed, transfer, paymentTypeOptions, submittedOnDate, interestedPostedAsOn, submittedByUsername, note,
null, null, lienTransaction, null, null);
}
private SavingsAccountTransactionData(final Long id, final SavingsAccountTransactionEnumData transactionType,
final PaymentDetailData paymentDetailData, final Long savingsId, final String savingsAccountNo, final LocalDate date,
final CurrencyData currency, final BigDecimal amount, final BigDecimal outstandingChargeAmount, final BigDecimal runningBalance,
final boolean reversed, final AccountTransferData transfer, final Collection<PaymentTypeData> paymentTypeOptions,
final LocalDate submittedOnDate, final boolean interestedPostedAsOn, final String submittedByUsername, final String note,
final Boolean isReversal, final Long originalTransactionId, final Boolean lienTransaction, final Long releaseTransactionId,
final String reasonForBlock) {
this.id = id;
this.transactionType = transactionType;
TransactionEntryType entryType = null;
if (transactionType != null) {
entryType = transactionType.getTransactionTypeEnum().getEntryType();
entryType = Boolean.TRUE.equals(isReversal) ? entryType.getReversal() : entryType;
}
this.entryType = entryType;
this.paymentDetailData = paymentDetailData;
this.accountId = savingsId;
this.accountNo = savingsAccountNo;
this.date = date;
this.currency = currency;
this.amount = amount;
this.outstandingChargeAmount = outstandingChargeAmount;
this.runningBalance = runningBalance;
this.reversed = reversed;
this.transfer = transfer;
this.paymentTypeOptions = paymentTypeOptions;
this.submittedOnDate = submittedOnDate;
this.interestedPostedAsOn = interestedPostedAsOn;
this.submittedByUsername = submittedByUsername;
this.note = note;
this.isManualTransaction = false;
this.isReversal = isReversal;
this.originalTransactionId = originalTransactionId;
this.lienTransaction = lienTransaction;
this.releaseTransactionId = releaseTransactionId;
this.reasonForBlock = reasonForBlock;
}
public static SavingsAccountTransactionData withWithDrawalTransactionDetails(
final SavingsAccountTransactionData savingsAccountTransactionData) {
final LocalDate currentDate = DateUtils.getBusinessLocalDate();
final SavingsAccountTransactionEnumData transactionType = SavingsEnumerations
.transactionType(SavingsAccountTransactionType.WITHDRAWAL.getValue());
return new SavingsAccountTransactionData(savingsAccountTransactionData.getId(), transactionType,
savingsAccountTransactionData.getPaymentDetailData(), savingsAccountTransactionData.getAccountId(),
savingsAccountTransactionData.getAccountNo(), currentDate, savingsAccountTransactionData.getCurrency(),
savingsAccountTransactionData.getAmount(), savingsAccountTransactionData.getOutstandingChargeAmount(),
savingsAccountTransactionData.getRunningBalance(), savingsAccountTransactionData.isReversed(),
savingsAccountTransactionData.getTransfer(), savingsAccountTransactionData.getPaymentTypeOptions(),
savingsAccountTransactionData.isInterestedPostedAsOn(), savingsAccountTransactionData.getSubmittedByUsername(),
savingsAccountTransactionData.getNote(), savingsAccountTransactionData.getLienTransaction(),
savingsAccountTransactionData.getSubmittedOnDate());
}
public void setId(final Long id) {
this.id = id;
this.modifiedId = id;
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.savings.data;
import java.io.Serializable;
import lombok.Getter;
import org.apache.fineract.portfolio.TransactionEntryType;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
/**
@@ -43,7 +44,7 @@ public class SavingsAccountTransactionEnumData implements Serializable {
private final boolean rejectTransfer;
private final boolean overdraftInterest;
private final boolean writtenoff;
private final boolean overdraftFee = true;
private final boolean overdraftFee;
private final boolean withholdTax;
private final boolean escheat;
private final boolean amountHold;
@@ -53,23 +54,25 @@ public class SavingsAccountTransactionEnumData implements Serializable {
this.id = id;
this.code = code;
this.value = value;
this.deposit = Long.valueOf(SavingsAccountTransactionType.DEPOSIT.getValue()).equals(this.id);
this.dividendPayout = Long.valueOf(SavingsAccountTransactionType.DIVIDEND_PAYOUT.getValue()).equals(this.id);
this.withdrawal = Long.valueOf(SavingsAccountTransactionType.WITHDRAWAL.getValue()).equals(this.id);
this.interestPosting = Long.valueOf(SavingsAccountTransactionType.INTEREST_POSTING.getValue()).equals(this.id);
this.feeDeduction = Long.valueOf(SavingsAccountTransactionType.ANNUAL_FEE.getValue()).equals(this.id)
|| Long.valueOf(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue()).equals(this.id)
|| Long.valueOf(SavingsAccountTransactionType.PAY_CHARGE.getValue()).equals(this.id);
this.initiateTransfer = Long.valueOf(SavingsAccountTransactionType.INITIATE_TRANSFER.getValue()).equals(this.id);
this.approveTransfer = Long.valueOf(SavingsAccountTransactionType.APPROVE_TRANSFER.getValue()).equals(this.id);
this.withdrawTransfer = Long.valueOf(SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue()).equals(this.id);
this.rejectTransfer = Long.valueOf(SavingsAccountTransactionType.REJECT_TRANSFER.getValue()).equals(this.id);
this.writtenoff = Long.valueOf(SavingsAccountTransactionType.WRITTEN_OFF.getValue()).equals(this.id);
this.overdraftInterest = Long.valueOf(SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue()).equals(this.id);
this.withholdTax = Long.valueOf(SavingsAccountTransactionType.WITHHOLD_TAX.getValue()).equals(this.id);
this.escheat = Long.valueOf(SavingsAccountTransactionType.ESCHEAT.getValue()).equals(this.id);
this.amountHold = Long.valueOf(SavingsAccountTransactionType.AMOUNT_HOLD.getValue()).equals(this.id);
this.amountRelease = Long.valueOf(SavingsAccountTransactionType.AMOUNT_RELEASE.getValue()).equals(this.id);
SavingsAccountTransactionType transactionType = id == null ? null : SavingsAccountTransactionType.fromInt(id.intValue());
this.deposit = transactionType == SavingsAccountTransactionType.DEPOSIT;
this.dividendPayout = transactionType == SavingsAccountTransactionType.DIVIDEND_PAYOUT;
this.withdrawal = transactionType == SavingsAccountTransactionType.WITHDRAWAL;
this.interestPosting = transactionType == SavingsAccountTransactionType.INTEREST_POSTING;
this.feeDeduction = transactionType == SavingsAccountTransactionType.ANNUAL_FEE
|| transactionType == SavingsAccountTransactionType.WITHDRAWAL_FEE
|| transactionType == SavingsAccountTransactionType.PAY_CHARGE;
this.initiateTransfer = transactionType == SavingsAccountTransactionType.INITIATE_TRANSFER;
this.approveTransfer = transactionType == SavingsAccountTransactionType.APPROVE_TRANSFER;
this.withdrawTransfer = transactionType == SavingsAccountTransactionType.WITHDRAW_TRANSFER;
this.rejectTransfer = transactionType == SavingsAccountTransactionType.REJECT_TRANSFER;
this.writtenoff = transactionType == SavingsAccountTransactionType.WRITTEN_OFF;
this.overdraftFee = false;
this.overdraftInterest = transactionType == SavingsAccountTransactionType.OVERDRAFT_INTEREST;
this.withholdTax = transactionType == SavingsAccountTransactionType.WITHHOLD_TAX;
this.escheat = transactionType == SavingsAccountTransactionType.ESCHEAT;
this.amountHold = transactionType == SavingsAccountTransactionType.AMOUNT_HOLD;
this.amountRelease = transactionType == SavingsAccountTransactionType.AMOUNT_RELEASE;
}
public boolean isIncomeFromInterest() {
@@ -85,7 +88,7 @@ public class SavingsAccountTransactionEnumData implements Serializable {
}
public boolean isChargeTransaction() {
return isPayCharge() || isWithdrawalFee() || isAnnualFee();
return feeDeduction;
}
public boolean isAnnualFee() {
@@ -100,7 +103,22 @@ public class SavingsAccountTransactionEnumData implements Serializable {
return Long.valueOf(SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue()).equals(this.id);
}
public boolean isCredit() {
SavingsAccountTransactionType transactionType = getTransactionTypeEnum();
return transactionType != null && transactionType.isCredit();
}
public boolean isDebit() {
SavingsAccountTransactionType transactionType = getTransactionTypeEnum();
return transactionType != null && transactionType.isDebit();
}
public TransactionEntryType getEntryType() {
SavingsAccountTransactionType transactionType = getTransactionTypeEnum();
return transactionType == null ? null : transactionType.getEntryType();
}
public SavingsAccountTransactionType getTransactionTypeEnum() {
return SavingsAccountTransactionType.fromInt(id.intValue());
return id == null ? null : SavingsAccountTransactionType.fromInt(id.intValue());
}
}
@@ -204,7 +204,7 @@ public class SavingsSchedularInterestPoster {
savingsAccountTransactionData.isReversed(), savingsAccountTransactionData.getTransactionType().getId(),
savingsAccountTransactionData.getTransactionDate(), savingsAccountTransactionData.getAmount(), balanceEndDate,
savingsAccountTransactionData.getBalanceNumberOfDays(), savingsAccountTransactionData.getRunningBalance(),
savingsAccountTransactionData.getCumulativeBalance(), currentDate, Integer.valueOf(1),
savingsAccountTransactionData.getCumulativeBalance(), currentDate, 1,
savingsAccountTransactionData.isManualTransaction(), savingsAccountTransactionData.getRefNo(),
savingsAccountTransactionData.isReversalTransaction(), savingsAccountTransactionData.getOverdraftAmount(),
DateUtils.getBusinessLocalDate() });
@@ -3162,7 +3162,56 @@ public class ClientSavingsIntegrationTest {
assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance",
error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE));
}
@Test
public void testSavingsAccountDepositAfterNegativeHoldAmount() {
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, "0", null, false, true, false);
Assertions.assertNotNull(savingsProductID);
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL);
this.savingsAccountHelper.approveSavings(savingsId);
HashMap savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
float balance = 0F;
float transactionAmount = 100F;
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId,
String.valueOf(transactionAmount), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
Assertions.assertNotNull(depositTransactionId);
balance = balance + transactionAmount;
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after deposit");
Integer withdrawalTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId,
String.valueOf(transactionAmount), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
Assertions.assertNotNull(withdrawalTransactionId);
balance = balance - transactionAmount;
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after withdrawal");
float holdAmount = 50F;
Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, String.valueOf(holdAmount),
false, SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
Assertions.assertNotNull(holdTransactionId);
balance = balance - holdAmount;
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after hold amount");
this.savingsAccountHelper.releaseAmount(savingsId, holdTransactionId);
balance = balance + holdAmount;
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after release amount");
depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, String.valueOf(transactionAmount),
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
Assertions.assertNotNull(depositTransactionId);
balance = balance + transactionAmount;
summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
assertEquals(balance, summary.get("availableBalance"), "Verifying Balance after hold-release-deposit");
}
private Integer createSavingsAccountDailyPostingOverdraft(final Integer clientID, final String startDate) {