- 18개 엔드포인트 전체 명세 (CRUD, 상태변경, 일괄생성, 미리보기 등) - 4대보험/근로소득세 계산 엔진 설명 - 상태 흐름도 (draft → confirmed → paid) - 프론트엔드 구현 가이드 및 UI 와이어프레임 - INDEX.md에 문서 등록
1118 lines
31 KiB
Markdown
1118 lines
31 KiB
Markdown
# 급여관리 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
|