# 급여관리 API 명세

> **작성일**: 2026-03-11
> **상태**: 개발 완료 (API 배포됨)
> **Base URL**: `api.codebridge-x.com` (운영) / `api.dev.codebridge-x.com` (개발)

---

## 1. 개요

월별 급여를 등록/확정/지급하고, 4대보험과 근로소득세를 자동 계산하는 급여관리 API이다.

### 1.1 인증

모든 요청에 다음 헤더가 필요하다:

```
X-API-KEY: {api_key}
Authorization: Bearer {token}
```

### 1.2 공통 응답 형식

```json
{
  "success": true,
  "message": "조회 성공",
  "data": { ... }
}
```

에러 시:

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

### 1.3 엔드포인트 요약

| # | Method | Path | 설명 |
|---|--------|------|------|
| 1 | GET | `/api/v1/payrolls` | 급여 목록 (페이지네이션) |
| 2 | POST | `/api/v1/payrolls` | 급여 등록 |
| 3 | GET | `/api/v1/payrolls/summary` | 월간 요약 통계 |
| 4 | POST | `/api/v1/payrolls/calculate` | 급여 일괄 계산 (draft 재계산) |
| 5 | POST | `/api/v1/payrolls/calculate-preview` | 계산 미리보기 (저장 안 함) |
| 6 | POST | `/api/v1/payrolls/bulk-confirm` | 일괄 확정 |
| 7 | POST | `/api/v1/payrolls/bulk-generate` | 재직사원 일괄 생성 |
| 8 | POST | `/api/v1/payrolls/copy-from-previous` | 전월 급여 복사 |
| 9 | GET | `/api/v1/payrolls/{id}` | 급여 상세 |
| 10 | PUT | `/api/v1/payrolls/{id}` | 급여 수정 |
| 11 | DELETE | `/api/v1/payrolls/{id}` | 급여 삭제 |
| 12 | POST | `/api/v1/payrolls/{id}/confirm` | 확정 |
| 13 | POST | `/api/v1/payrolls/{id}/unconfirm` | 확정 취소 |
| 14 | POST | `/api/v1/payrolls/{id}/pay` | 지급 처리 |
| 15 | POST | `/api/v1/payrolls/{id}/unpay` | 지급 취소 (슈퍼관리자) |
| 16 | GET | `/api/v1/payrolls/{id}/payslip` | 급여명세서 조회 |
| 17 | GET | `/api/v1/payrolls/settings` | 급여 설정 조회 |
| 18 | PUT | `/api/v1/payrolls/settings` | 급여 설정 수정 |

---

## 2. 상태 흐름도

```
┌────────────────┐
│  draft (작성중) │
└───┬──────┬─────┘
    │      ▲
    │ confirm  │ unconfirm
    ▼      │
┌────────────────┐
│confirmed (확정) │
└───┬──────┬─────┘
    │      ▲
    │ pay      │ unpay*
    ▼      │
┌────────────────┐
│  paid (지급완료)│
└────────────────┘

* unpay는 슈퍼관리자 전용 (paid → draft 초기화)
```

### 2.1 상태값

| 상태 | 값 | 설명 | UI 배지 색상 |
|------|---|------|-------------|
| 작성중 | `draft` | 수정/삭제/확정 가능 | gray |
| 확정 | `confirmed` | 확정취소/지급 가능 | blue |
| 지급완료 | `paid` | 상세보기만 가능 | green |

### 2.2 상태별 가능한 작업

| 상태 | 일반 사용자 | 슈퍼관리자 |
|------|-----------|-----------|
| `draft` | 수정, 삭제, 확정, 일괄계산 | 동일 |
| `confirmed` | 확정취소, 지급처리 | + **수정** |
| `paid` | 상세보기만 | + **수정**, **지급취소** |

---

## 3. 급여 계산 엔진

### 3.1 과세표준

```
과세표준 = 총지급액(gross_salary) - 식대(bonus)
```

- `bonus` 필드는 **식대(비과세)** 항목이다
- 4대보험과 세금은 과세표준 기준으로 계산한다

### 3.2 4대보험 자동 계산

