ERP-node/docs/결재시스템_v2_사용가이드.md

11 KiB

결재 시스템 v2 사용 가이드

개요

결재 시스템 v2는 기존 순차결재(escalation) 외에 다양한 결재 방식을 지원합니다.

결재 유형 코드 설명
순차결재 (기본) escalation 결재선 순서대로 한 명씩 처리
전결 (자기결재) self 상신자 본인이 직접 승인 (결재자 불필요)
합의결재 consensus 같은 단계에 여러 결재자 → 전원 승인 필요
후결 post 먼저 실행 후 나중에 결재 (결재 전 상태에서도 업무 진행)

추가 기능:

  • 대결 위임: 부재 시 다른 사용자에게 결재 위임
  • 통보 단계: 결재선에 통보만 하는 단계 (자동 승인 처리)
  • 긴급도: normal / urgent / critical
  • 혼합형 결재선: 한 결재선에 결재/합의/통보 단계를 자유롭게 조합

DB 스키마 변경사항

마이그레이션 적용

# 개발 DB에 마이그레이션 적용
psql -h 39.117.244.52 -p 11132 -U postgres -d plm -f db/migrations/1051_approval_system_v2.sql
psql -h 39.117.244.52 -p 11132 -U postgres -d plm -f db/migrations/1052_rename_proxy_id_to_id.sql

변경된 테이블

approval_requests (추가 컬럼)

컬럼 타입 기본값 설명
approval_type VARCHAR(20) 'escalation' self/escalation/consensus/post
is_post_approved BOOLEAN FALSE 후결 처리 완료 여부
post_approved_at TIMESTAMPTZ NULL 후결 처리 시각
urgency VARCHAR(20) 'normal' normal/urgent/critical

approval_lines (추가 컬럼)

컬럼 타입 기본값 설명
step_type VARCHAR(20) 'approval' approval/consensus/notification
proxy_for VARCHAR(50) NULL 대결 시 원래 결재자 ID
proxy_reason TEXT NULL 대결 사유
is_required BOOLEAN TRUE 필수 결재 여부

approval_proxy_settings (신규)

컬럼 타입 설명
id SERIAL PK
company_code VARCHAR(20) NOT NULL
original_user_id VARCHAR(50) 원래 결재자
proxy_user_id VARCHAR(50) 대결자
start_date DATE 위임 시작일
end_date DATE 위임 종료일
reason TEXT 위임 사유
is_active CHAR(1) 'Y'/'N'

API 엔드포인트

모든 API는 /api/approval 접두사 + JWT 인증 필수.

결재 요청 (Requests)

Method Endpoint 설명
GET /requests 목록 조회
GET /requests/:id 상세 조회 (lines 포함)
POST /requests 결재 요청 생성
POST /requests/:id/cancel 결재 회수
POST /requests/:id/post-approve 후결 처리

결재 요청 생성 Body

{
  title: string;
  target_table: string;
  target_record_id: string;
  approval_type?: "self" | "escalation" | "consensus" | "post";  // 기본: escalation
  urgency?: "normal" | "urgent" | "critical";  // 기본: normal
  definition_id?: number;
  target_record_data?: Record<string, any>;
  approvers: Array<{
    approver_id: string;
    step_order: number;
    step_type?: "approval" | "consensus" | "notification";  // 기본: approval
  }>;
}

결재 유형별 요청 예시

전결 (self): 결재자 없이 본인 즉시 승인

await createApprovalRequest({
  title: "긴급 출장비 전결",
  target_table: "expense",
  target_record_id: "123",
  approval_type: "self",
  approvers: [],
});

합의결재 (consensus): 같은 step_order에 여러 결재자

await createApprovalRequest({
  title: "프로젝트 예산안 합의",
  target_table: "budget",
  target_record_id: "456",
  approval_type: "consensus",
  approvers: [
    { approver_id: "user1", step_order: 1, step_type: "consensus" },
    { approver_id: "user2", step_order: 1, step_type: "consensus" },
    { approver_id: "user3", step_order: 1, step_type: "consensus" },
  ],
});

