# 결재관리 API 명세

> **작성일**: 2026-02-28
> **상태**: Phase 2 구현 완료
> **Base URL**: `/api/admin/approvals`
> **미들웨어**: `web`, `auth`, `hq.member`
> **관련**: [README.md](README.md) | [워크플로우](workflows.md) | [UI 화면](ui-screens.md)

---

## 1. 개요

모든 API는 JSON 응답을 반환한다. 인증은 세션 기반이며, CSRF 토큰이 필요하다.

### 1.1 공통 응답 형식

**성공:**

```json
{
  "success": true,
  "message": "처리 메시지",
  "data": { ... }
}
```

**실패 (400):**

```json
{
  "success": false,
  "message": "에러 메시지"
}
```

### 1.2 공통 헤더

```
Content-Type: application/json
Accept: application/json
X-CSRF-TOKEN: {csrf_token}
```

---

## 2. 목록 조회 API

### 2.1 기안함

내가 기안한 문서 목록을 조회한다.

```
GET /api/admin/approvals/drafts
```

**Query Parameters:**

| 파라미터 | 타입 | 설명 |
|---------|------|------|
| `search` | string | 제목/문서번호 검색 |
| `status` | string | 상태 필터 (`draft`, `pending`, `approved`, `rejected`, `cancelled`, `on_hold`) |
| `is_urgent` | boolean | 긴급 문서만 |
| `date_from` | date | 시작일 (YYYY-MM-DD) |
| `date_to` | date | 종료일 (YYYY-MM-DD) |
| `per_page` | int | 페이지당 건수 (기본 15) |
| `page` | int | 페이지 번호 |

**응답:** Laravel 페이지네이션 형식

```json
{
  "data": [
    {
      "id": 1,
      "document_number": "APR-260228-001",
      "title": "휴가 신청",
      "status": "pending",
      "is_urgent": false,
      "form": { "id": 1, "name": "휴가신청서" },
      "steps": [...],
      "created_at": "2026-02-28T10:00:00",
      "drafted_at": "2026-02-28T10:05:00"
    }
  ],
  "current_page": 1,
  "last_page": 3,
  "per_page": 15,
  "total": 42
}
```

---

### 2.2 결재 대기함

내가 현재 결재해야 할 문서 목록을 조회한다.

```
GET /api/admin/approvals/pending
```

**Query Parameters:**

| 파라미터 | 타입 | 설명 |
|---------|------|------|
| `search` | string | 제목/문서번호 검색 |
| `is_urgent` | boolean | 긴급 문서만 |
| `date_from` | date | 시작일 |
| `date_to` | date | 종료일 |
| `per_page` | int | 페이지당 건수 |

> 현재 사용자가 결재 차례인 문서만 표시된다. 이미 승인/반려한 문서는 표시되지 않는다.

---

### 2.3 처리 완료함

내가 승인 또는 반려한 문서 목록을 조회한다.

```
GET /api/admin/approvals/completed
```

**Query Parameters:**

| 파라미터 | 타입 | 설명 |
|---------|------|------|
| `search` | string | 제목/문서번호 검색 |
| `status` | string | 상태 필터 |
| `date_from` | date | 시작일 |
| `date_to` | date | 종료일 |
| `per_page` | int | 페이지당 건수 |

---

### 2.4 참조함

내가 참조자로 지정된 문서 목록을 조회한다.

```
GET /api/admin/approvals/references
```

**Query Parameters:**

| 파라미터 | 타입 | 설명 |
|---------|------|------|
| `search` | string | 제목/문서번호 검색 |
| `is_read` | string | 열람 상태 필터 (`true`=열람완료, `false`=미열람) |
| `date_from` | date | 시작일 |
| `date_to` | date | 종료일 |
| `per_page` | int | 페이지당 건수 |

---

## 3. CRUD API

### 3.1 상세 조회

```
GET /api/admin/approvals/{id}
```

**응답:**