| 항목 | 계산식 | 기본 요율 | 비고 |
|------|--------|----------|------|
| 건강보험 | `과세표준 × 3.545%` | 3.545% | 10원 단위 절삭 |
| 장기요양보험 | `건강보험료 × 0.9082%` | 0.9082% | 건강보험료 기준, 10원 단위 절삭 |
| 국민연금 | `과세표준 × 4.5%` | 4.5% | 상한 590만 / 하한 37만 적용 |
| 고용보험 | `과세표준 × 0.9%` | 0.9% | 10원 단위 절삭 |

> **모든 보험료는 10원 단위 절삭** (floor to nearest 10)

### 3.3 근로소득세

- **770천원 미만**: 0원
- **770~10,000천원**: 간이세액표(DB) 조회 (연도, 월급여 천원단위, 가족수)
- **10,000천원 초과**: 공식 계산 (소득세법 시행령 별표2)

### 3.4 지방소득세

```
지방소득세 = 근로소득세 × 10% (10원 단위 절삭)
```

### 3.5 가족수 (공제대상가족수)

- 기본: 본인 1인
- TenantUserProfile의 `json_extra.dependents` 배열에서 `is_dependent: true`인 수만큼 추가
- 범위: 최소 1, 최대 11

### 3.6 요율 설정

요율은 `payroll_settings` 테이블에서 테넌트별로 관리한다. 기본값은 위 표와 동일하며, 설정 API(17, 18번)를 통해 변경 가능하다.

---

## 4. 데이터 모델

### 4.1 Payroll 객체

```json
{
  "id": 1,
  "tenant_id": 1,
  "user_id": 5,
  "pay_year": 2026,
  "pay_month": 3,
  "base_salary": "3500000",
  "overtime_pay": "0",
  "bonus": "200000",
  "allowances": [
    {"name": "직책수당", "amount": 300000},
    {"name": "교통비", "amount": 100000}
  ],
  "gross_salary": "4100000",
  "income_tax": "117750",
  "resident_tax": "11770",
  "health_insurance": "138220",
  "long_term_care": "1250",
  "pension": "175500",
  "employment_insurance": "35100",
  "deductions": [
    {"name": "대출상환", "amount": 500000}
  ],
  "options": null,
  "total_deductions": "979590",
  "net_salary": "3120410",
  "status": "draft",
  "confirmed_at": null,
  "confirmed_by": null,
  "paid_at": null,
  "withdrawal_id": null,
  "note": null,
  "created_by": 1,
  "updated_by": 1,
  "created_at": "2026-03-11T10:00:00.000000Z",
  "updated_at": "2026-03-11T10:00:00.000000Z",
  "user": {
    "id": 5,
    "name": "홍길동",
    "email": "hong@example.com"
  },
  "creator": {
    "id": 1,
    "name": "관리자"
  }
}
```

### 4.2 필드 설명

**지급 항목:**

| 필드 | 타입 | 설명 |
|------|------|------|
| `base_salary` | decimal(0) | 기본급 |
| `overtime_pay` | decimal(0) | 고정연장근로수당 |
| `bonus` | decimal(0) | 식대 (비과세) |
| `allowances` | json | 추가 수당 `[{name, amount}]` |
| `gross_salary` | decimal(0) | 총지급액 (자동 계산) |

**법정 공제 항목 (자동 계산):**

| 필드 | 타입 | 설명 |
|------|------|------|
| `income_tax` | decimal(0) | 근로소득세 |
| `resident_tax` | decimal(0) | 지방소득세 |
| `health_insurance` | decimal(0) | 건강보험료 |
| `long_term_care` | decimal(0) | 장기요양보험료 |
| `pension` | decimal(0) | 국민연금 |
| `employment_insurance` | decimal(0) | 고용보험료 |

**기타 공제:**

| 필드 | 타입 | 설명 |
|------|------|------|
| `deductions` | json | 기타공제 `[{name, amount}]` |

**결과:**

| 필드 | 타입 | 설명 |
|------|------|------|
| `total_deductions` | decimal(0) | 총공제액 (법정 + 기타) |
| `net_salary` | decimal(0) | 실수령액 = gross - total_deductions |

