fix(chequebook,current-account): current-account transaction fixes for cheque-book charge

- CurrentAccountTransactionsApiResource.retrieveOne() was calling the savings
  read-service to fetch a single transaction, which always queries
  m_savings_account_transaction and can never find a current-account row —
  crashed with EmptyResultDataAccessException. Added a dedicated
  CurrentAccountReadPlatformService.retrieveOneTransaction() backed by
  m_current_account_transaction instead.
- CurrentAccountReadPlatformServiceImpl.buildTransactionType() (and the
  search-transactions endpoint) had no cases for hold (8) / release (9)
  transaction types, so they displayed as "Deposit"/"Other" instead of
  "Amount on hold"/"Release Amount".
- ChequeBookIssuanceChargeService.deductFromCurrent() never set
  runningBalanceDerived on the charge-deduction transaction, leaving the
  Balance column blank for that row in the transactions list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-07-16 15:06:35 +05:30
co-authored by Claude Sonnet 5
parent fde670e644
commit 5ed95c27e8
4 changed files with 62 additions and 46 deletions
@@ -26,6 +26,8 @@ public interface CurrentAccountReadPlatformService {
Map<String, Object> retrieveOne(Long accountId);
Map<String, Object> retrieveOneTransaction(Long accountId, Long transactionId);
List<Map<String, Object>> retrieveAll();
List<Map<String, Object>> retrieveAll(Integer offset, Integer limit, String accountNo, String clientName);
@@ -274,6 +274,7 @@ public class ChequeBookIssuanceChargeService {
final BigDecimal newBalance = safeBalance(account.getAccountBalanceDerived()).subtract(chargeAmount);
final CurrentAccountTransaction txn = CurrentAccountTransaction.chargeDeduction(account, office, chargeAmount, today);
txn.setRunningBalanceDerived(newBalance);
this.currentAccountTransactionRepository.saveAndFlush(txn);
account.setAccountBalanceDerived(newBalance);
this.currentAccountRepository.saveAndFlush(account);
@@ -43,19 +43,16 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService;
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@@ -70,8 +67,7 @@ public class CurrentAccountTransactionsApiResource {
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<SavingsAccountTransactionData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
private final CurrentAccountReadPlatformService currentAccountReadPlatformService;
private final PaymentTypeReadService paymentTypeReadService;
private final JdbcTemplate jdbcTemplate;
@@ -128,11 +124,11 @@ public class CurrentAccountTransactionsApiResource {
@PathParam("transactionId") final Long transactionId, @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
final SavingsAccountTransactionData transactionData = savingsAccountReadPlatformService.retrieveSavingsTransaction(accountId,
transactionId, DepositAccountType.CURRENT_DEPOSIT);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, transactionData,
SavingsApiSetConstants.SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS);
// current accounts live in their own table (m_current_account_transaction), not m_savings_account_transaction —
// the savings read-service's retrieveSavingsTransaction() can never find a row here regardless of
// DepositAccountType, since it always queries the savings schema.
final Map<String, Object> transactionData = currentAccountReadPlatformService.retrieveOneTransaction(accountId, transactionId);
return GSON.toJson(transactionData);
}
@POST
@@ -252,8 +248,11 @@ public class CurrentAccountTransactionsApiResource {
txnType.put("id", typeEnum);
txnType.put("deposit", typeEnum == 1);
txnType.put("withdrawal", typeEnum == 2);
txnType.put("amountHold", typeEnum == 8);
txnType.put("amountRelease", typeEnum == 9);
txnType.put("value", typeEnum == 1 ? "Deposit" : typeEnum == 2 ? "Withdrawal"
: typeEnum == 7 ? "Charge Deduction" : "Other");
: typeEnum == 7 ? "Charge Deduction" : typeEnum == 8 ? "Amount on hold"
: typeEnum == 9 ? "Release Amount" : "Other");
row.put("transactionType", txnType);
final java.sql.Date d = rs.getDate("transaction_date");
@@ -113,44 +113,56 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
}, accountId);
}
private static final String TRANSACTION_SELECT =
"SELECT t.id, t.transaction_type_enum, t.transaction_date, t.amount, "
+ "t.running_balance_derived, t.is_reversed, t.is_reversal, t.ref_no, t.remarks, "
+ "t.submitted_on_date "
+ "FROM m_current_account_transaction t ";
private List<Map<String, Object>> retrieveTransactions(Long accountId) {
final String sql =
"SELECT t.id, t.transaction_type_enum, t.transaction_date, t.amount, "
+ "t.running_balance_derived, t.is_reversed, t.is_reversal, t.ref_no, t.remarks, "
+ "t.submitted_on_date "
+ "FROM m_current_account_transaction t "
+ "WHERE t.current_account_id = ? "
+ "ORDER BY t.transaction_date ASC, t.id ASC";
final String sql = TRANSACTION_SELECT + "WHERE t.current_account_id = ? ORDER BY t.transaction_date ASC, t.id ASC";
return jdbcTemplate.query(sql, (rs, rowNum) -> mapTransactionRow(rs), accountId);
}
return jdbcTemplate.query(sql, (rs, rowNum) -> {
final Map<String, Object> t = new LinkedHashMap<>();
t.put("id", rs.getLong("id"));
/** Backs {@code GET /currentaccounts/{accountId}/transactions/{transactionId}} — a single transaction's detail. */
@Override
public Map<String, Object> retrieveOneTransaction(Long accountId, Long transactionId) {
final String sql = TRANSACTION_SELECT + "WHERE t.current_account_id = ? AND t.id = ?";
final List<Map<String, Object>> rows = jdbcTemplate.query(sql, (rs, rowNum) -> mapTransactionRow(rs), accountId, transactionId);
if (rows.isEmpty()) {
throw new CurrentAccountNotFoundException(accountId);
}
return rows.get(0);
}
final int txnTypeEnum = rs.getInt("transaction_type_enum");
t.put("transactionType", buildTransactionType(txnTypeEnum));
private Map<String, Object> mapTransactionRow(final java.sql.ResultSet rs) throws SQLException {
final Map<String, Object> t = new LinkedHashMap<>();
t.put("id", rs.getLong("id"));
final java.time.LocalDate txnDate = rs.getObject("transaction_date", java.time.LocalDate.class);
if (txnDate != null) {
t.put("date", new int[]{txnDate.getYear(), txnDate.getMonthValue(), txnDate.getDayOfMonth()});
} else {
t.put("date", null);
}
final int txnTypeEnum = rs.getInt("transaction_type_enum");
t.put("transactionType", buildTransactionType(txnTypeEnum));
t.put("amount", rs.getBigDecimal("amount"));
t.put("runningBalance", rs.getBigDecimal("running_balance_derived"));
t.put("reversed", rs.getBoolean("is_reversed"));
t.put("isReversal", rs.getBoolean("is_reversal"));
t.put("refNo", rs.getString("ref_no"));
t.put("remarks", rs.getString("remarks"));
final java.time.LocalDate txnDate = rs.getObject("transaction_date", java.time.LocalDate.class);
if (txnDate != null) {
t.put("date", new int[]{txnDate.getYear(), txnDate.getMonthValue(), txnDate.getDayOfMonth()});
} else {
t.put("date", null);
}
final java.time.LocalDate submittedOn = rs.getObject("submitted_on_date", java.time.LocalDate.class);
if (submittedOn != null) {
t.put("submittedOnDate", new int[]{submittedOn.getYear(), submittedOn.getMonthValue(), submittedOn.getDayOfMonth()});
} else {
t.put("submittedOnDate", null);
}
return t;
}, accountId);
t.put("amount", rs.getBigDecimal("amount"));
t.put("runningBalance", rs.getBigDecimal("running_balance_derived"));
t.put("reversed", rs.getBoolean("is_reversed"));
t.put("isReversal", rs.getBoolean("is_reversal"));
t.put("refNo", rs.getString("ref_no"));
t.put("remarks", rs.getString("remarks"));
final java.time.LocalDate submittedOn = rs.getObject("submitted_on_date", java.time.LocalDate.class);
if (submittedOn != null) {
t.put("submittedOnDate", new int[]{submittedOn.getYear(), submittedOn.getMonthValue(), submittedOn.getDayOfMonth()});
} else {
t.put("submittedOnDate", null);
}
return t;
}
private Map<String, Object> buildTransactionType(int typeEnum) {
@@ -169,8 +181,8 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
tt.put("writtenoff", false);
tt.put("overdraftFee", false);
tt.put("escheat", false);
tt.put("amountHold", false);
tt.put("amountRelease", false);
tt.put("amountHold", typeEnum == 8);
tt.put("amountRelease", typeEnum == 9);
tt.put("accrual", false);
switch (typeEnum) {
case 1 -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); }
@@ -178,6 +190,8 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
case 3 -> { tt.put("code", "savingsTransactionType.interestPosting"); tt.put("value", "Interest Posting"); }
case 4 -> { tt.put("code", "savingsTransactionType.feeDeduction"); tt.put("value", "Fee Deduction"); }
case 7 -> { tt.put("code", "savingsTransactionType.payCharge"); tt.put("value", "Charge Deduction"); }
case 8 -> { tt.put("code", "savingsTransactionType.onHold"); tt.put("value", "Amount on hold"); }
case 9 -> { tt.put("code", "savingsTransactionType.release"); tt.put("value", "Release Amount"); }
default -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); }
}
return tt;