```json
{
  "success": true,
  "data": {
    "id": 1,
    "tenant_id": 1,
    "document_number": "APR-260228-001",
    "form_id": 1,
    "line_id": null,
    "title": "휴가 신청",
    "content": {},
    "body": "2월 27일~28일 연차 사용 신청합니다.",
    "status": "pending",
    "is_urgent": false,
    "drafter_id": 10,
    "department_id": 3,
    "current_step": 2,
    "drafted_at": "2026-02-28T10:05:00",
    "completed_at": null,
    "recall_reason": null,
    "parent_doc_id": null,
    "form": { "id": 1, "name": "휴가신청서" },
    "drafter": { "id": 10, "name": "홍길동" },
    "line": null,
    "steps": [
      {
        "id": 1,
        "step_order": 1,
        "step_type": "approval",
        "approver_id": 20,
        "approver_name": "김과장",
        "approver_department": "경영지원팀",
        "approver_position": "과장",
        "status": "approved",
        "approval_type": "normal",
        "comment": "승인합니다.",
        "acted_at": "2026-02-28T11:00:00",
        "is_read": false,
        "read_at": null
      },
      {
        "id": 2,
        "step_order": 2,
        "step_type": "approval",
        "approver_id": 30,
        "approver_name": "박부장",
        "approver_department": "경영지원팀",
        "approver_position": "부장",
        "status": "pending",
        "approval_type": "normal",
        "comment": null,
        "acted_at": null,
        "is_read": false,
        "read_at": null
      }
    ]
  }
}
```

---

### 3.2 생성 (임시저장)

```
POST /api/admin/approvals
```

**Request Body:**

```json
{
  "form_id": 1,
  "title": "휴가 신청",
  "body": "2월 27일~28일 연차 사용",
  "is_urgent": false,
  "steps": [
    { "user_id": 20, "step_type": "approval" },
    { "user_id": 30, "step_type": "approval" },
    { "user_id": 40, "step_type": "reference" }
  ]
}
```

**Validation:**

| 필드 | 규칙 |
|------|------|
| `form_id` | required, exists:approval_forms,id |
| `title` | required, string, max:200 |
| `body` | nullable, string |
| `is_urgent` | boolean |
| `steps` | nullable, array |
| `steps.*.user_id` | required_with:steps, exists:users,id |
| `steps.*.step_type` | required_with:steps, in:approval,agreement,reference |

**응답 (201):**

```json
{
  "success": true,
  "message": "결재 문서가 저장되었습니다.",
  "data": { ... }
}
```

---

### 3.3 수정

```
PUT /api/admin/approvals/{id}
```

> `draft` 또는 `rejected` 상태에서만 수정 가능

**Request Body:** (생성과 동일, 모든 필드 선택)

**Validation:**

| 필드 | 규칙 |
|------|------|
| `title` | sometimes, string, max:200 |
| `body` | nullable, string |
| `is_urgent` | boolean |
| `steps` | nullable, array |

---

### 3.4 삭제

```
DELETE /api/admin/approvals/{id}
```

> `draft` 상태에서만 삭제 가능

**응답:**

```json
{
  "success": true,
  "message": "결재 문서가 삭제되었습니다."
}
```

---

## 4. 워크플로우 API

### 4.1 상신

```
POST /api/admin/approvals/{id}/submit
```

> 기안자가 `draft`/`rejected` 문서를 결재 요청한다.

**Request Body:** 없음

**응답:** `{ "success": true, "message": "결재가 상신되었습니다.", "data": {...} }`

---

### 4.2 승인

```
POST /api/admin/approvals/{id}/approve
```

> 현재 결재자가 승인한다.

**Request Body:**

```json
{
  "comment": "승인합니다."  // 선택
}
```

**응답:** `{ "success": true, "message": "승인되었습니다.", "data": {...} }`

---

### 4.3 반려

```
POST /api/admin/approvals/{id}/reject
```

> 현재 결재자가 반려한다. 사유 필수.

**Request Body:**

```json
{
  "comment": "예산 초과로 반려합니다."  // 필수
}
```

**Validation:** `comment` — required, string, max:1000

**응답:** `{ "success": true, "message": "반려되었습니다.", "data": {...} }`

---

### 4.4 회수

```
POST /api/admin/approvals/{id}/cancel
```

> 기안자가 `pending`/`on_hold` 문서를 회수한다. 첫 결재자 미처리 시에만 가능.

**Request Body:**

```json
{
  "recall_reason": "내용 수정 필요"  // 선택
}
```

**응답:** `{ "success": true, "message": "결재가 회수되었습니다.", "data": {...} }`

---

### 4.5 보류

```
POST /api/admin/approvals/{id}/hold
```

> 현재 결재자가 결재를 보류한다. 사유 필수.

**Request Body:**

```json
{
  "comment": "추가 자료 검토 필요"  // 필수
}
```

**Validation:** `comment` — required, string, max:1000

**응답:** `{ "success": true, "message": "보류되었습니다.", "data": {...} }`

---

### 4.6 보류 해제