**상태:**

| 필드 | 타입 | 설명 |
|------|------|------|
| `status` | string | `draft` / `confirmed` / `paid` |
| `confirmed_at` | datetime | 확정 일시 |
| `confirmed_by` | int | 확정자 user_id |
| `paid_at` | datetime | 지급 일시 |
| `withdrawal_id` | int | 연결된 출금 ID |

---

## 5. API 상세

### 5.1 급여 목록 조회

```
GET /api/v1/payrolls
```

**Query Parameters:**

| 파라미터 | 타입 | 필수 | 설명 | 예시 |
|---------|------|------|------|------|
| `year` | int | - | 귀속연도 | `2026` |
| `month` | int | - | 귀속월 | `3` |
| `user_id` | int | - | 사원 ID | `5` |
| `status` | string | - | 상태 필터 | `draft` |
| `department_id` | int | - | 부서 필터 | `2` |
| `search` | string | - | 사원명 검색 | `홍길동` |
| `sort_by` | string | - | 정렬 기준 (기본: `pay_year`) | `net_salary` |
| `sort_dir` | string | - | 정렬 방향 (기본: `desc`) | `asc` |
| `per_page` | int | - | 페이지당 건수 (기본: 20) | `50` |
| `page` | int | - | 페이지 번호 | `1` |

**응답 (200):**

```json
{
  "success": true,
  "message": "조회 성공",
  "data": {
    "current_page": 1,
    "data": [
      {
        "id": 1,
        "user_id": 5,
        "pay_year": 2026,
        "pay_month": 3,
        "base_salary": "3500000",
        "gross_salary": "4100000",
        "total_deductions": "979590",
        "net_salary": "3120410",
        "status": "draft",
        "user": {"id": 5, "name": "홍길동", "email": "hong@example.com"},
        "creator": {"id": 1, "name": "관리자"}
      }
    ],
    "per_page": 20,
    "total": 15,
    "last_page": 1
  }
}
```

**프론트엔드 참고:**
- 연월을 선택하는 UI를 제공하면 `year` + `month` 필터 사용
- `sort_by=period` 전달 시 `pay_year` + `pay_month` 복합 정렬

---

### 5.2 급여 등록

```
POST /api/v1/payrolls
```

**Request Body:**

```json
{
  "user_id": 5,
  "pay_year": 2026,
  "pay_month": 3,
  "base_salary": 3500000,
  "overtime_pay": 0,
  "bonus": 200000,
  "allowances": [
    {"name": "직책수당", "amount": 300000}
  ],
  "deductions": [
    {"name": "대출상환", "amount": 500000}
  ],
  "family_count": 3,
  "deduction_overrides": {
    "income_tax": 100000,
    "pension": 170000
  },
  "note": "메모"
}
```

**필드 규칙:**

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `user_id` | int | ✅ | 급여 대상 사원 ID |
| `pay_year` | int | ✅ | 귀속연도 (2000~2100) |
| `pay_month` | int | ✅ | 귀속월 (1~12) |
| `base_salary` | numeric | ✅ | 기본급 (0 이상) |
| `overtime_pay` | numeric | - | 고정연장근로수당 |
| `bonus` | numeric | - | 식대 (비과세) |
| `allowances` | array | - | 추가수당 `[{name, amount}]` |
| `deductions` | array | - | 기타공제 `[{name, amount}]` |
| `family_count` | int | - | 공제대상가족수 (1~11, 미전달 시 자동) |
| `deduction_overrides` | object | - | 법정공제 수동 입력 (아래 참고) |
| `note` | string | - | 메모 (최대 1000자) |

**`deduction_overrides` 필드:**

자동 계산된 법정 공제 항목을 수동으로 덮어쓸 때 사용한다. 지정한 항목만 덮어쓰고, 나머지는 자동 계산값을 유지한다.

```json
{
  "income_tax": 100000,
  "resident_tax": 10000,
  "health_insurance": 140000,
  "long_term_care": 1300,
  "pension": 170000,
  "employment_insurance": 35000
}
```

