diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java index 27b2b2627..fdfbae3f6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/StandingInstructionReadPlatformServiceImpl.java @@ -264,7 +264,8 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " "); sqlBuilder.append(this.standingInstructionMapper.schema()); if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null - || standingInstructionDTO.clientName() != null) { + || standingInstructionDTO.clientName() != null + || (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null)) { sqlBuilder.append(" where "); } boolean addAndCaluse = false; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountTransactionsApiResource.java index 36bf1ecd5..796562228 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentAccountTransactionsApiResource.java @@ -198,6 +198,84 @@ public class CurrentAccountTransactionsApiResource { return toApiJsonSerializer.serialize(result); } + /** + * GET /currentaccounts/{accountId}/transactions/search?fromDate=YYYY-MM-DD&toDate=YYYY-MM-DD&limit=20 + * + * Date-filtered transaction search for current accounts — mirrors the savings account search + * endpoint so the cashier transaction entry screen can use a consistent API across all types. + */ + @GET + @Path("search") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Search current account transactions by date range") + @ApiResponse(responseCode = "200", description = "OK") + public String searchTransactions( + @PathParam("accountId") final Long accountId, + @QueryParam("fromDate") final String fromDate, + @QueryParam("toDate") final String toDate, + @QueryParam("limit") @jakarta.ws.rs.DefaultValue("20") final int limit, + @QueryParam("offset") @jakarta.ws.rs.DefaultValue("0") final int offset) { + + context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME); + + final StringBuilder sql = new StringBuilder( + "SELECT cat.id, cat.transaction_type_enum, cat.transaction_date, cat.amount, " + + "cat.running_balance_derived, cat.is_reversed, cat.ref_no, cat.remarks " + + "FROM m_current_account_transaction cat " + + "WHERE cat.current_account_id = ? AND cat.is_reversed = false "); + + final java.util.List params = new java.util.ArrayList<>(); + params.add(accountId); + + if (fromDate != null && !fromDate.isBlank()) { + sql.append("AND cat.transaction_date >= CAST(? AS DATE) "); + params.add(fromDate); + } + if (toDate != null && !toDate.isBlank()) { + sql.append("AND cat.transaction_date <= CAST(? AS DATE) "); + params.add(toDate); + } + sql.append("ORDER BY cat.transaction_date DESC, cat.id DESC "); + sql.append("LIMIT ? OFFSET ?"); + params.add(limit); + params.add(offset); + + final java.util.List> rows = jdbcTemplate.query( + sql.toString(), + (rs, rowNum) -> { + final java.util.Map row = new java.util.LinkedHashMap<>(); + row.put("id", rs.getLong("id")); + + final int typeEnum = rs.getInt("transaction_type_enum"); + final java.util.Map txnType = new java.util.LinkedHashMap<>(); + txnType.put("id", typeEnum); + txnType.put("deposit", typeEnum == 1); + txnType.put("withdrawal", typeEnum == 2); + txnType.put("value", typeEnum == 1 ? "Deposit" : typeEnum == 2 ? "Withdrawal" + : typeEnum == 7 ? "Charge Deduction" : "Other"); + row.put("transactionType", txnType); + + final java.sql.Date d = rs.getDate("transaction_date"); + if (d != null) { + final java.time.LocalDate ld = d.toLocalDate(); + row.put("date", new int[]{ ld.getYear(), ld.getMonthValue(), ld.getDayOfMonth() }); + } + row.put("amount", rs.getBigDecimal("amount")); + row.put("runningBalance", rs.getBigDecimal("running_balance_derived")); + row.put("reversed", rs.getBoolean("is_reversed")); + row.put("refNo", rs.getString("ref_no")); + row.put("remarks", rs.getString("remarks")); + return row; + }, + params.toArray()); + + final java.util.Map resp = new java.util.LinkedHashMap<>(); + resp.put("totalFilteredRecords", rows.size()); + resp.put("pageItems", rows); + return new com.google.gson.Gson().toJson(resp); + } + private boolean is(final String commandParam, final String commandValue) { return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountTransactionWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountTransactionWritePlatformServiceImpl.java index 37b22540c..8b5539d5e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountTransactionWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/CurrentAccountTransactionWritePlatformServiceImpl.java @@ -132,11 +132,21 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren "Insufficient available balance. Available: " + available + ", Requested: " + amount, accountId); } - final String refNo = command.stringValueOfParameterNamed("accountNumber"); - - final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, office, amount, txnDate, refNo); - + // Minimum required balance validation final BigDecimal newBalance = currentBalance.subtract(amount); + if (account.isEnforceMinRequiredBalance() && account.getMinRequiredBalance() != null + && account.getMinRequiredBalance().compareTo(BigDecimal.ZERO) > 0) { + if (newBalance.compareTo(account.getMinRequiredBalance()) < 0) { + throw new GeneralPlatformDomainRuleException( + "error.msg.current.account.min.required.balance.breached", + "Withdrawal of " + amount + " would breach the minimum required balance of " + + account.getMinRequiredBalance() + ". Balance after withdrawal would be " + newBalance, + accountId); + } + } + + final String refNo = command.stringValueOfParameterNamed("accountNumber"); + final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, office, amount, txnDate, refNo); txn.setRunningBalanceDerived(newBalance); currentAccountTransactionRepository.saveAndFlush(txn); diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0315_grant_cashier_transaction_entry_permissions.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0315_grant_cashier_transaction_entry_permissions.xml index 131425a5c..6cca5cb78 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0315_grant_cashier_transaction_entry_permissions.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0315_grant_cashier_transaction_entry_permissions.xml @@ -10,16 +10,30 @@ http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd"> + + + UPDATE m_permission + SET code = TRIM(code) + WHERE id = 449 + AND code != TRIM(code); + + + + + + -- Grant code-based permissions (includes READ_STANDINGINSTRUCTION after trim fix) INSERT INTO m_role_permission (role_id, permission_id) SELECT r.id, p.id FROM m_role r @@ -28,7 +42,12 @@ 'READ_CLIENTNOMINEE', 'READ_ADDRESS', 'READ_FAMILYMEMBERS', - 'READ_SAVINGSACCOUNT' + 'READ_SAVINGSACCOUNT', + 'READ_CLIENTIMAGE', + 'READ_DOCUMENT', + 'READ_SAVINGNOTE', + 'READ_CLIENTNOTE', + 'READ_STANDINGINSTRUCTION' ) WHERE r.name = 'CASHIER' AND NOT EXISTS ( @@ -42,11 +61,10 @@ AND permission_id IN ( SELECT id FROM m_permission WHERE code IN ( - 'READ_CLIENTKYC', - 'READ_CLIENTNOMINEE', - 'READ_ADDRESS', - 'READ_FAMILYMEMBERS', - 'READ_SAVINGSACCOUNT' + 'READ_CLIENTKYC', 'READ_CLIENTNOMINEE', 'READ_ADDRESS', + 'READ_FAMILYMEMBERS', 'READ_SAVINGSACCOUNT', 'READ_CLIENTIMAGE', + 'READ_DOCUMENT', 'READ_SAVINGNOTE', 'READ_CLIENTNOTE', + 'READ_STANDINGINSTRUCTION' ) );