From 539f6cd0f423470f77ed6c4627ab4016c36f7369 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Tue, 13 Oct 2026 11:00:39 +0530 Subject: [PATCH] fix(accounting): allow unbalanced legs when defining opening balances Defining opening balances was rejected by the debit/credit balance check that ordinary journal entries go through. For opening balances that check does not hold: every leg is posted against the opening balance contra account, so the books always balance, and the submitted legs legitimately differ by the opening balance of the locked, denomination-managed cash account, which is already posted and can never be part of the request. - validateBusinessRulesForJournalEntries takes a requireBalanced flag. Ordinary journal entries keep the full check (requireBalanced = true). - defineOpeningBalance passes false: it still needs at least one leg and an account and amount on every leg, but no longer requires debits and credits to be present on both sides or to add up. Verified with :fineract-provider:compileJava. Co-Authored-By: Claude Opus 5.5 --- ...WritePlatformServiceJpaRepositoryImpl.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java index 93f8d9e80..f3e13716e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java @@ -687,6 +687,16 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa } private void validateBusinessRulesForJournalEntries(final JournalEntryCommand command) { + validateBusinessRulesForJournalEntries(command, true); + } + + /** + * @param requireBalanced + * false for opening balances: every leg is posted against the contra account, so the books always + * balance, and the submitted legs legitimately differ by the locked (denomination-managed) cash + * account's opening balance that is already posted and can never be part of this request. + */ + private void validateBusinessRulesForJournalEntries(final JournalEntryCommand command, final boolean requireBalanced) { // check if date of Journal entry is valid final LocalDate transactionDate = command.getTransactionDate(); // shouldn't be in the future @@ -706,6 +716,26 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final SingleDebitOrCreditEntryCommand[] credits = command.getCredits(); final SingleDebitOrCreditEntryCommand[] debits = command.getDebits(); + if (!requireBalanced) { + final List entries = new ArrayList<>(); + if (credits != null) { + entries.addAll(Arrays.asList(credits)); + } + if (debits != null) { + entries.addAll(Arrays.asList(debits)); + } + if (entries.isEmpty()) { + throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.NO_DEBITS_OR_CREDITS, null, null, null); + } + for (final SingleDebitOrCreditEntryCommand entry : entries) { + if (entry.getAmount() == null || entry.getGlAccountId() == null) { + throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null, null, + null); + } + } + return; + } + // atleast one debit or credit must be present if (credits == null || credits.length == 0 || debits == null || debits.length == 0) { throw new JournalEntryInvalidException(GlJournalEntryInvalidReason.NO_DEBITS_OR_CREDITS, null, null, null); @@ -783,7 +813,7 @@ public class JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa final Office office = this.officeRepositoryWrapper.findOneWithNotFoundDetection(officeId); final String currencyCode = command.stringValueOfParameterNamed(JournalEntryJsonInputParams.CURRENCY_CODE.getValue()); - validateBusinessRulesForJournalEntries(journalEntryCommand); + validateBusinessRulesForJournalEntries(journalEntryCommand, false); validateNoDenominationManagedCashAccounts(journalEntryCommand); /**