**응답 (201):**

```json
{
  "success": true,
  "message": "등록 성공",
  "data": { /* Payroll 객체 (4.1 참고) */ }
}
```

**에러:**

| 상황 | 응답 코드 | 메시지 |
|------|----------|--------|
| 동일 연월+사원 중복 | 400 | 해당 연월에 이미 급여가 등록되어 있습니다. |
| 검증 실패 | 422 | 요청 데이터 검증에 실패했습니다. |

---

### 5.3 월간 요약 통계

```
GET /api/v1/payrolls/summary?year=2026&month=3
```

**Query Parameters:**

| 파라미터 | 타입 | 필수 | 기본값 |
|---------|------|------|--------|
| `year` | int | - | 현재 연도 |
| `month` | int | - | 현재 월 |

**응답 (200):**

```json
{
  "success": true,
  "message": "조회 성공",
  "data": {
    "year": 2026,
    "month": 3,
    "total_count": 15,
    "draft_count": 3,
    "confirmed_count": 7,
    "paid_count": 5,
    "total_gross": 62500000,
    "total_deductions": 15800000,
    "total_net": 46700000
  }
}
```

**프론트엔드 참고:**
- 대시보드 카드에 `total_count`, `total_gross`, `total_net` 표시
- 상태별 카운트로 진행 상태 게이지 표현 가능

---

### 5.4 급여 일괄 계산

기존 `draft` 상태 급여의 공제 항목을 재계산한다.

```
POST /api/v1/payrolls/calculate
```

**Request Body:**

```json
{
  "year": 2026,
  "month": 3,
  "user_ids": [5, 8, 12]
}
```

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `year` | int | ✅ | 대상 연도 |
| `month` | int | ✅ | 대상 월 |
| `user_ids` | array | - | 특정 사원만 지정 (미전달 시 전체) |

**응답 (200):**

```json
{
  "success": true,
  "message": "급여가 일괄 계산되었습니다.",
  "data": [
    { /* 재계산된 Payroll 객체들 */ }
  ]
}
```

> **주의:** `confirmed`/`paid` 상태 급여는 재계산하지 않는다 (draft만 대상).

---

### 5.5 계산 미리보기

급여 데이터를 저장하지 않고 계산 결과만 미리 확인한다. 급여 등록/수정 폼에서 실시간 미리보기용.

```
POST /api/v1/payrolls/calculate-preview
```

**Request Body:**

```json
{
  "user_id": 5,
  "base_salary": 3500000,
  "overtime_pay": 0,
  "bonus": 200000,
  "allowances": [
    {"name": "직책수당", "amount": 300000}
  ],
  "deductions": [
    {"name": "대출상환", "amount": 500000}
  ]
}
```

**응답 (200):**

```json
{
  "success": true,
  "message": "계산 완료",
  "data": {
    "gross_salary": 4100000,
    "taxable_base": 3900000,
    "income_tax": 117750,
    "resident_tax": 11770,
    "health_insurance": 138220,
    "long_term_care": 1250,
    "pension": 175500,
    "employment_insurance": 35100,
    "total_deductions": 979590,
    "net_salary": 3120410,
    "family_count": 3
  }
}
```

**프론트엔드 참고:**
- `user_id`를 전달하면 해당 사원의 가족수를 자동 반영
- `user_id` 미전달 시 가족수 1로 계산
- 폼 입력값이 변경될 때마다 호출하여 실시간 계산 결과를 표시 (debounce 300ms 권장)

---

### 5.6 일괄 확정

해당 월의 모든 `draft` 급여를 한 번에 `confirmed`로 변경한다.

```
POST /api/v1/payrolls/bulk-confirm
```

**Request Body:**

```json
{
  "year": 2026,
  "month": 3
}
```

**응답 (200):**

```json
{
  "success": true,
  "message": "급여가 일괄 확정되었습니다.",
  "data": {
    "count": 12
  }
}
```

---

### 5.7 재직사원 일괄 생성

해당 연월에 재직 중인(employee_status=active) 모든 사원의 급여를 자동 생성한다.

```
POST /api/v1/payrolls/bulk-generate
```

