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