diff --git a/backend-spring/src/main/java/com/erp/controller/ApprovalController.java b/backend-spring/src/main/java/com/erp/controller/ApprovalController.java index adc270a5..0e8d2e9b 100644 --- a/backend-spring/src/main/java/com/erp/controller/ApprovalController.java +++ b/backend-spring/src/main/java/com/erp/controller/ApprovalController.java @@ -190,9 +190,11 @@ public class ApprovalController { public ResponseEntity>> getRequests( @RequestParam Map params, @RequestAttribute("company_code") String companyCode, - @RequestAttribute("user_id") String userId) { + @RequestAttribute("user_id") String userId, + @RequestAttribute(name = "effective_user_ids", required = false) List effectiveUserIds) { params.put("company_code", companyCode); params.put("user_id", userId); + params.put("effective_user_ids", effectiveUserIds); return ResponseEntity.ok(ApiResponse.success(approvalService.getRequests(params))); } @@ -277,10 +279,12 @@ public class ApprovalController { @GetMapping("/my-pending") public ResponseEntity>>> getMyPendingLines( @RequestAttribute("company_code") String companyCode, - @RequestAttribute("user_id") String userId) { + @RequestAttribute("user_id") String userId, + @RequestAttribute(name = "effective_user_ids", required = false) List effectiveUserIds) { Map params = new HashMap<>(); params.put("company_code", companyCode); params.put("user_id", userId); + params.put("effective_user_ids", effectiveUserIds); return ResponseEntity.ok(ApiResponse.success(approvalService.getMyPendingLines(params))); } diff --git a/backend-spring/src/main/java/com/erp/service/ApprovalService.java b/backend-spring/src/main/java/com/erp/service/ApprovalService.java index d3933f02..964e5669 100644 --- a/backend-spring/src/main/java/com/erp/service/ApprovalService.java +++ b/backend-spring/src/main/java/com/erp/service/ApprovalService.java @@ -17,6 +17,26 @@ public class ApprovalService extends BaseService { @Autowired private ObjectMapper objectMapper; + @Autowired + private AuditLogService auditLogService; + + /** + * IN (:effective_user_ids) 쿼리용 fallback. + * SubstituteContextFilter 가 attribute 를 못 채운 경로(통합 테스트/배치 등) 에서도 + * 빈 IN () SQL 에러를 막기 위해 항상 최소 [user_id] 가 들어가도록 한다. + */ + @SuppressWarnings("unchecked") + private void ensureEffectiveUserIds(Map params) { + Object v = params.get("effective_user_ids"); + boolean empty = v == null || (v instanceof Collection && ((Collection) v).isEmpty()); + if (empty) { + Object userId = params.get("user_id"); + if (userId != null) { + params.put("effective_user_ids", List.of(userId)); + } + } + } + // ═══════════════════════════════════════════════════════════════ // approval_definitions // ═══════════════════════════════════════════════════════════════ @@ -149,6 +169,7 @@ public class ApprovalService extends BaseService { // ═══════════════════════════════════════════════════════════════ public Map getRequests(Map params) { + ensureEffectiveUserIds(params); int page = toInt(params.getOrDefault("page", "1")); int limit = toInt(params.getOrDefault("limit", "20")); params.put("page_limit", limit); @@ -359,6 +380,7 @@ public class ApprovalService extends BaseService { // ═══════════════════════════════════════════════════════════════ public List> getMyPendingLines(Map params) { + ensureEffectiveUserIds(params); return sqlSession.selectList("approval.selectMyPendingLines", params); } @@ -456,6 +478,24 @@ public class ApprovalService extends BaseService { activateNextStep(requestId, stepOrder, totalSteps, lineCC, userId, comment); } } + + // 결재 처리 audit log — 대무 시 user_id(A)와 processor_id(B) 분리 기록. + // 실패는 본 처리를 막지 않음 (가용성 우선). + try { + Map auditP = new HashMap<>(); + auditP.put("company_code", lineCC); + auditP.put("user_id", approverId); // 위임자 A + auditP.put("user_name", line.get("approver_name")); + auditP.put("processor_id", userId); // 실제 처리자 B + // processor_name 은 AuditLogService 가 USER_INFO 에서 lookup (T14) + auditP.put("action", "approval." + action); + auditP.put("resource_type", "approval_line"); + auditP.put("resource_id", String.valueOf(lineId)); + auditP.put("summary", "결재 " + action + (proxyFor != null ? " (대무)" : "")); + auditLogService.insertAuditLog(auditP); + } catch (Exception e) { + log.warn("결재 audit log 기록 실패 (line={}): {}", lineId, e.getMessage()); + } } // ═══════════════════════════════════════════════════════════════ diff --git a/backend-spring/src/main/resources/mapper/approval.xml b/backend-spring/src/main/resources/mapper/approval.xml index 1259b5e9..91005bb9 100644 --- a/backend-spring/src/main/resources/mapper/approval.xml +++ b/backend-spring/src/main/resources/mapper/approval.xml @@ -214,7 +214,10 @@ AND EXISTS ( SELECT 1 FROM APPROVAL_LINES L WHERE L.REQUEST_ID = R.REQUEST_ID - AND L.APPROVER_ID = #{user_id} + AND L.APPROVER_ID IN + + #{uid} + AND L.STATUS = 'pending' AND L.COMPANY_CODE = R.COMPANY_CODE ) @@ -248,7 +251,10 @@ AND EXISTS ( SELECT 1 FROM APPROVAL_LINES L WHERE L.REQUEST_ID = R.REQUEST_ID - AND L.APPROVER_ID = #{user_id} + AND L.APPROVER_ID IN + + #{uid} + AND L.STATUS = 'pending' AND L.COMPANY_CODE = R.COMPANY_CODE ) @@ -463,7 +469,10 @@ FROM APPROVAL_LINES L JOIN APPROVAL_REQUESTS R ON L.REQUEST_ID = R.REQUEST_ID AND L.COMPANY_CODE = R.COMPANY_CODE - WHERE L.APPROVER_ID = #{user_id} + WHERE L.APPROVER_ID IN + + #{uid} + AND L.STATUS = 'pending' AND (L.COMPANY_CODE = #{company_code} OR L.COMPANY_CODE = '*') ORDER BY R.CREATED_DATE ASC @@ -536,12 +545,14 @@ AND (COMPANY_CODE = #{company_code} OR COMPANY_CODE = '*') +