**Request Body:**

```json
{
  "year": 2026,
  "month": 3
}
```

**응답 (200):**

```json
{
  "success": true,
  "message": "급여가 일괄 생성되었습니다.",
  "data": {
    "created": 12,
    "skipped": 3
  }
}
```

**동작 설명:**
- 사원별 연봉 정보(`json_extra.salary_info.annual_salary`)에서 `÷12`로 월 기본급 산출
- 이미 존재하는 사원은 `skipped` 처리 (중복 생성하지 않음)
- 연봉 정보가 없는 사원도 기본급 0으로 생성됨
- 모든 급여는 `draft` 상태로 생성됨

**프론트엔드 참고:**
- 매월 초에 "일괄 생성" 버튼을 눌러 해당 월 급여를 초기화
- `created`/`skipped` 결과를 토스트 메시지로 표시

---

### 5.8 전월 급여 복사

전월 급여 데이터를 현재 월로 복사한다. 매월 동일한 급여 구조를 유지할 때 사용.

```
POST /api/v1/payrolls/copy-from-previous
```

**Request Body:**

```json
{
  "year": 2026,
  "month": 3
}
```

**응답 (200):**

```json
{
  "success": true,
  "message": "전월 급여가 복사되었습니다.",
  "data": {
    "created": 15,
    "skipped": 0
  }
}
```

**동작 설명:**
- 지급/공제 항목을 전월 그대로 복사 (`base_salary`, `allowances`, `deductions` 등)
- 상태는 `draft`로 초기화
- 이미 존재하는 사원은 `skipped` 처리
- 1월 요청 시 전년 12월 데이터 참조

**에러:**

| 상황 | 응답 코드 | 메시지 |
|------|----------|--------|
| 전월 데이터 없음 | 400 | 전월 급여 데이터가 없습니다. |

---

### 5.9 급여 상세 조회

```
GET /api/v1/payrolls/{id}
```

**응답 (200):**

```json
{
  "success": true,
  "message": "조회 성공",
  "data": {
    "id": 1,
    "user": {"id": 5, "name": "홍길동", "email": "hong@example.com"},
    "confirmer": {"id": 1, "name": "관리자"},
    "withdrawal": null,
    "creator": {"id": 1, "name": "관리자"},
    /* ... 전체 Payroll 필드 */
  }
}
```

---

### 5.10 급여 수정

```
PUT /api/v1/payrolls/{id}
```

**Request Body:**

```json
{
  "base_salary": 3600000,
  "overtime_pay": 100000,
  "bonus": 200000,
  "allowances": [
    {"name": "직책수당", "amount": 300000}
  ],
  "deductions": [
    {"name": "대출상환", "amount": 500000}
  ],
  "deduction_overrides": {
    "pension": 175000
  },
  "_is_super_admin": false,
  "note": "기본급 인상 반영"
}
```

**필드 규칙:**
- `_is_super_admin: true` 전달 시 `confirmed`/`paid` 상태에서도 수정 가능
- `deduction_overrides`로 법정 공제 항목을 수동 변경 가능
- 전달하지 않은 필드는 기존값 유지

**에러:**

| 상황 | 응답 코드 | 메시지 |
|------|----------|--------|
| draft 아닌 상태에서 수정 | 400 | 작성중 상태의 급여만 수정할 수 있습니다. |
| 연월/사원 변경 시 중복 | 400 | 해당 연월에 이미 급여가 등록되어 있습니다. |

---

### 5.11 급여 삭제

```
DELETE /api/v1/payrolls/{id}
```

**응답 (200):**

```json
{
  "success": true,
  "message": "삭제 성공",
  "data": null
}
```

> `draft` 상태에서만 삭제 가능. Soft delete 처리.

---

### 5.12 확정

```
POST /api/v1/payrolls/{id}/confirm
```

**Request Body:** 없음

**응답 (200):**

```json
{
  "success": true,
  "message": "급여가 확정되었습니다.",
  "data": {
    "id": 1,
    "status": "confirmed",
    "confirmed_at": "2026-03-11T14:30:00.000000Z",
    "confirmer": {"id": 1, "name": "관리자"}
  }
}
```

