diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashierdeposit/api/CashierDepositRequestApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashierdeposit/api/CashierDepositRequestApiResource.java index 7a0b27413..2f1eab9f7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashierdeposit/api/CashierDepositRequestApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/cashierdeposit/api/CashierDepositRequestApiResource.java @@ -335,7 +335,14 @@ public class CashierDepositRequestApiResource { final String txnDateStr = txnDateObj != null ? txnDateObj.toString().split(" ")[0] : null; final String description = (String) record.get("description"); - final String txnId = executeSavingsDeposit(savingsAccountId, amount, ptId, txnDateStr, description); + // Determine real account type by checking which table owns this ID. + // Do NOT rely solely on the stored account_type field — old records may have 'SAVINGS' + // even for current accounts (submitted before the frontend fix). + final boolean isCurrentAccount = isCurrentAccountById(savingsAccountId); + + final String txnId = isCurrentAccount + ? executeCurrentAccountDeposit(savingsAccountId, amount, ptId, txnDateStr, description) + : executeSavingsDeposit(savingsAccountId, amount, ptId, txnDateStr, description); // Link voucher to the created savings txn and stamp voucher_no on it if (txnId != null) { @@ -419,6 +426,52 @@ public class CashierDepositRequestApiResource { return gson.toJson(Map.of("resourceId", id, "status", "REJECTED")); } + // ── POST /v1/cashier/deposit-requests/{id}/reprocess ── Fix wrongly-routed approvals ── + // Use this for records that show APPROVED but whose deposit went to the wrong account + // (savings instead of current). Requires ADMIN / ALL_FUNCTIONS permission. + + @POST + @Path("/{id}/reprocess") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Transactional + public String reprocess(@PathParam("id") final Long id, final String body) { + final AppUser currentUser = context.authenticatedUser(); + if (!currentUser.hasAnyPermission("ALL_FUNCTIONS")) { + throw apiError(403, "Only a super-admin (ALL_FUNCTIONS) can reprocess deposit requests."); + } + final Map record = loadRecord(id); + + if (!"APPROVED".equals(record.get("status"))) { + throw apiError(409, "Only APPROVED requests can be reprocessed. Current status: " + record.get("status")); + } + + final Long savingsAccountId = ((Number) record.get("savings_account_id")).longValue(); + final BigDecimal amount = new BigDecimal(record.get("amount").toString()); + final Object ptId = record.get("payment_type_id"); + final Object txnDateObj = record.get("txn_date"); + final String txnDateStr = txnDateObj != null ? txnDateObj.toString().split(" ")[0] : null; + final String description = (String) record.get("description"); + + if (!isCurrentAccountById(savingsAccountId)) { + throw apiError(400, "This account is not a current account. Reprocess is only needed for current account deposits."); + } + + final String txnId = executeCurrentAccountDeposit(savingsAccountId, amount, ptId, txnDateStr, description); + + if (txnId != null) { + voucherService.linkSavingsTransaction(VoucherService.REF_CASHIER_DEPOSIT_REQUEST, id, Long.valueOf(txnId)); + jdbcTemplate.update("UPDATE m_cashier_deposit_request SET txn_id = ? WHERE id = ?", txnId, id); + } + + final JsonObject res = new JsonObject(); + res.addProperty("resourceId", id); + res.addProperty("status", "APPROVED"); + res.addProperty("reprocessed", true); + if (txnId != null) res.addProperty("txnId", txnId); + return gson.toJson(res); + } + // ── helpers ────────────────────────────────────────────────────────────────── private Map loadRecord(final Long id) { @@ -464,6 +517,45 @@ public class CashierDepositRequestApiResource { return result.getResourceId() != null ? result.getResourceId().toString() : null; } + private String executeCurrentAccountDeposit( + final Long currentAccountId, final BigDecimal amount, + final Object paymentTypeId, final String txnDateStr, final String note) { + + final String today = LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy")); + final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today; + + final JsonObject payload = new JsonObject(); + payload.addProperty("locale", "en"); + payload.addProperty("dateFormat", "dd MMMM yyyy"); + payload.addProperty("transactionDate", date); + payload.addProperty("transactionAmount", amount); + if (paymentTypeId != null) { + payload.addProperty("paymentTypeId", ((Number) paymentTypeId).longValue()); + } + if (note != null && !note.isBlank()) { + payload.addProperty("note", note); + } + + final CommandWrapper commandRequest = new CommandWrapperBuilder() + .currentAccountDeposit(currentAccountId) + .withJson(payload.toString()) + .build(); + + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + return result.getResourceId() != null ? result.getResourceId().toString() : null; + } + + /** Returns true if this account ID belongs to m_current_account (not m_savings_account). */ + private boolean isCurrentAccountById(final Long accountId) { + try { + final Integer count = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM m_current_account WHERE id = ?", Integer.class, accountId); + return count != null && count > 0; + } catch (Exception e) { + return false; + } + } + /** * Saves the denomination breakdown the cashier entered at submit time into * m_savings_txn_denomination once the deposit is approved, so the cashier's diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/clerkwithdrawal/api/ClerkWithdrawalRequestApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/clerkwithdrawal/api/ClerkWithdrawalRequestApiResource.java index bf97e5a50..cae04d393 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/clerkwithdrawal/api/ClerkWithdrawalRequestApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/clerkwithdrawal/api/ClerkWithdrawalRequestApiResource.java @@ -442,7 +442,10 @@ public class ClerkWithdrawalRequestApiResource { String txnId = null; if (savingsAccountId != null) { - txnId = executeSavingsWithdrawal(savingsAccountId, amount, ptId, txnDateStr, description); + final boolean isCurrentAccount = isCurrentAccountById(savingsAccountId); + txnId = isCurrentAccount + ? executeCurrentAccountWithdrawal(savingsAccountId, amount, ptId, txnDateStr, description) + : executeSavingsWithdrawal(savingsAccountId, amount, ptId, txnDateStr, description); } // Link voucher to the created savings txn and stamp voucher_no on it @@ -734,6 +737,44 @@ public class ClerkWithdrawalRequestApiResource { return result.getResourceId() != null ? result.getResourceId().toString() : null; } + private String executeCurrentAccountWithdrawal( + final Long currentAccountId, final BigDecimal amount, + final Object paymentTypeId, final String txnDateStr, final String note) { + + final String today = LocalDate.now().format(DateTimeFormatter.ofPattern("dd MMMM yyyy")); + final String date = txnDateStr != null && !txnDateStr.isBlank() ? formatForFineract(txnDateStr) : today; + + final JsonObject payload = new JsonObject(); + payload.addProperty("locale", "en"); + payload.addProperty("dateFormat", "dd MMMM yyyy"); + payload.addProperty("transactionDate", date); + payload.addProperty("transactionAmount", amount); + if (paymentTypeId != null) { + payload.addProperty("paymentTypeId", ((Number) paymentTypeId).longValue()); + } + if (note != null && !note.isBlank()) { + payload.addProperty("note", note); + } + + final CommandWrapper commandRequest = new CommandWrapperBuilder() + .currentAccountWithdrawal(currentAccountId) + .withJson(payload.toString()) + .build(); + + final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest); + return result.getResourceId() != null ? result.getResourceId().toString() : null; + } + + private boolean isCurrentAccountById(final Long accountId) { + try { + final Integer count = jdbcTemplate.queryForObject( + "SELECT COUNT(*) FROM m_current_account WHERE id = ?", Integer.class, accountId); + return count != null && count > 0; + } catch (Exception e) { + return false; + } + } + private String formatForFineract(final String isoDate) { try { final LocalDate ld = LocalDate.parse(isoDate, DateTimeFormatter.ISO_LOCAL_DATE);