혼합형 결재선: 결재 → 합의 → 통보 조합

await createApprovalRequest({
  title: "신규 채용 승인",
  target_table: "recruitment",
  target_record_id: "789",
  approval_type: "escalation",
  approvers: [
    { approver_id: "teamLead",  step_order: 1, step_type: "approval" },
    { approver_id: "hrManager", step_order: 2, step_type: "consensus" },
    { approver_id: "cfo",       step_order: 2, step_type: "consensus" },
    { approver_id: "ceo",       step_order: 3, step_type: "approval" },
    { approver_id: "secretary", step_order: 4, step_type: "notification" },
  ],
});

후결 (post): 먼저 실행 후 나중에 결재

await createApprovalRequest({
  title: "긴급 자재 발주",
  target_table: "purchase_order",
  target_record_id: "101",
  approval_type: "post",
  urgency: "urgent",
  approvers: [
    { approver_id: "manager", step_order: 1, step_type: "approval" },
  ],
});

결재 처리 (Lines)

Method Endpoint 설명
GET /my-pending 내 결재 대기 목록
POST /lines/:lineId/process 승인/반려 처리

승인/반려 Body

{
  action: "approved" | "rejected";
  comment?: string;
  proxy_reason?: string;  // 대결 시 사유
}

대결 처리: 원래 결재자가 아닌 사용자가 처리하면 자동으로 대결 설정 확인 후 proxy_for, proxy_reason 기록.

대결 위임 설정 (Proxy Settings)

Method Endpoint 설명
GET /proxy-settings 위임 목록
POST /proxy-settings 위임 생성
PUT /proxy-settings/:id 위임 수정
DELETE /proxy-settings/:id 위임 삭제
GET /proxy-settings/check/:userId 활성 대결자 확인

대결 생성 Body

{
  original_user_id: string;
  proxy_user_id: string;
  start_date: string;  // "2026-03-10"
  end_date: string;    // "2026-03-20"
  reason?: string;
  is_active?: "Y" | "N";
}

템플릿 (Templates)

Method Endpoint 설명
GET /templates 템플릿 목록
GET /templates/:id 템플릿 상세 (steps 포함)
POST /templates 템플릿 생성
PUT /templates/:id 템플릿 수정
DELETE /templates/:id 템플릿 삭제

프론트엔드 화면

1. 결재 요청 모달 (ApprovalRequestModal)

경로: frontend/components/approval/ApprovalRequestModal.tsx

  • 결재 유형 선택: 상신결재 / 전결 / 합의결재 / 후결
  • 템플릿 불러오기: 등록된 템플릿에서 결재선 자동 세팅
  • 전결 시 결재자 섹션 숨김 + "본인이 직접 승인합니다" 안내
  • 합의결재 시 결재자 레이블 "합의 결재자"로 변경
  • 후결 시 안내 배너 표시
  • 혼합형 step_type 뱃지 표시 (결재/합의/통보)

2. 결재함 (/admin/approvalBox)

경로: frontend/app/(main)/admin/approvalBox/page.tsx

탭 구성:

  • 수신함: 내가 결재할 건 목록
  • 상신함: 내가 요청한 건 목록
  • 대결 설정: 대결 위임 CRUD

대결 설정 기능:

  • 위임자/대결자 사용자 검색 (디바운스 300ms)
  • 시작일/종료일 설정
  • 활성/비활성 토글
  • 기간 중복 체크 (서버 측)
  • 등록/수정/삭제 모달

3. 결재 템플릿 관리 (/admin/approvalTemplate)

경로: frontend/app/(main)/admin/approvalTemplate/page.tsx

  • 템플릿 목록/검색
  • 등록/수정 Dialog
  • 단계별 결재 유형 설정 (결재/합의/통보)
  • 합의 단계: "합의자 추가" 버튼으로 같은 step_order에 복수 결재자
  • 결재자 사용자 검색