---

### 5.13 확정 취소

```
POST /api/v1/payrolls/{id}/unconfirm
```

**Request Body:** 없음

**응답 (200):**

```json
{
  "success": true,
  "message": "급여 확정이 취소되었습니다.",
  "data": {
    "id": 1,
    "status": "draft",
    "confirmed_at": null,
    "confirmed_by": null
  }
}
```

> `confirmed` 상태에서만 가능. `draft`로 되돌린다.

---

### 5.14 지급 처리

```
POST /api/v1/payrolls/{id}/pay
```

**Request Body:**

```json
{
  "withdrawal_id": 42
}
```

| 필드 | 타입 | 필수 | 설명 |
|------|------|------|------|
| `withdrawal_id` | int | - | 연결할 출금 내역 ID |

**응답 (200):**

```json
{
  "success": true,
  "message": "급여가 지급 처리되었습니다.",
  "data": {
    "id": 1,
    "status": "paid",
    "paid_at": "2026-03-25T09:00:00.000000Z",
    "withdrawal": { "id": 42, "..." : "..." }
  }
}
```

---

### 5.15 지급 취소

```
POST /api/v1/payrolls/{id}/unpay
```

**Request Body:** 없음

> **슈퍼관리자 전용.** `paid` → `draft`로 초기화. 확정/지급 이력 모두 제거.

**응답 (200):**

```json
{
  "success": true,
  "message": "급여 지급이 취소되었습니다.",
  "data": {
    "id": 1,
    "status": "draft",
    "confirmed_at": null,
    "paid_at": null,
    "withdrawal_id": null
  }
}
```

---

### 5.16 급여명세서 조회

```
GET /api/v1/payrolls/{id}/payslip
```

**응답 (200):**

```json
{
  "success": true,
  "message": "조회 성공",
  "data": {
    "payroll": { /* 전체 Payroll 객체 */ },
    "period": "2026년 03월",
    "employee": {
      "id": 5,
      "name": "홍길동",
      "email": "hong@example.com"
    },
    "earnings": {
      "base_salary": 3500000,
      "overtime_pay": 0,
      "bonus": 200000,
      "allowances": [
        {"name": "직책수당", "amount": 300000},
        {"name": "교통비", "amount": 100000}
      ],
      "allowances_total": 400000,
      "gross_total": 4100000
    },
    "deductions": {
      "income_tax": 117750,
      "resident_tax": 11770,
      "health_insurance": 138220,
      "long_term_care": 1250,
      "pension": 175500,
      "employment_insurance": 35100,
      "other_deductions": [
        {"name": "대출상환", "amount": 500000}
      ],
      "other_total": 500000,
      "total": 979590
    },
    "net_salary": 3120410,
    "status": "confirmed",
    "status_label": "확정",
    "paid_at": null
  }
}
```

**프론트엔드 참고:**
- `earnings` 구조로 지급 항목 테이블 구성
- `deductions` 구조로 공제 항목 테이블 구성
- `period`, `employee`, `net_salary`로 명세서 헤더 구성
- 인쇄용 레이아웃은 A4 세로 기준 권장

---

### 5.17 급여 설정 조회

```
GET /api/v1/payrolls/settings
```

**응답 (200):**

```json
{
  "success": true,
  "message": "조회 성공",
  "data": {
    "id": 1,
    "tenant_id": 1,
    "income_tax_rate": "0.00",
    "resident_tax_rate": "10.00",
    "health_insurance_rate": "3.545",
    "long_term_care_rate": "0.9082",
    "pension_rate": "4.500",
    "employment_insurance_rate": "0.900",
    "pension_max_salary": "5900000.00",
    "pension_min_salary": "370000.00",
    "pay_day": 25,
    "auto_calculate": false,
    "allowance_types": [
      {"code": "meal", "name": "식대", "is_taxable": false},
      {"code": "transport", "name": "교통비", "is_taxable": false},
      {"code": "position", "name": "직책수당", "is_taxable": true},
      {"code": "skill", "name": "기술수당", "is_taxable": true},
      {"code": "family", "name": "가족수당", "is_taxable": true},
      {"code": "housing", "name": "주거수당", "is_taxable": true}
    ],
    "deduction_types": [
      {"code": "loan", "name": "대출상환"},
      {"code": "union", "name": "조합비"},
      {"code": "savings", "name": "저축"},
      {"code": "etc", "name": "기타공제"}
    ]
  }
}
```