```
POST /api/admin/approvals/{id}/release-hold
```

> 보류한 결재자가 보류를 해제한다.

**Request Body:** 없음

**응답:** `{ "success": true, "message": "보류가 해제되었습니다.", "data": {...} }`

---

### 4.7 전결

```
POST /api/admin/approvals/{id}/pre-decide
```

> 현재 결재자가 이후 모든 결재를 건너뛰고 최종 승인한다.

**Request Body:**

```json
{
  "comment": "전결 처리합니다."  // 선택
}
```

**응답:** `{ "success": true, "message": "전결 처리되었습니다.", "data": {...} }`

---

### 4.8 복사 재기안

```
POST /api/admin/approvals/{id}/copy
```

> 기안자가 `approved`/`rejected`/`cancelled` 문서를 복사하여 새 draft를 생성한다.

**Request Body:** 없음

**응답:**

```json
{
  "success": true,
  "message": "문서가 복사되었습니다.",
  "data": {
    "id": 15,
    "document_number": "APR-260228-003",
    "parent_doc_id": 1,
    "status": "draft",
    ...
  }
}
```

> 응답의 `data.id`를 사용하여 `/approval-mgmt/{id}/edit`로 이동한다.

---

### 4.9 참조 열람 추적

```
POST /api/admin/approvals/{id}/mark-read
```

> 참조자가 문서를 열람했음을 기록한다.

**Request Body:** 없음

**응답:** `{ "success": true, "message": "열람 처리되었습니다." }`

---

## 5. 유틸리티 API

### 5.1 결재선 템플릿 목록

```
GET /api/admin/approvals/lines
```

**응답:**

```json
{
  "success": true,
  "data": [
    { "id": 1, "name": "일반 결재선", "steps": [...] }
  ]
}
```

---

### 5.2 양식 목록

```
GET /api/admin/approvals/forms
```

**응답:**

```json
{
  "success": true,
  "data": [
    { "id": 1, "name": "휴가신청서", "is_active": true }
  ]
}
```

---

### 5.3 미처리 건수 (뱃지)

```
GET /api/admin/approvals/badge-counts
```

**응답:**

```json
{
  "success": true,
  "data": {
    "pending": 3,
    "draft": 1,
    "reference_unread": 5
  }
}
```

| 필드 | 설명 |
|------|------|
| `pending` | 내가 결재해야 할 문서 수 |
| `draft` | 내 임시저장 문서 수 |
| `reference_unread` | 미열람 참조 문서 수 |

---

## 6. 라우트 전체 목록

| Method | Path | 컨트롤러 메서드 | 이름 | 설명 |
|--------|------|---------------|------|------|
| GET | `/drafts` | `drafts` | `drafts` | 기안함 |
| GET | `/pending` | `pending` | `pending` | 결재 대기함 |
| GET | `/completed` | `completed` | `completed` | 처리 완료함 |
| GET | `/references` | `references` | `references` | 참조함 |
| GET | `/lines` | `lines` | `lines` | 결재선 템플릿 |
| GET | `/forms` | `forms` | `forms` | 양식 목록 |
| GET | `/badge-counts` | `badgeCounts` | `badge-counts` | 뱃지 건수 |
| POST | `/` | `store` | `store` | 생성 |
| GET | `/{id}` | `show` | `show` | 상세 |
| PUT | `/{id}` | `update` | `update` | 수정 |
| DELETE | `/{id}` | `destroy` | `destroy` | 삭제 |
| POST | `/{id}/submit` | `submit` | `submit` | 상신 |
| POST | `/{id}/approve` | `approve` | `approve` | 승인 |
| POST | `/{id}/reject` | `reject` | `reject` | 반려 |
| POST | `/{id}/cancel` | `cancel` | `cancel` | 회수 |
| POST | `/{id}/hold` | `hold` | `hold` | 보류 |
| POST | `/{id}/release-hold` | `releaseHold` | `release-hold` | 보류 해제 |
| POST | `/{id}/pre-decide` | `preDecide` | `pre-decide` | 전결 |
| POST | `/{id}/copy` | `copyForRedraft` | `copy` | 복사 재기안 |
| POST | `/{id}/mark-read` | `markAsRead` | `mark-read` | 열람 추적 |

---

## 관련 문서

- [README.md](README.md) — 시스템 전체 개요
- [워크플로우 상세](workflows.md) — 각 동작의 상세 흐름
- [UI 화면 구성](ui-screens.md) — 화면별 동작

---

**최종 업데이트**: 2026-02-28
