diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java index 748daf5d0..5092daed6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForSavings.java @@ -173,12 +173,25 @@ public class AccrualBasedAccountingProcessorForSavings implements AccountingProc } else if (savingsTransactionDTO.getTransactionType().isInterestPosting()) { - // Post journal entry if earned interest amount is greater than - // zero - if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) { + // A premature FD closure that reprices interest downward (see + // FixedDepositAccount#postPreMaturityInterest) posts the difference as a NEGATIVE + // interest-posting transaction -- a clawback of interest already recognised in earlier + // periods, not "zero interest, nothing to post". Skipping it here (the original + // "amount > 0" guard) left the FD subledger showing the clawback while the GL never + // recorded it, silently going out of sync. Treating a negative amount as a mirror-image + // reversal -- positive magnitude, debit/credit legs swapped, same mechanism + // createCashBasedJournalEntriesAndReversalsForSavings already uses for isReversal -- keeps + // the two in sync instead. See CashBasedAccountingProcessorForSavings for the same fix on + // the cash-based path. + final int amountSign = savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO); + if (amountSign != 0) { + final boolean isClawback = amountSign < 0; + final BigDecimal postingAmount = isClawback ? amount.abs() : amount; + final boolean effectiveIsReversal = isReversal ^ isClawback; this.helper.createCashBasedJournalEntriesAndReversalsForSavings(office, currencyCode, AccrualAccountsForSavings.INTEREST_PAYABLE.getValue(), AccrualAccountsForSavings.SAVINGS_CONTROL.getValue(), - savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal); + savingsProductId, paymentTypeId, savingsId, transactionId, transactionDate, postingAmount, + effectiveIsReversal); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java index d6a6034e4..1ca1ad249 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForSavings.java @@ -167,15 +167,26 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess } else if (savingsTransactionDTO.getTransactionType().isInterestPosting()) { - // Post journal entry if earned interest amount is greater than - // zero - if (savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO) > 0) { + // A premature FD closure that reprices interest downward (see + // FixedDepositAccount#postPreMaturityInterest) posts the difference as a NEGATIVE + // interest-posting transaction -- a clawback of interest already recognised in earlier + // periods, not "zero interest, nothing to post". Skipping it here (the original + // "amount > 0" guard) left the FD subledger showing the clawback while the GL never + // recorded it, silently going out of sync. Treating a negative amount as a mirror-image + // reversal -- positive magnitude, debit/credit legs swapped, same mechanism + // createInterestPostingJournalEntriesForSavings already uses for isReversal -- keeps the two + // in sync instead. + final int amountSign = savingsTransactionDTO.getAmount().compareTo(BigDecimal.ZERO); + if (amountSign != 0) { + final boolean isClawback = amountSign < 0; + final BigDecimal postingAmount = isClawback ? amount.abs() : amount; + final boolean effectiveIsReversal = isReversal ^ isClawback; // Routed through the payable and paid accounts the product names, so the society // can read interest declared but not yet credited as its own balance. Collapses // to the plain Dr Interest On Savings / Cr Savings Control pair for a product // that names neither. this.helper.createInterestPostingJournalEntriesForSavings(office, currencyCode, savingsProductId, paymentTypeId, - savingsId, transactionId, transactionDate, amount, isReversal); + savingsId, transactionId, transactionDate, postingAmount, effectiveIsReversal); } }