**프론트엔드 참고:**
- `allowance_types`로 수당 입력 폼의 드롭다운 구성
- `deduction_types`로 공제 입력 폼의 드롭다운 구성
- `pay_day`를 급여 지급일 표시에 활용

---

### 5.18 급여 설정 수정

```
PUT /api/v1/payrolls/settings
```

**Request Body:**

```json
{
  "health_insurance_rate": 3.545,
  "pension_rate": 4.5,
  "pay_day": 25,
  "allowance_types": [
    {"code": "meal", "name": "식대", "is_taxable": false},
    {"code": "transport", "name": "교통비", "is_taxable": false}
  ]
}
```

> 전달한 필드만 업데이트. 미전달 필드는 기존값 유지.

---

## 6. 에러 코드 정리

| 에러 키 | 메시지 | 발생 상황 |
|---------|--------|----------|
| `error.payroll.not_found` | 급여 정보를 찾을 수 없습니다. | 존재하지 않는 ID |
| `error.payroll.already_exists` | 해당 연월에 이미 급여가 등록되어 있습니다. | 동일 사원+연월 중복 |
| `error.payroll.not_editable` | 작성중 상태의 급여만 수정할 수 있습니다. | draft 외 수정 시도 |
| `error.payroll.not_deletable` | 작성중 상태의 급여만 삭제할 수 있습니다. | draft 외 삭제 시도 |
| `error.payroll.not_confirmable` | 작성중 상태의 급여만 확정할 수 있습니다. | draft 외 확정 시도 |
| `error.payroll.not_unconfirmable` | 확정된 급여만 확정 취소할 수 있습니다. | confirmed 외 확정취소 |
| `error.payroll.not_payable` | 확정된 급여만 지급 처리할 수 있습니다. | confirmed 외 지급 |
| `error.payroll.not_unpayable` | 지급완료된 급여만 지급 취소할 수 있습니다. | paid 외 지급취소 |
| `error.payroll.no_previous_month` | 전월 급여 데이터가 없습니다. | 전월 복사 시 데이터 없음 |
| `error.payroll.invalid_withdrawal` | 유효하지 않은 출금 내역입니다. | 존재하지 않는 withdrawal_id |

---

## 7. 프론트엔드 구현 가이드

### 7.1 추천 화면 구성

```
┌─────────────────────────────────────────────────────┐
│ 급여관리                            [2026년 03월 ▼] │
├─────────────────────────────────────────────────────┤
│                                                     │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐│
│ │ 총 인원   │ │ 총 지급액 │ │ 총 공제액 │ │ 실수령액 ││
│ │    15명   │ │ 62,500천원│ │ 15,800천원│ │46,700천원││
│ └──────────┘ └──────────┘ └──────────┘ └─────────┘│
│                                                     │
│ [일괄생성] [전월복사] [일괄계산] [일괄확정] [+ 등록] │
│                                                     │
│ ┌───┬──────┬──────┬──────┬──────┬──────┬────┬────┐│
│ │ # │ 사원  │ 기본급│총지급액│총공제액│실수령액│상태 │ 작업││
│ ├───┼──────┼──────┼──────┼──────┼──────┼────┼────┤│
│ │ 1 │홍길동│ 350만│ 410만│ 98만 │ 312만│작성│ ⋮  ││
│ │ 2 │김철수│ 300만│ 350만│ 85만 │ 265만│확정│ ⋮  ││
│ └───┴──────┴──────┴──────┴──────┴──────┴────┴────┘│
└─────────────────────────────────────────────────────┘
```

### 7.2 급여 등록/수정 폼

