343 lines
12 KiB
Markdown
343 lines
12 KiB
Markdown
|
|
# 결재 시스템 구현 현황
|
||
|
|
|
||
|
|
## 1. 개요
|
||
|
|
|
||
|
|
어떤 화면/테이블에서든 결재 버튼을 추가하여 다단계(순차) 및 다중(병렬) 결재를 처리할 수 있는 범용 결재 시스템.
|
||
|
|
|
||
|
|
### 핵심 특징
|
||
|
|
- **범용성**: 특정 테이블에 종속되지 않고 어떤 화면에서든 사용 가능
|
||
|
|
- **멀티테넌시**: 모든 데이터가 `company_code`로 격리
|
||
|
|
- **사용자 주도**: 결재 요청 시 결재 모드/결재자를 직접 설정 (관리자 사전 세팅 불필요)
|
||
|
|
- **컴포넌트 연동**: 버튼 액션 타입 + 결재 단계 시각화 컴포넌트 제공
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 아키텍처
|
||
|
|
|
||
|
|
```
|
||
|
|
[버튼 클릭 (approval 액션)]
|
||
|
|
↓
|
||
|
|
[ButtonActionExecutor] → CustomEvent('open-approval-modal') 발송
|
||
|
|
↓
|
||
|
|
[ApprovalGlobalListener] → 이벤트 수신
|
||
|
|
↓
|
||
|
|
[ApprovalRequestModal] → 결재 모드/결재자 선택 UI
|
||
|
|
↓
|
||
|
|
[POST /api/approval/requests] → 결재 요청 생성
|
||
|
|
↓
|
||
|
|
[approval_requests + approval_lines 테이블에 저장]
|
||
|
|
↓
|
||
|
|
[결재함 / 결재 단계 컴포넌트에서 조회 및 처리]
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 데이터베이스
|
||
|
|
|
||
|
|
### 마이그레이션 파일
|
||
|
|
- `db/migrations/100_create_approval_system.sql`
|
||
|
|
|
||
|
|
### 테이블 구조
|
||
|
|
|
||
|
|
| 테이블 | 용도 | 주요 컬럼 |
|
||
|
|
|--------|------|-----------|
|
||
|
|
| `approval_definitions` | 결재 유형 정의 (구매결재, 문서결재 등) | definition_id, definition_name, max_steps, company_code |
|
||
|
|
| `approval_line_templates` | 결재선 템플릿 (미리 저장된 결재선) | template_id, template_name, definition_id, company_code |
|
||
|
|
| `approval_line_template_steps` | 템플릿별 결재 단계 | step_id, template_id, step_order, approver_user_id, company_code |
|
||
|
|
| `approval_requests` | 실제 결재 요청 건 | request_id, title, target_table, target_record_id, status, requester_id, company_code |
|
||
|
|
| `approval_lines` | 결재 건별 각 단계 결재자 | line_id, request_id, step_order, approver_id, status, comment, company_code |
|
||
|
|
|
||
|
|
### 결재 상태 흐름
|
||
|
|
|
||
|
|
```
|
||
|
|
[requested] → [in_progress] → [approved] (모든 단계 승인)
|
||
|
|
→ [rejected] (어느 단계에서든 반려)
|
||
|
|
→ [cancelled] (요청자가 취소)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### approval_requests.status
|
||
|
|
| 상태 | 의미 |
|
||
|
|
|------|------|
|
||
|
|
| `requested` | 결재 요청됨 (1단계 결재자 처리 대기) |
|
||
|
|
| `in_progress` | 결재 진행 중 (2단계 이상 진행) |
|
||
|
|
| `approved` | 최종 승인 완료 |
|
||
|
|
| `rejected` | 반려됨 |
|
||
|
|
| `cancelled` | 요청자에 의해 취소 |
|
||
|
|
|
||
|
|
#### approval_lines.status
|
||
|
|
| 상태 | 의미 |
|
||
|
|
|------|------|
|
||
|
|
| `waiting` | 아직 차례가 아님 |
|
||
|
|
| `pending` | 현재 결재 차례 (처리 대기) |
|
||
|
|
| `approved` | 승인 완료 |
|
||
|
|
| `rejected` | 반려 |
|
||
|
|
| `skipped` | 이전 단계 반려로 스킵됨 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 백엔드 API
|
||
|
|
|
||
|
|
### 파일 위치
|
||
|
|
- **컨트롤러**: `backend-node/src/controllers/approvalController.ts`
|
||
|
|
- **라우트**: `backend-node/src/routes/approvalRoutes.ts`
|
||
|
|
|
||
|
|
### API 엔드포인트
|
||
|
|
|
||
|
|
#### 결재 유형 (Definitions)
|
||
|
|
| 메서드 | 경로 | 설명 |
|
||
|
|
|--------|------|------|
|
||
|
|
| GET | `/api/approval/definitions` | 결재 유형 목록 |
|
||
|
|
| GET | `/api/approval/definitions/:id` | 결재 유형 상세 |
|
||
|
|
| POST | `/api/approval/definitions` | 결재 유형 생성 |
|
||
|
|
| PUT | `/api/approval/definitions/:id` | 결재 유형 수정 |
|
||
|
|
| DELETE | `/api/approval/definitions/:id` | 결재 유형 삭제 |
|
||
|
|
|
||
|
|
#### 결재선 템플릿 (Templates)
|
||
|
|
| 메서드 | 경로 | 설명 |
|
||
|
|
|--------|------|------|
|
||
|
|
| GET | `/api/approval/templates` | 템플릿 목록 |
|
||
|
|
| GET | `/api/approval/templates/:id` | 템플릿 상세 (단계 포함) |
|
||
|
|
| POST | `/api/approval/templates` | 템플릿 생성 |
|
||
|
|
| PUT | `/api/approval/templates/:id` | 템플릿 수정 |
|
||
|
|
| DELETE | `/api/approval/templates/:id` | 템플릿 삭제 |
|
||
|
|
|
||
|
|
#### 결재 요청 (Requests)
|
||
|
|
| 메서드 | 경로 | 설명 |
|
||
|
|
|--------|------|------|
|
||
|
|
| GET | `/api/approval/requests` | 결재 요청 목록 (필터 가능) |
|
||
|
|
| GET | `/api/approval/requests/:id` | 결재 요청 상세 (결재 라인 포함) |
|
||
|
|
| POST | `/api/approval/requests` | 결재 요청 생성 |
|
||
|
|
| POST | `/api/approval/requests/:id/cancel` | 결재 취소 |
|
||
|
|
|
||
|
|
#### 결재 라인 처리 (Lines)
|
||
|
|
| 메서드 | 경로 | 설명 |
|
||
|
|
|--------|------|------|
|
||
|
|
| GET | `/api/approval/my-pending` | 내 미처리 결재 목록 |
|
||
|
|
| POST | `/api/approval/lines/:lineId/process` | 승인/반려 처리 |
|
||
|
|
|
||
|
|
### 결재 요청 생성 시 입력
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface CreateApprovalRequestInput {
|
||
|
|
title: string; // 결재 제목
|
||
|
|
description?: string; // 결재 설명
|
||
|
|
target_table: string; // 대상 테이블명 (예: sales_order_mng)
|
||
|
|
target_record_id?: string; // 대상 레코드 ID (선택)
|
||
|
|
approval_mode?: "sequential" | "parallel"; // 결재 모드
|
||
|
|
approvers: { // 결재자 목록
|
||
|
|
approver_id: string;
|
||
|
|
approver_name?: string;
|
||
|
|
approver_position?: string;
|
||
|
|
approver_dept?: string;
|
||
|
|
}[];
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 결재 처리 로직
|
||
|
|
|
||
|
|
#### 순차 결재 (sequential)
|
||
|
|
1. 첫 번째 결재자 `status = 'pending'`, 나머지 `'waiting'`
|
||
|
|
2. 1단계 승인 → 2단계 `'pending'`으로 변경
|
||
|
|
3. 모든 단계 승인 → `approval_requests.status = 'approved'`
|
||
|
|
4. 어느 단계에서 반려 → 이후 단계 `'skipped'`, 요청 `'rejected'`
|
||
|
|
|
||
|
|
#### 병렬 결재 (parallel)
|
||
|
|
1. 모든 결재자 `status = 'pending'` (동시 처리)
|
||
|
|
2. 모든 결재자 승인 → `'approved'`
|
||
|
|
3. 한 명이라도 반려 → `'rejected'`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. 프론트엔드
|
||
|
|
|
||
|
|
### 5.1 결재 요청 모달
|
||
|
|
|
||
|
|
**파일**: `frontend/components/approval/ApprovalRequestModal.tsx`
|
||
|
|
|
||
|
|
- 결재 모드 선택 (다단 결재 / 다중 결재)
|
||
|
|
- 결재자 검색 (사용자 API 검색, 한글/영문/ID 검색 가능)
|
||
|
|
- 결재자 추가/삭제, 순서 변경 (순차 결재 시)
|
||
|
|
- 대상 테이블/레코드 ID 자동 세팅
|
||
|
|
|
||
|
|
### 5.2 결재 글로벌 리스너
|
||
|
|
|
||
|
|
**파일**: `frontend/components/approval/ApprovalGlobalListener.tsx`
|
||
|
|
|
||
|
|
- `open-approval-modal` CustomEvent를 전역으로 수신
|
||
|
|
- 이벤트의 `detail`에서 `targetTable`, `targetRecordId`, `formData` 추출
|
||
|
|
- `ApprovalRequestModal` 열기
|
||
|
|
|
||
|
|
### 5.3 결재함 페이지
|
||
|
|
|
||
|
|
**파일**: `frontend/app/(main)/admin/approvalBox/page.tsx`
|
||
|
|
|
||
|
|
- 탭 구성: 보낸 결재 / 받은 결재 / 완료된 결재
|
||
|
|
- 결재 상태별 필터링
|
||
|
|
- 결재 상세 조회 및 승인/반려 처리
|
||
|
|
|
||
|
|
**진입점**: 사용자 프로필 드롭다운 > "결재함"
|
||
|
|
|
||
|
|
### 5.4 결재 단계 시각화 컴포넌트 (v2-approval-step)
|
||
|
|
|
||
|
|
**파일 위치**: `frontend/lib/registry/components/v2-approval-step/`
|
||
|
|
|
||
|
|
| 파일 | 역할 |
|
||
|
|
|------|------|
|
||
|
|
| `types.ts` | ApprovalStepConfig 타입 정의 |
|
||
|
|
| `ApprovalStepComponent.tsx` | 결재 단계 시각화 UI (가로형 스테퍼 / 세로형 타임라인) |
|
||
|
|
| `ApprovalStepConfigPanel.tsx` | 설정 패널 (대상 테이블/컬럼 Combobox, 표시 옵션) |
|
||
|
|
| `ApprovalStepRenderer.tsx` | 컴포넌트 레지스트리 등록 |
|
||
|
|
| `index.ts` | 컴포넌트 정의 (이름, 태그, 기본값 등) |
|
||
|
|
|
||
|
|
#### 설정 항목
|
||
|
|
| 설정 | 설명 |
|
||
|
|
|------|------|
|
||
|
|
| 대상 테이블 | 결재를 걸 데이터가 있는 테이블 (Combobox 검색) |
|
||
|
|
| 레코드 ID 필드명 | 테이블의 PK 컬럼 (Combobox 검색) |
|
||
|
|
| 표시 모드 | 가로형 스테퍼 / 세로형 타임라인 |
|
||
|
|
| 부서/직급 표시 | 결재자의 부서/직급 정보 표시 여부 |
|
||
|
|
| 결재 코멘트 표시 | 승인/반려 시 입력한 코멘트 표시 여부 |
|
||
|
|
| 처리 시각 표시 | 결재 처리 시각 표시 여부 |
|
||
|
|
| 콤팩트 모드 | 작게 표시 |
|
||
|
|
|
||
|
|
### 5.5 API 클라이언트
|
||
|
|
|
||
|
|
**파일**: `frontend/lib/api/approval.ts`
|
||
|
|
|
||
|
|
| 함수 | 용도 |
|
||
|
|
|------|------|
|
||
|
|
| `getApprovalDefinitions()` | 결재 유형 목록 조회 |
|
||
|
|
| `getApprovalTemplates()` | 결재선 템플릿 목록 조회 |
|
||
|
|
| `getApprovalRequests()` | 결재 요청 목록 조회 (필터 지원) |
|
||
|
|
| `getApprovalRequest(id)` | 결재 요청 상세 조회 |
|
||
|
|
| `createApprovalRequest(data)` | 결재 요청 생성 |
|
||
|
|
| `cancelApprovalRequest(id)` | 결재 취소 |
|
||
|
|
| `getMyPendingApprovals()` | 내 미처리 결재 목록 |
|
||
|
|
| `processApprovalLine(lineId, data)` | 승인/반려 처리 |
|
||
|
|
|
||
|
|
### 5.6 버튼 액션 연동
|
||
|
|
|
||
|
|
#### 관련 파일
|
||
|
|
| 파일 | 수정 내용 |
|
||
|
|
|------|-----------|
|
||
|
|
| `frontend/lib/utils/buttonActions.ts` | `ButtonActionType`에 `"approval"` 추가, `handleApproval` 구현 |
|
||
|
|
| `frontend/lib/utils/improvedButtonActionExecutor.ts` | `approval` 액션 핸들러 추가 |
|
||
|
|
| `frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx` | `silentActions`에 `"approval"` 추가 |
|
||
|
|
| `frontend/components/screen/config-panels/ButtonConfigPanel.tsx` | 결재 액션 설정 UI (대상 테이블 자동 세팅) |
|
||
|
|
|
||
|
|
#### 동작 흐름
|
||
|
|
1. 버튼 설정에서 액션 타입 = `"approval"` 선택
|
||
|
|
2. 대상 테이블 자동 설정 (현재 화면 테이블)
|
||
|
|
3. 버튼 클릭 시 `CustomEvent('open-approval-modal')` 발송
|
||
|
|
4. `ApprovalGlobalListener`가 수신하여 `ApprovalRequestModal` 오픈
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. 멀티테넌시 적용
|
||
|
|
|
||
|
|
| 영역 | 적용 |
|
||
|
|
|------|------|
|
||
|
|
| DB 테이블 | 모든 테이블에 `company_code NOT NULL` 포함 |
|
||
|
|
| 인덱스 | `company_code` 컬럼에 인덱스 생성 |
|
||
|
|
| SELECT | `WHERE company_code = $N` 필수 |
|
||
|
|
| INSERT | `company_code` 값 포함 필수 |
|
||
|
|
| UPDATE/DELETE | `WHERE` 절에 `company_code` 조건 포함 |
|
||
|
|
| 최고관리자 | `company_code = '*'` → 모든 데이터 조회 가능 |
|
||
|
|
| JOIN | `ON` 절에 `company_code` 매칭 포함 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. 전체 파일 목록
|
||
|
|
|
||
|
|
### 데이터베이스
|
||
|
|
```
|
||
|
|
db/migrations/100_create_approval_system.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
### 백엔드
|
||
|
|
```
|
||
|
|
backend-node/src/controllers/approvalController.ts
|
||
|
|
backend-node/src/routes/approvalRoutes.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
### 프론트엔드 - 결재 모달/리스너
|
||
|
|
```
|
||
|
|
frontend/components/approval/ApprovalRequestModal.tsx
|
||
|
|
frontend/components/approval/ApprovalGlobalListener.tsx
|
||
|
|
```
|
||
|
|
|
||
|
|
### 프론트엔드 - 결재함 페이지
|
||
|
|
```
|
||
|
|
frontend/app/(main)/admin/approvalBox/page.tsx
|
||
|
|
```
|
||
|
|
|
||
|
|
### 프론트엔드 - 결재 단계 컴포넌트
|
||
|
|
```
|
||
|
|
frontend/lib/registry/components/v2-approval-step/types.ts
|
||
|
|
frontend/lib/registry/components/v2-approval-step/ApprovalStepComponent.tsx
|
||
|
|
frontend/lib/registry/components/v2-approval-step/ApprovalStepConfigPanel.tsx
|
||
|
|
frontend/lib/registry/components/v2-approval-step/ApprovalStepRenderer.tsx
|
||
|
|
frontend/lib/registry/components/v2-approval-step/index.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
### 프론트엔드 - API 클라이언트
|
||
|
|
```
|
||
|
|
frontend/lib/api/approval.ts
|
||
|
|
```
|
||
|
|
|
||
|
|
### 프론트엔드 - 버튼 액션 연동 (수정된 파일)
|
||
|
|
```
|
||
|
|
frontend/lib/utils/buttonActions.ts
|
||
|
|
frontend/lib/utils/improvedButtonActionExecutor.ts
|
||
|
|
frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx
|
||
|
|
frontend/components/screen/config-panels/ButtonConfigPanel.tsx
|
||
|
|
```
|
||
|
|
|
||
|
|
### 프론트엔드 - 레이아웃 (수정된 파일)
|
||
|
|
```
|
||
|
|
frontend/components/layout/UserDropdown.tsx (결재함 메뉴 추가)
|
||
|
|
frontend/components/layout/AppLayout.tsx (결재함 메뉴 추가)
|
||
|
|
frontend/lib/registry/components/index.ts (v2-approval-step 렌더러 import)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. 사용 방법
|
||
|
|
|
||
|
|
### 결재 버튼 추가
|
||
|
|
1. 화면 디자이너에서 버튼 컴포넌트 추가
|
||
|
|
2. 버튼 설정 > 액션 타입 = `결재` 선택
|
||
|
|
3. 대상 테이블이 자동 설정됨 (수동 변경 가능)
|
||
|
|
4. 저장
|
||
|
|
|
||
|
|
### 결재 요청하기
|
||
|
|
1. 데이터 행 선택 (선택적)
|
||
|
|
2. 결재 버튼 클릭
|
||
|
|
3. 결재 모달에서:
|
||
|
|
- 결재 제목 입력
|
||
|
|
- 결재 모드 선택 (다단 결재 / 다중 결재)
|
||
|
|
- 결재자 검색하여 추가
|
||
|
|
4. 결재 요청 클릭
|
||
|
|
|
||
|
|
### 결재 처리하기
|
||
|
|
1. 프로필 드롭다운 > 결재함 클릭
|
||
|
|
2. 받은 결재 탭에서 대기 중인 결재 확인
|
||
|
|
3. 상세 보기 > 승인 또는 반려
|
||
|
|
|
||
|
|
### 결재 단계 표시하기
|
||
|
|
1. 화면 디자이너에서 `결재 단계` 컴포넌트 추가
|
||
|
|
2. 설정에서 대상 테이블 / 레코드 ID 필드 선택
|
||
|
|
3. 표시 모드 (가로/세로) 및 옵션 설정
|
||
|
|
4. 저장 → 행 선택 시 해당 레코드의 결재 단계가 표시됨
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. 향후 개선 사항
|
||
|
|
|
||
|
|
- [ ] 결재 알림 (실시간 알림, 이메일 연동)
|
||
|
|
- [ ] 제어관리 시스템 연동 (결재 완료 후 자동 액션)
|
||
|
|
- [ ] 결재 위임 기능
|
||
|
|
- [ ] 결재 이력 조회 / 통계 대시보드
|
||
|
|
- [ ] 결재선 즐겨찾기 (자주 쓰는 결재선 저장)
|
||
|
|
- [ ] 모바일 결재 처리 최적화
|