From 2213308254199e67fdede34ac5b72ccb2be01e1b Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Thu, 6 Aug 2026 20:10:57 +0530 Subject: [PATCH] fix(shares): skip zero-value redeem transaction on account close MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closing a share account that already has zero approved shares no longer synthesizes a redeem transaction, so it stops posting a meaningless ₹0.00/₹0.00 GL journal-entry pair. Save/note-capture on close now happen unconditionally instead of being gated on a transaction existing. Co-Authored-By: Claude Sonnet 5 --- .../ShareAccountDataSerializer.java | 18 ++++++++----- ...WritePlatformServiceJpaRepositoryImpl.java | 26 ++++++++++--------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java index 14a327cc8..0e257bcf5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/serialization/ShareAccountDataSerializer.java @@ -1139,13 +1139,19 @@ public class ShareAccountDataSerializer { } AppUser approvedUser = this.platformSecurityContext.authenticatedUser(); - final BigDecimal unitPrice = account.getShareProduct().deriveMarketPrice(DateUtils.getBusinessLocalDate()); - ShareAccountTransaction transaction = ShareAccountTransaction.createRedeemTransaction(closedDate, account.getTotalApprovedShares(), - unitPrice); - account.addAdditionalPurchasedShares(transaction); + // Only synthesize a redeem transaction (and its GL posting) when shares actually remain to + // be redeemed — an account closed after every share was already withdrawn has nothing left + // to force-redeem, and posting a zero-share/zero-value transaction just clutters the GL with + // a meaningless ₹0.00/₹0.00 entry pair. + final Long remainingShares = account.getTotalApprovedShares(); + if (remainingShares != null && remainingShares > 0) { + final BigDecimal unitPrice = account.getShareProduct().deriveMarketPrice(DateUtils.getBusinessLocalDate()); + ShareAccountTransaction transaction = ShareAccountTransaction.createRedeemTransaction(closedDate, remainingShares, unitPrice); + account.addAdditionalPurchasedShares(transaction); + handleRedeemSharesChargeTransactions(account, transaction); + actualChanges.put(ShareAccountApiConstants.requestedshares_paramname, transaction); + } account.close(closedDate, approvedUser); - handleRedeemSharesChargeTransactions(account, transaction); - actualChanges.put(ShareAccountApiConstants.requestedshares_paramname, transaction); return actualChanges; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java index cc6664731..6377c494e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/shareaccounts/service/ShareAccountWritePlatformServiceJpaRepositoryImpl.java @@ -484,23 +484,25 @@ public class ShareAccountWritePlatformServiceJpaRepositoryImpl implements ShareA try { ShareAccount account = this.shareAccountRepository.findOneWithNotFoundDetection(accountId); Map changes = this.accountDataSerializer.validateAndClose(jsonCommand, account); - if (!changes.isEmpty()) { - this.shareAccountRepository.saveAndFlush(account); - final String noteText = jsonCommand.stringValueOfParameterNamed("note"); - if (StringUtils.isNotBlank(noteText)) { - final Note note = Note.shareNote(account, noteText); - changes.put("note", noteText); - this.noteRepository.save(note); - } - ShareAccountTransaction transaction = (ShareAccountTransaction) changes - .get(ShareAccountApiConstants.requestedshares_paramname); - transaction = account.getShareAccountTransaction(transaction); + // Account status/closedDate are applied regardless of whether there was anything left to + // redeem — save and note-capture must not be gated on a transaction actually existing. + this.shareAccountRepository.saveAndFlush(account); + final String noteText = jsonCommand.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + final Note note = Note.shareNote(account, noteText); + this.noteRepository.save(note); + } + // Only closures that actually redeemed remaining shares carry a transaction to post GL + // entries for — an account already at zero shares has none, and posting a zero-value + // transaction would just add a meaningless ₹0.00/₹0.00 entry pair to the GL. + final Object transactionChange = changes.get(ShareAccountApiConstants.requestedshares_paramname); + if (transactionChange instanceof ShareAccountTransaction) { + ShareAccountTransaction transaction = account.getShareAccountTransaction((ShareAccountTransaction) transactionChange); Set transactions = new HashSet<>(); transactions.add(transaction); this.journalEntryWritePlatformService.createJournalEntriesForShares(populateJournalEntries(account, transactions)); changes.clear(); changes.put(ShareAccountApiConstants.requestedshares_paramname, transaction.getId()); - } return new CommandProcessingResultBuilder() // .withCommandId(jsonCommand.commandId()) //