From 24f5c179d8563b3b55e5db6d9035560fe08e6f57 Mon Sep 17 00:00:00 2001 From: DDD1542 Date: Fri, 6 Mar 2026 02:51:51 +0900 Subject: [PATCH] [agent-pipeline] pipe-20260305174856-9ogt round-1 --- .../src/controllers/approvalController.ts | 74 ++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/backend-node/src/controllers/approvalController.ts b/backend-node/src/controllers/approvalController.ts index a63c8196..cec2ca67 100644 --- a/backend-node/src/controllers/approvalController.ts +++ b/backend-node/src/controllers/approvalController.ts @@ -3,6 +3,14 @@ import { AuthenticatedRequest } from "../types/auth"; import { query, queryOne, transaction } from "../database/db"; import { PoolClient } from "pg"; +// 트랜잭션 내부에서 throw하고 외부에서 instanceof로 구분하기 위한 커스텀 에러 +class ValidationError extends Error { + constructor(public statusCode: number, message: string) { + super(message); + this.name = "ValidationError"; + } +} + // ============================================================ // 결재 정의 (Approval Definitions) CRUD // ============================================================ @@ -18,24 +26,34 @@ export class ApprovalDefinitionController { const { is_active, search } = req.query; - const conditions: string[] = ["company_code = $1"]; - const params: any[] = [companyCode]; - let idx = 2; + const conditions: string[] = []; + const params: any[] = []; + let idx = 1; + + // SUPER_ADMIN은 전체 조회, 일반 회사는 자사 데이터만 + if (companyCode === "*") { + // 전체 조회 (company_code 필터 없음) + } else { + conditions.push(`company_code = $${idx++}`); + params.push(companyCode); + } if (is_active) { - conditions.push(`is_active = $${idx}`); + conditions.push(`is_active = $${idx++}`); params.push(is_active); - idx++; } if (search) { + // ILIKE에서 같은 파라미터를 두 조건에서 참조 (파라미터는 1개만 push) conditions.push(`(definition_name ILIKE $${idx} OR definition_name_eng ILIKE $${idx})`); params.push(`%${search}%`); idx++; } + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; + const rows = await query( - `SELECT * FROM approval_definitions WHERE ${conditions.join(" AND ")} ORDER BY definition_id ASC`, + `SELECT * FROM approval_definitions ${whereClause} ORDER BY company_code, definition_id ASC`, params ); @@ -239,9 +257,15 @@ export class ApprovalTemplateController { const { definition_id, is_active } = req.query; - const conditions: string[] = ["t.company_code = $1"]; - const params: any[] = [companyCode]; - let idx = 2; + const conditions: string[] = []; + const params: any[] = []; + let idx = 1; + + // SUPER_ADMIN은 전체 조회, 일반 회사는 자사 데이터만 + if (companyCode !== "*") { + conditions.push(`t.company_code = $${idx++}`); + params.push(companyCode); + } if (definition_id) { conditions.push(`t.definition_id = $${idx++}`); @@ -252,12 +276,14 @@ export class ApprovalTemplateController { params.push(is_active); } + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; + const rows = await query( `SELECT t.*, d.definition_name FROM approval_line_templates t LEFT JOIN approval_definitions d ON t.definition_id = d.definition_id AND t.company_code = d.company_code - WHERE ${conditions.join(" AND ")} - ORDER BY t.template_id ASC`, + ${whereClause} + ORDER BY t.company_code, t.template_id ASC`, params ); @@ -576,9 +602,15 @@ export class ApprovalRequestController { const { status, target_table, target_record_id, requester_id, my_approvals, page = "1", limit = "20" } = req.query; - const conditions: string[] = ["r.company_code = $1"]; - const params: any[] = [companyCode]; - let idx = 2; + const conditions: string[] = []; + const params: any[] = []; + let idx = 1; + + // SUPER_ADMIN은 전체 조회, 일반 회사는 자사 데이터만 + if (companyCode !== "*") { + conditions.push(`r.company_code = $${idx++}`); + params.push(companyCode); + } if (status) { conditions.push(`r.status = $${idx++}`); @@ -606,12 +638,12 @@ export class ApprovalRequestController { } const offset = (parseInt(page as string) - 1) * parseInt(limit as string); + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; // 카운트 쿼리는 LIMIT/OFFSET 파라미터 없이 실행 const countParams = [...params]; const [countRow] = await query( - `SELECT COUNT(*) as total FROM approval_requests r - WHERE ${conditions.join(" AND ")}`, + `SELECT COUNT(*) as total FROM approval_requests r ${whereClause}`, countParams ); @@ -624,7 +656,7 @@ export class ApprovalRequestController { `SELECT r.*, d.definition_name FROM approval_requests r LEFT JOIN approval_definitions d ON r.definition_id = d.definition_id AND r.company_code = d.company_code - WHERE ${conditions.join(" AND ")} + ${whereClause} ORDER BY r.created_at DESC LIMIT $${limitIdx} OFFSET $${offsetIdx}`, params @@ -1057,14 +1089,6 @@ export class ApprovalLineController { return res.status(400).json({ success: false, message: "액션은 approved 또는 rejected여야 합니다." }); } - // 검증 에러를 트랜잭션 바깥으로 전달하기 위한 커스텀 에러 클래스 - class ValidationError extends Error { - constructor(public statusCode: number, message: string) { - super(message); - this.name = "ValidationError"; - } - } - await transaction(async (client) => { // FOR UPDATE로 결재 라인 잠금 (동시성 방어) const { rows: [line] } = await client.query(