```
┌─────────────────────────────────────────────────────┐
│ 급여 등록                                           │
├─────────────────────────────────────────────────────┤
│ 사원: [홍길동 ▼]  연도: [2026]  월: [3 ▼]          │
│                                                     │
│ ── 지급 항목 ──────────────────────────────────── │
│ 기본급:         [  3,500,000 ]                      │
│ 연장근로수당:    [          0 ]                      │
│ 식대(비과세):   [    200,000 ]                      │
│ 수당:                                               │
│   직책수당      [    300,000 ] [삭제]               │
│   교통비        [    100,000 ] [삭제]               │
│   [+ 수당 추가]                                     │
│ ────────────────────────── 총 지급액: 4,100,000    │
│                                                     │
│ ── 공제 항목 (자동 계산) ────────────────────────  │
│ 근로소득세:     [    117,750 ] ← 자동 (수정 가능)   │
│ 지방소득세:     [     11,770 ] ← 자동 (수정 가능)   │
│ 건강보험:       [    138,220 ] ← 자동 (수정 가능)   │
│ 장기요양보험:   [      1,250 ] ← 자동 (수정 가능)   │
│ 국민연금:       [    175,500 ] ← 자동 (수정 가능)   │
│ 고용보험:       [     35,100 ] ← 자동 (수정 가능)   │
│ 기타 공제:                                          │
│   대출상환      [    500,000 ] [삭제]               │
│   [+ 기타 공제 추가]                                │
│ ────────────────────────── 총 공제액:   979,590    │
│                                                     │
│ ═════════════════════════ 실수령액:  3,120,410    │
│                                                     │
│ 메모: [                    ]                        │
│                                                     │
│                            [취소]  [미리보기]  [저장]│
└─────────────────────────────────────────────────────┘
```

**구현 포인트:**
- 지급 항목 변경 시 `calculate-preview` API 호출하여 공제 항목 자동 갱신
- 법정 공제 필드는 기본 readonly + "수정" 토글로 수동 입력 허용
- 수동 입력된 공제 항목은 `deduction_overrides`로 전달
- `allowance_types`, `deduction_types`는 설정 API에서 조회하여 드롭다운 제공

### 7.3 급여명세서 (인쇄용)

`payslip` API 응답의 `earnings`/`deductions` 구조를 활용:

```
┌─────────────────────────────────────────────┐
│              급 여 명 세 서                  │
│                                             │
│ 사원명: 홍길동     귀속기간: 2026년 03월    │
│                                             │
│ ┌──── 지급 내역 ────┬── 공제 내역 ────┐    │
│ │ 기본급    3,500,000│ 소득세    117,750│    │
│ │ 식대       200,000│ 지방소득세 11,770│    │
│ │ 직책수당    300,000│ 건강보험  138,220│    │
│ │ 교통비     100,000│ 장기요양    1,250│    │
│ │                   │ 국민연금  175,500│    │
│ │                   │ 고용보험   35,100│    │
│ │                   │ 대출상환  500,000│    │
│ ├───────────────────┼─────────────────┤    │
│ │ 지급합계 4,100,000│ 공제합계 979,590│    │
│ └───────────────────┴─────────────────┘    │
│                                             │
│         실수령액: 3,120,410원               │
└─────────────────────────────────────────────┘
```

### 7.4 월간 워크플로우

```
1. 월초 → [일괄생성] 또는 [전월복사] 실행
2. 개별 급여 데이터 확인/수정
3. [일괄계산] 실행 (공제 항목 최신 요율로 재계산)
4. 데이터 확인 완료 → [일괄확정]
5. 급여 지급일 → 개별 [지급처리] (출금과 연결)
6. 급여명세서 조회/인쇄
```

### 7.5 금액 표시 규칙

- 모든 금액은 **원(KRW)** 단위 정수
- 천 단위 콤마 필수: `3,500,000`
- 음수 금액(환급): 빨간색 + `-` 부호

---

## 관련 문서

- [급여관리 기능 상세](../../features/finance/payroll.md) — 전표 변환, 권한, 멀티테넌트
- [DB 스키마 — 인사](../../system/database/hr.md)
- [결재관리 API 명세](approval-api.md)

---

**최종 업데이트**: 2026-03-11
