From 1f107b4e817631e609a15fcf70cde1e202c7a961 Mon Sep 17 00:00:00 2001 From: jmarta Date: Tue, 5 Sep 2023 19:30:49 +0200 Subject: [PATCH] FINERACT-1931: Savings Account with overdraft - cannot deposit after negative balance --- .../data/SavingsAccountTransactionData.java | 554 ++++++------------ .../SavingsAccountTransactionEnumData.java | 58 +- .../SavingsSchedularInterestPoster.java | 2 +- .../ClientSavingsIntegrationTest.java | 49 ++ 4 files changed, 282 insertions(+), 381 deletions(-) diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java index d99dcd95f60..0ff25943d98 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java @@ -30,7 +30,6 @@ 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 chargesPaidByData = new HashSet<>(); + private final Set chargesPaidByData = new HashSet<>(); // templates - final Collection paymentTypeOptions; + private final Collection 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 taxDetails = new ArrayList<>(); + private final transient List 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 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 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 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 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 boolean isOverdraftInterestAndNotReversed() { } 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 boolean isDeposit() { 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 Money getAmount(final MonetaryCurrency currency) { return Money.of(currency, this.amount); } - public static SavingsAccountTransactionData withHoldTax(final SavingsAccountData savingsAccount, final LocalDate date, - final Money amount, final Map 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 taxDetails, - final SavingsAccountTransactionData accountTransaction) { + public void addTaxDetails(final Map taxDetails) { if (taxDetails != null) { + List thisTaxDetails = getTaxDetails(); for (Map.Entry 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 Map toMapData(final CurrencyData currencyData, final Long 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 boolean spansAnyPortionOf(final LocalDateInterval periodInterval) { 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 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 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 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 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 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 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 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 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 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 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; diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java index c9814a96047..60401d5b2ba 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionEnumData.java @@ -20,6 +20,7 @@ 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 SavingsAccountTransactionEnumData(final Long id, final String code, final 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 boolean isDepositOrWithdrawal() { } public boolean isChargeTransaction() { - return isPayCharge() || isWithdrawalFee() || isAnnualFee(); + return feeDeduction; } public boolean isAnnualFee() { @@ -100,7 +103,22 @@ public boolean isWithdrawalFee() { 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()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java index 3ba3de47ed1..ccf0d5f34b9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsSchedularInterestPoster.java @@ -204,7 +204,7 @@ private void batchUpdate(final List savingsAccountDataList) 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() }); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index 55a6dcf9bd7..8923bcefe33 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -3162,7 +3162,56 @@ public void testSavingsAccountDepositAfterHoldAmount() { 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) {