4. 결재 단계 컴포넌트 (v2-approval-step)

경로: frontend/lib/registry/components/v2-approval-step/

화면 디자이너에서 사용하는 결재 단계 시각화 컴포넌트:

  • 가로형/세로형 스테퍼
  • step_order 기준 그룹핑 (합의결재 시 가로 나열)
  • step_type 아이콘: 결재(CheckCircle), 합의(Users), 통보(Bell)
  • 상태별 색상: 승인(success), 반려(destructive), 대기(warning)
  • 대결/후결/전결 뱃지
  • 긴급도 표시 (urgent: 주황 dot, critical: 빨강 배경)

API 클라이언트 사용법

import {
  // 결재 요청
  createApprovalRequest,
  getApprovalRequests,
  getApprovalRequest,
  cancelApprovalRequest,
  postApproveRequest,
  
  // 대결 위임
  getProxySettings,
  createProxySetting,
  updateProxySetting,
  deleteProxySetting,
  checkActiveProxy,
  
  // 템플릿 단계
  getTemplateSteps,
  createTemplateStep,
  updateTemplateStep,
  deleteTemplateStep,
  
  // 타입
  type ApprovalProxySetting,
  type CreateApprovalRequestInput,
  type ApprovalLineTemplateStep,
} from "@/lib/api/approval";

핵심 로직 설명

동시성 보호 (FOR UPDATE)

결재 처리(processApproval)에서 동시 승인/반려 방지:

SELECT * FROM approval_lines WHERE line_id = $1 FOR UPDATE
SELECT * FROM approval_requests WHERE request_id = $1 FOR UPDATE

대결 자동 감지

결재자가 아닌 사용자가 결재 처리하면:

  1. approval_proxy_settings에서 활성 대결 설정 확인
  2. 대결 설정이 있으면 → proxy_for, proxy_reason 자동 기록
  3. 없으면 → 403 에러

통보 단계 자동 처리

step_type = 'notification'인 단계가 활성화되면:

  1. 해당 단계의 모든 결재자를 자동 approved 처리
  2. comment = '자동 통보 처리' 기록
  3. activateNextStep() 재귀 호출로 다음 단계 진행

합의결재 단계 완료 판정

같은 step_order의 모든 결재자가 approved여야 다음 단계로:

SELECT COUNT(*) FROM approval_lines
WHERE request_id = $1 AND step_order = $2
AND status NOT IN ('approved', 'skipped')

하나라도 rejected면 전체 결재 반려.


메뉴 등록

결재 관련 화면을 메뉴에 등록하려면:

화면 URL 메뉴명 예시
결재함 /admin/approvalBox 결재함
결재 템플릿 관리 /admin/approvalTemplate 결재 템플릿
결재 유형 관리 /admin/approvalMng 결재 유형 (기존)

파일 구조

backend-node/src/
├── controllers/
│   ├── approvalController.ts      # 결재 유형/템플릿/요청/라인 처리
│   └── approvalProxyController.ts # 대결 위임 CRUD
└── routes/
    └── approvalRoutes.ts          # 라우트 등록

frontend/
├── app/(main)/admin/
│   ├── approvalBox/page.tsx       # 결재함 (수신/상신/대결)
│   ├── approvalTemplate/page.tsx  # 템플릿 관리
│   └── approvalMng/page.tsx       # 결재 유형 관리 (기존)
├── components/approval/
│   └── ApprovalRequestModal.tsx   # 결재 요청 모달
└── lib/
    ├── api/approval.ts            # API 클라이언트
    └── registry/components/v2-approval-step/
        ├── ApprovalStepComponent.tsx  # 결재 단계 시각화
        └── types.ts                  # 확장 타입

db/migrations/
├── 1051_approval_system_v2.sql       # v2 스키마 확장
└── 1052_rename_proxy_id_to_id.sql    # PK 컬럼명 통일