From 6ab7c3e7801862a05807fa1ffe98dfc783ae3f2c Mon Sep 17 00:00:00 2001 From: johngreen Date: Tue, 12 May 2026 08:06:39 +0900 Subject: [PATCH] =?UTF-8?q?feat(=EB=8C=80=EB=AC=B4=EC=9E=90):=20=EA=B2=B0?= =?UTF-8?q?=EC=9E=AC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EC=96=B4=EB=8C=91?= =?UTF-8?q?=ED=84=B0=20=ED=86=B5=ED=95=A9=20=E2=80=94=20USER=5FSUBSTITUTES?= =?UTF-8?q?=20read=20+=20IN(effective=5Fuser=5Fids)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - approval.xml selectActiveProxyForLine: APPROVAL_PROXY_SETTINGS → USER_SUBSTITUTES 참조 (BOOLEAN IS_ACTIVE, START_DATE NULL 허용) - approval.xml 3곳 (selectRequests/countRequests/selectMyPendingLines): APPROVER_ID = #{user_id} → APPROVER_ID IN (effective_user_ids) foreach - ApprovalService.ensureEffectiveUserIds helper — mybatis 빈 IN() 방지 - ApprovalController: getRequests/getMyPendingLines 에 @RequestAttribute(effective_user_ids) 추가 - ApprovalService.processApproval 마지막에 AuditLogService.insertAuditLog 호출 추가 (user_id=A, processor_id=B 분리 기록) --- .../erp/controller/ApprovalController.java | 8 +++- .../java/com/erp/service/ApprovalService.java | 40 +++++++++++++++++++ .../src/main/resources/mapper/approval.xml | 23 ++++++++--- 3 files changed, 63 insertions(+), 8 deletions(-) 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 = '*') +