diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java index e92edaefd..145f3e19f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountSummaryData.java @@ -78,7 +78,7 @@ public class SavingsAccountSummaryData implements Serializable { this.interestNotPosted = interestNotPosted; this.lastInterestCalculationDate = lastInterestCalculationDate; this.availableBalance = availableBalance; - this.interestPostedTillDate = interestPostedTillDate == null ? lastInterestCalculationDate : interestPostedTillDate; + this.interestPostedTillDate = interestPostedTillDate; } public void setPrevInterestPostedTillDate(LocalDate interestPostedTillDate) { @@ -152,7 +152,7 @@ public class SavingsAccountSummaryData implements Serializable { public void updateSummaryWithPivotConfig(final CurrencyData currency, final SavingsAccountTransactionSummaryWrapper wrapper, final SavingsAccountTransaction transaction, final List savingsAccountTransactions) { - if (transaction != null) { + if (transaction != null && !transaction.isReversalTransaction()) { Money transactionAmount = Money.of(currency, transaction.getAmount()); switch (SavingsAccountTransactionType.fromInt(transaction.getTypeOf())) { case DEPOSIT: @@ -219,15 +219,17 @@ public class SavingsAccountSummaryData implements Serializable { // boolean isUpdated = false; Money interestTotal = Money.of(currency, this.totalInterestPosted); Money withHoldTaxTotal = Money.of(currency, this.totalWithholdTax); - + Money overdraftInterestTotal = Money.of(currency, this.totalOverdraftInterestDerived); final HashMap map = updateRunningBalanceAndPivotDate(true, savingsAccountTransactions, interestTotal, - withHoldTaxTotal, currency); + overdraftInterestTotal, withHoldTaxTotal, currency); interestTotal = map.get("interestTotal"); withHoldTaxTotal = map.get("withHoldTax"); + overdraftInterestTotal = map.get("overdraftInterestTotal"); this.totalInterestPosted = interestTotal.getAmountDefaultedToNullIfZero(); this.totalWithholdTax = withHoldTaxTotal.getAmountDefaultedToNullIfZero(); + this.totalOverdraftInterestDerived = overdraftInterestTotal.getAmountDefaultedToNullIfZero(); this.accountBalance = Money.of(currency, this.accountBalance).plus(this.totalInterestPosted).minus(this.totalWithholdTax) - .getAmount(); + .minus(this.totalOverdraftInterestDerived).getAmount(); } } @@ -246,13 +248,23 @@ public class SavingsAccountSummaryData implements Serializable { @SuppressWarnings("unchecked") private HashMap updateRunningBalanceAndPivotDate(final boolean backdatedTxnsAllowedTill, - final List savingsAccountTransactions, Money interestTotal, Money withHoldTaxTotal, - CurrencyData currency) { + final List savingsAccountTransactions, Money interestTotal, Money overdraftInterestTotal, + Money withHoldTaxTotal, CurrencyData currency) { boolean isUpdated = false; HashMap map = new HashMap<>(); for (int i = savingsAccountTransactions.size() - 1; i >= 0; i--) { final SavingsAccountTransactionData savingsAccountTransaction = savingsAccountTransactions.get(i); - if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed() && !isUpdated) { + if (savingsAccountTransaction.isInterestPostingAndNotReversed() && !savingsAccountTransaction.isReversalTransaction() + && !isUpdated) { + setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance(currency).getAmount()); + setInterestPostedTillDate(savingsAccountTransaction.getTransactionDate()); + isUpdated = true; + if (!backdatedTxnsAllowedTill) { + break; + } + } + if (savingsAccountTransaction.isOverdraftInterestAndNotReversed() && !savingsAccountTransaction.isReversalTransaction() + && !isUpdated) { setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance(currency).getAmount()); setInterestPostedTillDate(savingsAccountTransaction.getTransactionDate()); isUpdated = true; @@ -261,10 +273,12 @@ public class SavingsAccountSummaryData implements Serializable { } } if (backdatedTxnsAllowedTill) { - if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed()) { + if (savingsAccountTransaction.isInterestPostingAndNotReversed()) { interestTotal = interestTotal.plus(savingsAccountTransaction.getAmount()); } - + if (savingsAccountTransaction.isOverdraftInterestAndNotReversed()) { + overdraftInterestTotal = overdraftInterestTotal.plus(savingsAccountTransaction.getAmount()); + } if (savingsAccountTransaction.isWithHoldTaxAndNotReversed()) { withHoldTaxTotal = withHoldTaxTotal.plus(savingsAccountTransaction.getAmount()); } @@ -273,6 +287,7 @@ public class SavingsAccountSummaryData implements Serializable { if (backdatedTxnsAllowedTill) { map.put("interestTotal", interestTotal); map.put("withHoldTax", withHoldTaxTotal); + map.put("overdraftInterestTotal", overdraftInterestTotal); } return map; } @@ -293,7 +308,7 @@ public class SavingsAccountSummaryData implements Serializable { this.totalWithholdTax = wrapper.calculateTotalWithholdTaxWithdrawal(currency, transactions); // boolean isUpdated = false; - updateRunningBalanceAndPivotDate(false, transactions, null, null, currency); + updateRunningBalanceAndPivotDate(false, transactions, null, null, null, currency); this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index 9dff82f57..c03c8f957 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -191,19 +191,20 @@ public class FixedDepositAccount extends SavingsAccount { public void updateMaturityDateAndAmountBeforeAccountActivation(final MathContext mc, final boolean isPreMatureClosure, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth) { List allTransactions = new ArrayList<>(); + String refNo = null; final Money transactionAmountMoney = Money.of(getCurrency(), this.accountTermAndPreClosure.depositAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, - this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date(), null); // TODO: - // verify - // if - // it - // is - // ok - // to - // pass - // null - // for - // AppUser + this.accountSubmittedOrActivationDate(), transactionAmountMoney, new Date(), null, refNo); // TODO: + // verify + // if + // it + // is + // ok + // to + // pass + // null + // for + // AppUser transaction.updateRunningBalance(transactionAmountMoney); transaction.updateCumulativeBalanceAndDates(this.getCurrency(), interestCalculatedUpto()); allTransactions.add(transaction); @@ -528,8 +529,10 @@ public class FixedDepositAccount extends SavingsAccount { final boolean isInterestTransfer = false; final LocalDate postInterestOnDate = null; final boolean backdatedTxnsAllowedTill = false; + boolean postReversals = false; final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, + postReversals); Money interestPostedToDate = Money.zero(this.currency); @@ -568,7 +571,7 @@ public class FixedDepositAccount extends SavingsAccount { if (recalucateDailyBalanceDetails) { // update existing transactions so derived balance fields are // correct. - recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill); + recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill, postReversals); } this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); @@ -597,11 +600,11 @@ public class FixedDepositAccount extends SavingsAccount { } recalucateDailyBalance = applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill); - + boolean postReversals = false; if (recalucateDailyBalance) { // update existing transactions so derived balance fields are // correct. - recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill); + recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill, postReversals); } this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); this.accountTermAndPreClosure.updateMaturityDetails(this.getAccountBalance(), accountCloseDate); @@ -651,10 +654,10 @@ public class FixedDepositAccount extends SavingsAccount { @Override public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill) { + final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill, postReversals); } private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java index 6d87fee26..cd28932d4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java @@ -382,6 +382,7 @@ public class RecurringDepositAccount extends SavingsAccount { latestTransactionDate = installment.getTransactionLocalDate(); } } + String refNo = null; if (generateFutureTransactions) { for (RecurringDepositScheduleInstallment installment : depositScheduleInstallments()) { if (installment.isPrincipalNotCompleted(getCurrency())) { @@ -391,7 +392,7 @@ public class RecurringDepositAccount extends SavingsAccount { } final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(null, office(), null, dueDate, installment.getDepositAmountOutstanding(getCurrency()), - Date.from(installment.dueDate().atStartOfDay(ZoneId.systemDefault()).toInstant()), null); + Date.from(installment.dueDate().atStartOfDay(ZoneId.systemDefault()).toInstant()), null, refNo); allTransactions.add(transaction); } } @@ -542,14 +543,16 @@ public class RecurringDepositAccount extends SavingsAccount { protected void processAccountUponActivation(final DateTimeFormatter fmt, final AppUser user) { final Money minRequiredOpeningBalance = Money.of(this.currency, this.minRequiredOpeningBalance); final boolean backdatedTxnsAllowedTill = false; + String refNo = null; if (minRequiredOpeningBalance.isGreaterThanZero()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, getActivationLocalDate(), minRequiredOpeningBalance.getAmount(), null, new Date(), user, accountType); - deposit(transactionDTO, backdatedTxnsAllowedTill); + deposit(transactionDTO, backdatedTxnsAllowedTill, refNo); // update existing transactions so derived balance fields are // correct. - recalculateDailyBalances(Money.zero(this.currency), DateUtils.getBusinessLocalDate(), backdatedTxnsAllowedTill); + boolean postReversals = false; + recalculateDailyBalances(Money.zero(this.currency), DateUtils.getBusinessLocalDate(), backdatedTxnsAllowedTill, postReversals); } } @@ -644,8 +647,10 @@ public class RecurringDepositAccount extends SavingsAccount { boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final boolean backdatedTxnsAllowedTill = false; + boolean postReversals = false; final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate.minusDays(1), isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, + postReversals); Money interestPostedToDate = Money.zero(this.currency); @@ -681,7 +686,7 @@ public class RecurringDepositAccount extends SavingsAccount { if (recalucateDailyBalanceDetails) { // update existing transactions so derived balance fields are // correct. - recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill); + recalculateDailyBalances(Money.zero(this.currency), interestPostingUpToDate, backdatedTxnsAllowedTill, postReversals); } this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); } @@ -710,11 +715,11 @@ public class RecurringDepositAccount extends SavingsAccount { } applyWithholdTaxForDepositAccounts(accountCloseDate, recalucateDailyBalance, backdatedTxnsAllowedTill); - + boolean postReversals = false; if (recalucateDailyBalance) { // update existing transactions so derived balance fields are // correct. - recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill); + recalculateDailyBalances(Money.zero(this.currency), accountCloseDate, backdatedTxnsAllowedTill, postReversals); } this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); @@ -764,10 +769,10 @@ public class RecurringDepositAccount extends SavingsAccount { @Override public List calculateInterestUsing(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill) { + final LocalDate postAsInterestOn, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); return super.calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postAsInterestOn, backdatedTxnsAllowedTill, postReversals); } private LocalDate interestPostingUpToDate(final LocalDate interestPostingDate) { @@ -839,7 +844,8 @@ public class RecurringDepositAccount extends SavingsAccount { } @Override - public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill) { + public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill, + final String refNo) { if (isAccountMatured()) { final String defaultUserMessage = "Transaction is not allowed. Account is matured."; @@ -877,7 +883,7 @@ public class RecurringDepositAccount extends SavingsAccount { throw new PlatformApiDataValidationException(dataValidationErrors); } - final SavingsAccountTransaction transaction = super.deposit(transactionDTO, backdatedTxnsAllowedTill); + final SavingsAccountTransaction transaction = super.deposit(transactionDTO, backdatedTxnsAllowedTill, refNo); return transaction; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 625f998f3..864840876 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -514,7 +514,8 @@ public class SavingsAccount extends AbstractPersistableCustom { final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, + postReversals); Money interestPostedToDate = Money.zero(this.currency); @@ -628,7 +629,7 @@ public class SavingsAccount extends AbstractPersistableCustom { // update existing transactions so derived balance fields are // correct. - recalculateDailyBalances(openingAccountBalance, interestPostingUpToDate, backdatedTxnsAllowedTill); + recalculateDailyBalances(openingAccountBalance, interestPostingUpToDate, backdatedTxnsAllowedTill, postReversals); } if (!backdatedTxnsAllowedTill) { @@ -654,7 +655,7 @@ public class SavingsAccount extends AbstractPersistableCustom { final List withholdTransactions = new ArrayList<>(); List trans = getSavingsAccountTransactionsWithPivotConfig(); for (final SavingsAccountTransaction transaction : trans) { - if (transaction.isWithHoldTaxAndNotReversed()) { + if (transaction.isWithHoldTaxAndNotReversed() && !transaction.isReversalTransaction()) { withholdTransactions.add(transaction); } } @@ -670,7 +671,7 @@ public class SavingsAccount extends AbstractPersistableCustom { List trans = getTransactions(); for (final SavingsAccountTransaction transaction : trans) { if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed()) - && transaction.occursOn(postingDate)) { + && transaction.occursOn(postingDate) && !transaction.isReversalTransaction()) { postingTransation = transaction; break; } @@ -683,7 +684,7 @@ public class SavingsAccount extends AbstractPersistableCustom { List trans = getSavingsAccountTransactionsWithPivotConfig(); for (final SavingsAccountTransaction transaction : trans) { if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed()) - && transaction.occursOn(postingDate)) { + && transaction.occursOn(postingDate) && !transaction.isReversalTransaction()) { postingTransation = transaction; break; } @@ -810,7 +811,7 @@ public class SavingsAccount extends AbstractPersistableCustom { public List calculateInterestUsing(final MathContext mc, final LocalDate upToInterestCalculationDate, boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) { + final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { // no openingBalance concept supported yet but probably will to allow // for migrations. @@ -825,7 +826,8 @@ public class SavingsAccount extends AbstractPersistableCustom { // update existing transactions so derived balance fields are // correct. - recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate, backdatedTxnsAllowedTill); + + recalculateDailyBalances(openingAccountBalance, upToInterestCalculationDate, backdatedTxnsAllowedTill, postReversals); // 1. default to calculate interest based on entire history OR // 2. determine latest 'posting period' and find interest credited to @@ -931,7 +933,7 @@ public class SavingsAccount extends AbstractPersistableCustom { for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { if (!(transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed()) - && transaction.isNotReversed()) { + && transaction.isNotReversed() && !transaction.isReversalTransaction()) { orderedNonInterestPostingTransactions.add(transaction); } } @@ -946,7 +948,7 @@ public class SavingsAccount extends AbstractPersistableCustom { for (final SavingsAccountTransaction transaction : listOfTransactionsSorted) { if (!(transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed()) - && transaction.isNotReversed()) { + && transaction.isNotReversed() && !transaction.isReversalTransaction()) { orderedNonInterestPostingTransactions.add(transaction); } } @@ -972,7 +974,7 @@ public class SavingsAccount extends AbstractPersistableCustom { } protected void recalculateDailyBalances(final Money openingAccountBalance, final LocalDate interestPostingUpToDate, - final boolean backdatedTxnsAllowedTill) { + final boolean backdatedTxnsAllowedTill, boolean postReversals) { Money runningBalance = openingAccountBalance.copy(); @@ -1026,15 +1028,25 @@ public class SavingsAccount extends AbstractPersistableCustom { .add(SavingsAccountChargePaidBy.instance(accountTransaction, x.getSavingsAccountCharge(), x.getAmount()))); accountTransaction.getSavingsAccountChargesPaid().addAll(newChargePaidBy); } + SavingsAccountTransaction reversal = null; transaction.reverse(); + if (postReversals) { + reversal = SavingsAccountTransaction.reversal(transaction); + } if (overdraftAmount.isGreaterThanZero()) { accountTransaction.updateOverdraftAmount(overdraftAmount.getAmount()); } // accountTransaction.updateRunningBalance(runningBalance); if (backdatedTxnsAllowedTill) { addTransactionToExisting(accountTransaction); + if (reversal != null) { + addTransactionToExisting(reversal); + } } else { addTransaction(accountTransaction); + if (reversal != null) { + addTransaction(reversal); + } } isTransactionsModified = true; } @@ -1058,7 +1070,7 @@ public class SavingsAccount extends AbstractPersistableCustom { LocalDate endOfBalanceDate = interestPostingUpToDate; for (int i = accountTransactionsSorted.size() - 1; i >= 0; i--) { final SavingsAccountTransaction transaction = accountTransactionsSorted.get(i); - if (transaction.isNotReversed() + if (transaction.isNotReversed() && !transaction.isReversalTransaction() && !(transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed())) { transaction.updateCumulativeBalanceAndDates(this.currency, endOfBalanceDate); // this transactions transaction date is end of balance date for @@ -1068,17 +1080,19 @@ public class SavingsAccount extends AbstractPersistableCustom { } } - public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill) { - return deposit(transactionDTO, SavingsAccountTransactionType.DEPOSIT, backdatedTxnsAllowedTill); + public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill, + final String refNo) { + return deposit(transactionDTO, SavingsAccountTransactionType.DEPOSIT, backdatedTxnsAllowedTill, refNo); } public SavingsAccountTransaction dividendPayout(final SavingsAccountTransactionDTO transactionDTO, final boolean backdatedTxnsAllowedTill) { - return deposit(transactionDTO, SavingsAccountTransactionType.DIVIDEND_PAYOUT, backdatedTxnsAllowedTill); + String refNo = null; + return deposit(transactionDTO, SavingsAccountTransactionType.DIVIDEND_PAYOUT, backdatedTxnsAllowedTill, refNo); } public SavingsAccountTransaction deposit(final SavingsAccountTransactionDTO transactionDTO, - final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill) { + final SavingsAccountTransactionType savingsAccountTransactionType, final boolean backdatedTxnsAllowedTill, final String refNo) { final String resourceTypeName = depositAccountType().resourceName(); if (isNotActive()) { final String defaultUserMessage = "Transaction is not allowed. Account is not active."; @@ -1122,7 +1136,7 @@ public class SavingsAccount extends AbstractPersistableCustom { final SavingsAccountTransaction transaction = SavingsAccountTransaction.deposit(this, office(), transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), amount, transactionDTO.getCreatedDate(), transactionDTO.getAppUser(), - savingsAccountTransactionType); + savingsAccountTransactionType, refNo); if (backdatedTxnsAllowedTill) { addTransactionToExisting(transaction); @@ -1251,6 +1265,10 @@ public class SavingsAccount extends AbstractPersistableCustom { || this.sub_status.equals(SavingsAccountSubStatusEnum.DORMANT.getValue())) { this.sub_status = SavingsAccountSubStatusEnum.NONE.getValue(); } + if (backdatedTxnsAllowedTill) { + this.summary.updateSummaryWithPivotConfig(this.currency, this.savingsAccountTransactionSummaryWrapper, transaction, + this.savingsAccountTransactions); + } return transaction; } @@ -1375,7 +1393,7 @@ public class SavingsAccount extends AbstractPersistableCustom { if (!backdatedTxnsAllowedTill) { for (final SavingsAccountTransaction transaction : retreiveListOfTransactions()) { if ((transaction.isInterestPostingAndNotReversed() || transaction.isOverdraftInterestAndNotReversed()) - && transaction.isAfter(transactionDate)) { + && transaction.isAfter(transactionDate) && !transaction.isReversalTransaction()) { transactionBeforeLastInterestPosting = true; break; } @@ -2760,15 +2778,15 @@ public class SavingsAccount extends AbstractPersistableCustom { final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; LocalDate postInterestAsOnDate = null; + boolean postReversals = false; if (this.isBeforeLastPostingPeriod(getActivationLocalDate(), backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - boolean postReversals = false; this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestAsOnDate, backdatedTxnsAllowedTill, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); this.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestAsOnDate, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestAsOnDate, backdatedTxnsAllowedTill, postReversals); } } } @@ -3513,7 +3531,8 @@ public class SavingsAccount extends AbstractPersistableCustom { refNo.toString()); } } - recalculateDailyBalances(Money.zero(this.currency), transactionDate, backdatedTxnsAllowedTill); + boolean postReversals = false; + recalculateDailyBalances(Money.zero(this.currency), transactionDate, backdatedTxnsAllowedTill, postReversals); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); } @@ -3532,7 +3551,8 @@ public class SavingsAccount extends AbstractPersistableCustom { SavingsAccountTransaction transaction = SavingsAccountTransaction.escheat(this, transactionDate, appUser, postInterestAsOnDate); this.transactions.add(transaction); } - recalculateDailyBalances(Money.zero(this.currency), transactionDate, false); + boolean postReversals = false; + recalculateDailyBalances(Money.zero(this.currency), transactionDate, false, postReversals); this.summary.updateSummary(this.currency, this.savingsAccountTransactionSummaryWrapper, this.transactions); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java index de98a78e3..f5418f3a9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java @@ -361,6 +361,11 @@ public class SavingsAccountAssembler { // Update transient variable account.setSavingsAccountTransactions(savingsAccountTransactions); } + // Update last running balance on account level + if (savingsAccountTransactions != null && !savingsAccountTransactions.isEmpty()) { + account.getSummary().setRunningBalanceOnPivotDate(savingsAccountTransactions.get(savingsAccountTransactions.size() - 1) + .getRunningBalance(account.getCurrency()).getAmount()); + } } else { savingsAccountTransactions = this.savingsAccountRepository.findAllTransactions(account); account.setSavingsAccountTransactions(savingsAccountTransactions); @@ -368,10 +373,11 @@ public class SavingsAccountAssembler { } // Update last running balance on account level - if (savingsAccountTransactions != null) { - account.getSummary().setRunningBalanceOnPivotDate(savingsAccountTransactions.get(savingsAccountTransactions.size() - 1) - .getRunningBalance(account.getCurrency()).getAmount()); - } + // if (savingsAccountTransactions != null && !savingsAccountTransactions.isEmpty()) { + // account.getSummary().setRunningBalanceOnPivotDate(savingsAccountTransactions.get(savingsAccountTransactions.size() + // - 1) + // .getRunningBalance(account.getCurrency()).getAmount()); + // } account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); return account; @@ -380,7 +386,8 @@ public class SavingsAccountAssembler { public SavingsAccountData assembleSavings(final SavingsAccountData account) { // Update last running balance on account level - if (account.getTransactions() != null && account.getTransactions().size() != 0) { + if (account.getTransactions() != null && account.getTransactions().size() != 0 + && account.getSummary().getInterestPostedTillDate() != null) { account.getSummary().setRunningBalanceOnPivotDate(account.getTransactions().get(account.getTransactions().size() - 1) .getRunningBalance(account.getCurrency()).getAmount()); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index 29dbc4ce7..142605ee1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -115,13 +115,14 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final MathContext mc = MathContext.DECIMAL64; final LocalDate today = DateUtils.getBusinessLocalDate(); + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { - boolean postReversals = false; account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } else { account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(), - isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, + postReversals); } List depositAccountOnHoldTransactions = null; @@ -191,18 +192,20 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi Integer accountType = null; final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, new Date(), user, accountType); - final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType, backdatedTxnsAllowedTill); + UUID refNo = UUID.randomUUID(); + final SavingsAccountTransaction deposit = account.deposit(transactionDTO, savingsAccountTransactionType, backdatedTxnsAllowedTill, + refNo.toString()); final LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; final LocalDate today = DateUtils.getBusinessLocalDate(); + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { - boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } saveTransactionToGenerateTransactionId(deposit); @@ -316,20 +319,22 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi final LocalDate today = DateUtils.getBusinessLocalDate(); final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) { + boolean postReversals = true; if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), backdatedTxnsAllowedTill)) { - boolean postReversals = true; + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } account.validateAccountBalanceDoesNotBecomeNegativeMinimal(savingsAccountTransaction.getAmount(), false); account.activateAccountBasedOnBalance(); } this.savingsAccountRepository.save(account); + newTransactions.addAll(account.getSavingsAccountTransactionsWithPivotConfig()); this.savingsAccountTransactionRepository.saveAll(newTransactions); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false, backdatedTxnsAllowedTill); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java index 82d105de1..a234186b6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java @@ -113,7 +113,7 @@ public final class SavingsAccountSummary { this.totalWithholdTax = wrapper.calculateTotalWithholdTaxWithdrawal(currency, transactions); // boolean isUpdated = false; - updateRunningBalanceAndPivotDate(false, transactions, null, null, currency); + updateRunningBalanceAndPivotDate(false, transactions, null, null, null, currency); this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge) @@ -189,30 +189,53 @@ public final class SavingsAccountSummary { } } else { // boolean isUpdated = false; - Money interestTotal = Money.of(currency, this.totalInterestPosted); - Money withHoldTaxTotal = Money.of(currency, this.totalWithholdTax); - + Money interestTotal = Money.zero(currency); + Money withHoldTaxTotal = Money.zero(currency); + Money overdraftInterestTotal = Money.zero(currency); + this.totalDeposits = wrapper.calculateTotalDeposits(currency, savingsAccountTransactions); + this.totalWithdrawals = wrapper.calculateTotalWithdrawals(currency, savingsAccountTransactions); + this.totalWithdrawalFees = wrapper.calculateTotalWithdrawalFees(currency, savingsAccountTransactions); + this.totalAnnualFees = wrapper.calculateTotalAnnualFees(currency, savingsAccountTransactions); + this.totalFeeCharge = wrapper.calculateTotalFeesCharge(currency, savingsAccountTransactions); + this.totalPenaltyCharge = wrapper.calculateTotalPenaltyCharge(currency, savingsAccountTransactions); + this.totalFeeChargesWaived = wrapper.calculateTotalFeesChargeWaived(currency, savingsAccountTransactions); + this.totalPenaltyChargesWaived = wrapper.calculateTotalPenaltyChargeWaived(currency, savingsAccountTransactions); final HashMap map = updateRunningBalanceAndPivotDate(true, savingsAccountTransactions, interestTotal, - withHoldTaxTotal, currency); + overdraftInterestTotal, withHoldTaxTotal, currency); interestTotal = map.get("interestTotal"); withHoldTaxTotal = map.get("withHoldTax"); + overdraftInterestTotal = map.get("overdraftInterestTotal"); this.totalInterestPosted = interestTotal.getAmountDefaultedToNullIfZero(); + this.totalOverdraftInterestDerived = overdraftInterestTotal.getAmountDefaultedToNullIfZero(); this.totalWithholdTax = withHoldTaxTotal.getAmountDefaultedToNullIfZero(); - this.accountBalance = Money.of(currency, this.accountBalance).plus(this.totalInterestPosted).minus(this.totalWithholdTax) - .getAmount(); + this.accountBalance = Money.of(currency, this.totalDeposits).plus(this.totalInterestPosted).minus(this.totalWithdrawals) + .minus(this.totalWithdrawalFees).minus(this.totalAnnualFees).minus(this.totalFeeCharge).minus(this.totalPenaltyCharge) + .minus(this.totalOverdraftInterestDerived).minus(totalWithholdTax).getAmount(); + // this.accountBalance = Money.of(currency, + // this.accountBalance).plus(this.totalInterestPosted).minus(this.totalWithholdTax) + // .getAmount(); } } @SuppressWarnings("unchecked") private HashMap updateRunningBalanceAndPivotDate(final boolean backdatedTxnsAllowedTill, - final List savingsAccountTransactions, Money interestTotal, Money withHoldTaxTotal, - MonetaryCurrency currency) { + final List savingsAccountTransactions, Money interestTotal, Money overdraftInterestTotal, + Money withHoldTaxTotal, MonetaryCurrency currency) { boolean isUpdated = false; HashMap map = new HashMap<>(); for (int i = savingsAccountTransactions.size() - 1; i >= 0; i--) { final SavingsAccountTransaction savingsAccountTransaction = savingsAccountTransactions.get(i); - if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed() - && !savingsAccountTransaction.isReversalTransaction() && !isUpdated) { + if (savingsAccountTransaction.isInterestPostingAndNotReversed() && !savingsAccountTransaction.isReversalTransaction() + && !isUpdated) { + setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance(currency).getAmount()); + setInterestPostedTillDate(savingsAccountTransaction.getLastTransactionDate()); + isUpdated = true; + if (!backdatedTxnsAllowedTill) { + break; + } + } + if (savingsAccountTransaction.isOverdraftInterestAndNotReversed() && !savingsAccountTransaction.isReversalTransaction() + && !isUpdated) { setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance(currency).getAmount()); setInterestPostedTillDate(savingsAccountTransaction.getLastTransactionDate()); isUpdated = true; @@ -225,8 +248,10 @@ public final class SavingsAccountSummary { && !savingsAccountTransaction.isReversalTransaction()) { interestTotal = interestTotal.plus(savingsAccountTransaction.getAmount(currency)); } - - if (savingsAccountTransaction.isWithHoldTaxAndNotReversed()) { + if (savingsAccountTransaction.isOverdraftInterestAndNotReversed() && !savingsAccountTransaction.isReversalTransaction()) { + overdraftInterestTotal = overdraftInterestTotal.plus(savingsAccountTransaction.getAmount()); + } + if (savingsAccountTransaction.isWithHoldTaxAndNotReversed() && !savingsAccountTransaction.isReversalTransaction()) { withHoldTaxTotal = withHoldTaxTotal.plus(savingsAccountTransaction.getAmount(currency)); } } @@ -234,6 +259,7 @@ public final class SavingsAccountSummary { if (backdatedTxnsAllowedTill) { map.put("interestTotal", interestTotal); map.put("withHoldTax", withHoldTaxTotal); + map.put("overdraftInterestTotal", overdraftInterestTotal); } return map; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java index 3362d4ebe..861aa465a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java @@ -153,22 +153,21 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { } public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) { + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser, + final String refNo) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; - final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.DEPOSIT.getValue(), date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser, - final SavingsAccountTransactionType savingsAccountTransactionType) { + final SavingsAccountTransactionType savingsAccountTransactionType, final String refNo) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; - final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, savingsAccountTransactionType.getValue(), date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index c7667bb1c..353070900 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -239,6 +239,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo } final boolean isInterestTransfer = false; final LocalDate postInterestOnDate = null; + final boolean postReversals = false; if (activationChargeAmount.isGreaterThanZero()) { payActivationCharge(account, user); } @@ -249,7 +250,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); @@ -366,6 +367,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo account.updateOverduePayments(overdueUptoDate); final boolean isInterestTransfer = false; final LocalDate postInterestOnDate = null; + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(account.getActivationLocalDate(), false)) { final LocalDate today = DateUtils.getBusinessLocalDate(); account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, @@ -373,7 +375,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { @@ -521,11 +523,12 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo checkClientOrGroupActive(account); final LocalDate today = DateUtils.getBusinessLocalDate(); + boolean postReversals = false; final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); this.savingAccountRepositoryWrapper.save(account); @@ -643,13 +646,14 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; checkClientOrGroupActive(account); + boolean postReversals = false; if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { @@ -746,7 +750,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo if (savingsAccountTransaction.isDeposit()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, savingsAccountTransaction.createdDate(), user, accountType); - transaction = account.deposit(transactionDTO, false); + transaction = account.deposit(transactionDTO, false, refNo.toString()); } else { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, savingsAccountTransaction.createdDate(), user, accountType); @@ -755,13 +759,14 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); boolean isInterestTransfer = false; final LocalDate postInterestOnDate = null; + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, false) || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { @@ -983,8 +988,9 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo savingsAccount.setStatus(SavingsAccountStatusType.TRANSFER_IN_PROGRESS.getValue()); final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; + boolean postReversals = false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); this.savingsAccountTransactionRepository.save(newTransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -1014,11 +1020,12 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo savingsAccount.office(), transferDate, user); savingsAccount.addTransaction(withdrawtransferTransaction); savingsAccount.setStatus(SavingsAccountStatusType.ACTIVE.getValue()); + boolean postReversals = false; final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); this.savingsAccountTransactionRepository.save(withdrawtransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -1059,10 +1066,11 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo savingsAccount.reassignSavingsOfficer(fieldOfficer, transferDate); } boolean isInterestTransfer = false; + boolean postReversals = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); this.savingsAccountTransactionRepository.save(acceptTransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -1215,15 +1223,15 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), false)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { @@ -1355,15 +1363,15 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, false)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java index 74a19d350..eed7393bb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountInterestPostingServiceImpl.java @@ -422,11 +422,10 @@ public class SavingsAccountInterestPostingServiceImpl implements SavingsAccountI Money runningBalance = openingAccountBalance.copy(); List accountTransactionsSorted = retreiveListOfTransactions(savingsAccountData); - boolean isTransactionsModified = false; for (final SavingsAccountTransactionData transaction : accountTransactionsSorted) { - if (transaction.isReversed()) { + if (transaction.isReversed() || transaction.isReversalTransaction()) { transaction.zeroBalanceFields(); } else { Money overdraftAmount = Money.zero(savingsAccountData.currency()); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 6f9d473fc..d22c85534 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -515,9 +515,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); boolean isInterestTransfer = false; final LocalDate postInterestOnDate = null; - + boolean postReversals = false; account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); if (backdatedTxnsAllowedTill) { this.savingsAccountTransactionRepository.saveAll(account.getSavingsAccountTransactionsWithPivotConfig()); @@ -787,15 +787,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi } boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; + boolean postReversals = false; checkClientOrGroupActive(account); if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { - boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { @@ -882,22 +882,22 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi Integer accountType = null; final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, savingsAccountTransaction.createdDate(), user, accountType); + UUID refNo = UUID.randomUUID(); if (savingsAccountTransaction.isDeposit()) { - transaction = account.deposit(transactionDTO, false); + transaction = account.deposit(transactionDTO, false, refNo.toString()); } else { - UUID refNo = UUID.randomUUID(); transaction = account.withdraw(transactionDTO, true, false, refNo.toString()); } final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); final LocalDate postInterestOnDate = null; + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, false) || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { - boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { @@ -1079,8 +1079,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; + boolean postReversals = false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); this.savingsAccountTransactionRepository.save(newTransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -1110,8 +1111,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final MathContext mc = MathContext.DECIMAL64; boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; + boolean postReversals = false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); this.savingsAccountTransactionRepository.save(withdrawtransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -1152,8 +1154,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi boolean isInterestTransfer = false; final MathContext mc = MathContext.DECIMAL64; LocalDate postInterestOnDate = null; + boolean postReversals = false; savingsAccount.calculateInterestUsing(mc, transferDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, false); + financialYearBeginningMonth, postInterestOnDate, false, postReversals); this.savingsAccountTransactionRepository.save(acceptTransferTransaction); this.savingAccountRepositoryWrapper.saveAndFlush(savingsAccount); @@ -1322,15 +1325,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } List depositAccountOnHoldTransactions = null; @@ -1475,15 +1478,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; + boolean postReversals = false; if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getBusinessLocalDate(); - boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, isInterestTransfer, postReversals); } else { final LocalDate today = DateUtils.getBusinessLocalDate(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } List depositAccountOnHoldTransactions = null; if (account.getOnHoldFunds().compareTo(BigDecimal.ZERO) > 0) { 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 fda525233..57c6797d6 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 @@ -253,14 +253,20 @@ public class SavingsSchedularInterestPoster implements Callable { List transRefNo = new ArrayList<>(); for (SavingsAccountData savingsAccountData : savingsAccountDataList) { SavingsAccountSummaryData savingsAccountSummaryData = savingsAccountData.getSummary(); - paramsForSavingsSummary - .add(new Object[] { savingsAccountSummaryData.getTotalDeposits(), savingsAccountSummaryData.getTotalWithdrawals(), - savingsAccountSummaryData.getTotalInterestEarned(), savingsAccountSummaryData.getTotalInterestPosted(), - savingsAccountSummaryData.getTotalWithdrawalFees(), savingsAccountSummaryData.getTotalFeeCharge(), - savingsAccountSummaryData.getTotalPenaltyCharge(), savingsAccountSummaryData.getTotalAnnualFees(), - savingsAccountSummaryData.getAccountBalance(), savingsAccountSummaryData.getTotalOverdraftInterestDerived(), - savingsAccountSummaryData.getTotalWithholdTax(), savingsAccountSummaryData.getLastInterestCalculationDate(), - savingsAccountSummaryData.getInterestPostedTillDate(), savingsAccountData.getId() }); + paramsForSavingsSummary.add(new Object[] { savingsAccountSummaryData.getTotalDeposits(), + savingsAccountSummaryData.getTotalWithdrawals(), savingsAccountSummaryData.getTotalInterestEarned(), + savingsAccountSummaryData.getTotalInterestPosted(), savingsAccountSummaryData.getTotalWithdrawalFees(), + savingsAccountSummaryData.getTotalFeeCharge(), savingsAccountSummaryData.getTotalPenaltyCharge(), + savingsAccountSummaryData.getTotalAnnualFees(), savingsAccountSummaryData.getAccountBalance(), + savingsAccountSummaryData.getTotalOverdraftInterestDerived(), savingsAccountSummaryData.getTotalWithholdTax(), + Date.from(savingsAccountSummaryData.getLastInterestCalculationDate().atStartOfDay(DateUtils.getDateTimeZoneOfTenant()) + .toInstant()), + savingsAccountSummaryData.getInterestPostedTillDate() != null + ? Date.from(savingsAccountSummaryData.getInterestPostedTillDate() + .atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()) + : Date.from(savingsAccountSummaryData.getLastInterestCalculationDate() + .atStartOfDay(DateUtils.getDateTimeZoneOfTenant()).toInstant()), + savingsAccountData.getId() }); List savingsAccountTransactionDataList = savingsAccountData.getSavingsAccountTransactionData(); for (SavingsAccountTransactionData savingsAccountTransactionData : savingsAccountTransactionDataList) { if (savingsAccountTransactionData.getId() == null) { @@ -287,7 +293,7 @@ public class SavingsSchedularInterestPoster implements Callable { if (transRefNo.size() > 0) { this.jdbcTemplate.batchUpdate(queryForSavingsUpdate, paramsForSavingsSummary); this.jdbcTemplate.batchUpdate(queryForTransactionInsertion, paramsForTransactionInsertion); - + LOG.info("`Total No Of Interest Posting:` {}", transRefNo.size()); List savingsAccountTransactionDataList = fetchTransactionsFromIds(transRefNo); if (savingsAccountDataList != null) { LOG.info("Fetched Transactions from DB: {}", savingsAccountTransactionDataList.size()); 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 7a85e0a88..f0512f740 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 @@ -52,6 +52,7 @@ import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper; import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper; import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker; import org.apache.fineract.portfolio.charge.domain.ChargeTimeType; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -2815,4 +2816,59 @@ public class ClientSavingsIntegrationTest { balance = Float.parseFloat("900.0"); assertEquals(balance, summary.get("accountBalance"), "Verifying account balance is 900"); } + + @Test + public void testAccountBalanceAndTransactionRunningBalanceWithConfigOn() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "38", false); + GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(this.requestSpec, this.responseSpec, "39", true); + GlobalConfigurationHelper.updateValueForGlobalConfiguration(requestSpec, responseSpec, "39", "5"); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final String minBalanceForInterestCalculation = null; + final String minRequiredBalance = "0"; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = true; + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, "0", minBalanceForInterestCalculation, + minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assertions.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsId); + + HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + // withdrawal transaction 1 + Integer withdrawalTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "500", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + Float balance = Float.parseFloat("-500.0"); + assertEquals(balance, summary.get("accountBalance"), "Verifying account balance is -500"); + + // withdrawal transaction 2 + withdrawalTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "500", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + balance = Float.parseFloat("-1000.0"); + assertEquals(balance, summary.get("accountBalance"), "Verifying account balance is -1000"); + + // Check for last transactions running balance + Object transactionObj = this.savingsAccountHelper.getSavingsDetails(savingsId, "transactions"); + ArrayList> transactions = (ArrayList>) transactionObj; + HashMap requestedTransaction = transactions.get(transactions.size() - 2); + balance = Float.parseFloat("-1000.0"); + assertEquals(balance.toString(), requestedTransaction.get("runningBalance").toString(), "Equality check for Balance"); + } + + @AfterEach + public void tearDown() { + GlobalConfigurationHelper.resetAllDefaultGlobalConfigurations(this.requestSpec, this.responseSpec); + GlobalConfigurationHelper.verifyAllDefaultGlobalConfigurations(this.requestSpec, this.responseSpec); + } }