feat(cashier): fix current account transactions search and withdrawal validations
CurrentAccountTransactionsApiResource: - Fixed: search endpoint now queries m_current_account_transaction instead of m_savings_account_transaction (wrong table) - Added remarks column to search query (column exists in CA table) - Charge Deduction (type=7) mapped in transactionType value - Removed sat.remarks that caused 500 (column doesn't exist in savings table) CurrentAccountTransactionWritePlatformServiceImpl: - Added min required balance validation in withdrawal() - Throws GeneralPlatformDomainRuleException when balance after withdrawal would fall below minRequiredBalance (if enforceMinRequiredBalance=true) StandingInstructionReadPlatformServiceImpl: - Fixed SQL WHERE clause: fromAccountType + fromAccount condition now correctly adds WHERE when fromAccountId is set Liquibase 0315: - Fix standing instruction permission code (trim trailing space on id=449) - Grant READ permissions to CASHIER role for transaction entry screen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
2371ad98b1
commit
edaeb674d5
+2
-1
@@ -264,7 +264,8 @@ public class StandingInstructionReadPlatformServiceImpl implements StandingInstr
|
|||||||
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
|
sqlBuilder.append("select " + sqlGenerator.calcFoundRows() + " ");
|
||||||
sqlBuilder.append(this.standingInstructionMapper.schema());
|
sqlBuilder.append(this.standingInstructionMapper.schema());
|
||||||
if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null
|
if (standingInstructionDTO.transferType() != null || standingInstructionDTO.clientId() != null
|
||||||
|| standingInstructionDTO.clientName() != null) {
|
|| standingInstructionDTO.clientName() != null
|
||||||
|
|| (standingInstructionDTO.fromAccountType() != null && standingInstructionDTO.fromAccount() != null)) {
|
||||||
sqlBuilder.append(" where ");
|
sqlBuilder.append(" where ");
|
||||||
}
|
}
|
||||||
boolean addAndCaluse = false;
|
boolean addAndCaluse = false;
|
||||||
|
|||||||
+78
@@ -198,6 +198,84 @@ public class CurrentAccountTransactionsApiResource {
|
|||||||
return toApiJsonSerializer.serialize(result);
|
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<Object> 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<java.util.Map<String, Object>> rows = jdbcTemplate.query(
|
||||||
|
sql.toString(),
|
||||||
|
(rs, rowNum) -> {
|
||||||
|
final java.util.Map<String, Object> row = new java.util.LinkedHashMap<>();
|
||||||
|
row.put("id", rs.getLong("id"));
|
||||||
|
|
||||||
|
final int typeEnum = rs.getInt("transaction_type_enum");
|
||||||
|
final java.util.Map<String, Object> 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<String, Object> 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) {
|
private boolean is(final String commandParam, final String commandValue) {
|
||||||
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
|
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-4
@@ -132,11 +132,21 @@ public class CurrentAccountTransactionWritePlatformServiceImpl implements Curren
|
|||||||
"Insufficient available balance. Available: " + available + ", Requested: " + amount, accountId);
|
"Insufficient available balance. Available: " + available + ", Requested: " + amount, accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String refNo = command.stringValueOfParameterNamed("accountNumber");
|
// Minimum required balance validation
|
||||||
|
|
||||||
final CurrentAccountTransaction txn = CurrentAccountTransaction.withdrawal(account, office, amount, txnDate, refNo);
|
|
||||||
|
|
||||||
final BigDecimal newBalance = currentBalance.subtract(amount);
|
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);
|
txn.setRunningBalanceDerived(newBalance);
|
||||||
|
|
||||||
currentAccountTransactionRepository.saveAndFlush(txn);
|
currentAccountTransactionRepository.saveAndFlush(txn);
|
||||||
|
|||||||
+31
-13
@@ -10,16 +10,30 @@
|
|||||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Grant additional READ permissions to the CASHIER role required for
|
Fix: m_permission.code for READ_STANDINGINSTRUCTION (id=449) had a trailing
|
||||||
the Cashier Transaction Entry screen to display full account details:
|
space which caused permission checks to fail (exact string match).
|
||||||
- READ_CLIENTKYC → PAN, Aadhaar, CKYC number
|
Must run BEFORE the permission grant below.
|
||||||
- READ_CLIENTNOMINEE → nominee list per account
|
-->
|
||||||
- READ_ADDRESS → client address
|
<changeSet id="0315_fix_standing_instruction_permission_code" author="fineract">
|
||||||
- READ_FAMILYMEMBERS → S/D/W/H/O (guardian/spouse name)
|
<sql>
|
||||||
- READ_SAVINGSACCOUNT → savings account detail fetch
|
UPDATE m_permission
|
||||||
|
SET code = TRIM(code)
|
||||||
|
WHERE id = 449
|
||||||
|
AND code != TRIM(code);
|
||||||
|
</sql>
|
||||||
|
<rollback><!-- no rollback needed — trimming is idempotent --></rollback>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Grant READ permissions to CASHIER role for Cashier Transaction Entry screen:
|
||||||
|
- READ_CLIENTKYC, READ_CLIENTNOMINEE, READ_ADDRESS, READ_FAMILYMEMBERS
|
||||||
|
- READ_SAVINGSACCOUNT, READ_CLIENTIMAGE, READ_DOCUMENT
|
||||||
|
- READ_SAVINGNOTE, READ_CLIENTNOTE
|
||||||
|
- READ_STANDINGINSTRUCTION (id 449 — trailing space fixed above)
|
||||||
-->
|
-->
|
||||||
<changeSet id="0315_grant_cashier_transaction_entry_permissions" author="fineract">
|
<changeSet id="0315_grant_cashier_transaction_entry_permissions" author="fineract">
|
||||||
<sql>
|
<sql>
|
||||||
|
-- Grant code-based permissions (includes READ_STANDINGINSTRUCTION after trim fix)
|
||||||
INSERT INTO m_role_permission (role_id, permission_id)
|
INSERT INTO m_role_permission (role_id, permission_id)
|
||||||
SELECT r.id, p.id
|
SELECT r.id, p.id
|
||||||
FROM m_role r
|
FROM m_role r
|
||||||
@@ -28,7 +42,12 @@
|
|||||||
'READ_CLIENTNOMINEE',
|
'READ_CLIENTNOMINEE',
|
||||||
'READ_ADDRESS',
|
'READ_ADDRESS',
|
||||||
'READ_FAMILYMEMBERS',
|
'READ_FAMILYMEMBERS',
|
||||||
'READ_SAVINGSACCOUNT'
|
'READ_SAVINGSACCOUNT',
|
||||||
|
'READ_CLIENTIMAGE',
|
||||||
|
'READ_DOCUMENT',
|
||||||
|
'READ_SAVINGNOTE',
|
||||||
|
'READ_CLIENTNOTE',
|
||||||
|
'READ_STANDINGINSTRUCTION'
|
||||||
)
|
)
|
||||||
WHERE r.name = 'CASHIER'
|
WHERE r.name = 'CASHIER'
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
@@ -42,11 +61,10 @@
|
|||||||
AND permission_id IN (
|
AND permission_id IN (
|
||||||
SELECT id FROM m_permission
|
SELECT id FROM m_permission
|
||||||
WHERE code IN (
|
WHERE code IN (
|
||||||
'READ_CLIENTKYC',
|
'READ_CLIENTKYC', 'READ_CLIENTNOMINEE', 'READ_ADDRESS',
|
||||||
'READ_CLIENTNOMINEE',
|
'READ_FAMILYMEMBERS', 'READ_SAVINGSACCOUNT', 'READ_CLIENTIMAGE',
|
||||||
'READ_ADDRESS',
|
'READ_DOCUMENT', 'READ_SAVINGNOTE', 'READ_CLIENTNOTE',
|
||||||
'READ_FAMILYMEMBERS',
|
'READ_STANDINGINSTRUCTION'
|
||||||
'READ_SAVINGSACCOUNT'
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
</rollback>
|
</rollback>
|
||||||
|
|||||||
Reference in New Issue
Block a user