# 급여관리 (Payroll)

> **작성일**: 2026-02-27
> **상태**: 운영 중
> **최종 갱신**: 2026-03-11

---

## 1. 개요

### 1.1 목적

MNG 관리자 페이지(`/hr/payrolls`)에서 월별 급여를 등록/확정/지급하고, 일반전표로 자동 변환하는 시스템이다.

### 1.2 핵심 원칙

- **Payroll 모델** 중심 운영 (Salary 모델은 React 연동용으로 별도 존재)
- 급여 데이터는 MNG에서 관리, DB 마이그레이션은 API에서 관리
- 법정공제는 자동 계산, 기타공제(`deductions` JSON)는 수기 입력

---

## 2. 상태 워크플로우

```
draft(작성중) ──확정──→ confirmed(확정) ──지급──→ paid(지급완료)
     ↑                       │                       │
     │                  확정취소                 지급취소*
     │                       │                       │
     └───────────────────────┘                       │
     └───────────────────────────────────────────────┘
                                          * 슈퍼관리자 전용
```

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

| 상태 | 일반 사용자 | 슈퍼관리자 |
|------|-----------|-----------|
| `draft` (작성중) | 수정, 삭제, 확정 | 동일 |
| `confirmed` (확정) | 확정취소, 지급처리, 이메일발송 | + **수정** |
| `paid` (지급완료) | 상세보기만 | + **수정**, **지급취소** |

### 2.2 슈퍼관리자 예외 (2026-03-11 추가)

- `confirmed`/`paid` 상태에서도 급여 데이터 직접 수정 가능
- `paid` → `draft` 지급취소: 확정/지급 이력(`confirmed_at`, `paid_at`) 초기화
- 용도: 수기 입력 오류(기타공제 항목명 오타 등) 수정 후 전표 재생성

---

## 3. 데이터 구조

### 3.1 payrolls 테이블 주요 필드

| 구분 | 필드 | 설명 |
|------|------|------|
| **식별** | `tenant_id`, `user_id`, `pay_year`, `pay_month` | 테넌트/사원/귀속연월 |
| **지급** | `base_salary`, `overtime_pay`, `bonus` | 기본급, 고정연장근로수당, 식대(비과세) |
| **수당** | `allowances` (JSON) | `[{name, amount}, ...]` |
| **총액** | `gross_salary` | 기본급 + 수당 + 식대 합계 |
| **법정공제** | `pension`, `health_insurance`, `long_term_care`, `employment_insurance`, `income_tax`, `resident_tax` | 자동 계산 (수동 override 가능) |
| **기타공제** | `deductions` (JSON) | `[{name, amount}, ...]` 수기 입력 |
| **결과** | `total_deductions`, `net_salary` | 총공제액, 실수령액 |
| **상태** | `status`, `confirmed_at`, `paid_at` | draft/confirmed/paid |

### 3.2 기타공제 (deductions JSON)

> **경고: `deductions`의 `name`은 수기 입력 데이터다. 오타에 주의해야 한다.**

```json
[
  {"name": "연말정산 소득세", "amount": -564120},
  {"name": "연말정산 지방소득세", "amount": -120650},
  {"name": "건강보험 퇴직정산", "amount": -41600}
]
```

- 양수: 공제 (급여에서 차감)
- 음수: 환급 (급여에 추가)
- **이름이 다르면 전표 변환 시 별도 항목으로 처리됨** (예: "소득세" vs "소득제" → 그룹핑 실패)

---

## 4. 일반전표 변환

### 4.1 개요

해당 월의 전체 급여를 합산하여 일반전표 1건을 자동 생성한다.

- 메서드: `PayrollController::generateJournalEntry()`
- `source_type`: `payroll`
- `source_key`: `payroll-{year}-{month}` (중복 생성 불가)
- 전표일자: 해당 월 말일

### 4.2 분개 구조

| 차/대 | 계정코드 | 계정명 | 거래처 | 금액 | 적요 |
|-------|---------|--------|--------|------|------|
| 차변 | 801 | 급여 | 임직원 | 총지급액 합산 | N월분 급여 |
| 대변 | 207 | 예수금 | 건강보험연금 | 국민연금 합산 | 국민연금 |
| 대변 | 207 | 예수금 | 건강보험건강 | 건강보험 합산 | 건강보험 |
| 대변 | 207 | 예수금 | 건강보험건강 | 장기요양보험 합산 | 장기요양보험 |
| 대변 | 207 | 예수금 | 건강보험고용 | 고용보험 합산 | 고용보험 |
| 대변 | 207 | 예수금 | 강서세무서 | 근로소득세 합산 | N월분 근로소득세 |
| 대변 | 207 | 예수금 | 강서구청 | 지방소득세 합산 | N월분 지방소득세 |
| 대변 | 207 | 예수금 | 임직원 | 기타공제 항목별 | 항목명 |
| 대변 | 205 | 미지급비용 | 임직원 | 실수령액 합산 | N월분 급여 |

### 4.3 거래처 매핑 (현재 하드코딩)

| 공제 항목 | 거래처명 |
|----------|---------|
| 국민연금 | 건강보험연금 |
| 건강보험, 장기요양보험 | 건강보험건강 |
| 고용보험 | 건강보험고용 |
| 근로소득세 | 강서세무서 |
| 지방소득세 | 강서구청 |
| 기타공제, 실수령액 | 임직원 |

### 4.4 음수 공제(환급) 처리

- `amount < 0`: 적요에 `"(환급)"` 접미사 추가
- 대변에 음수 금액으로 표기 (예수금 감소)
- 동일 `거래처+적요` 조합은 병합: `mergeKey = "{partnerName}|{description}"`

### 4.5 차대 균형 검증

전표 저장 전 `총 차변 === 총 대변` 검증. 불일치 시 상세 내역과 함께 에러 반환.

### 4.6 전표 수정/삭제

생성된 전표의 수정/삭제는 **회계관리 > 일반전표입력** 메뉴에서 처리한다.
급여관리에서 전표를 삭제하면 동일 월에 대해 재생성 가능하다.

---

## 5. 권한 체계

### 5.1 접근 제어

```php
private const ALLOWED_PAYROLL_USERS = ['이의찬', '전진선', '김보곤'];
```

급여관리 메뉴 접근은 위 사용자로 제한된다 (`auth()->user()->name` 기준).

### 5.2 슈퍼관리자 권한

`users.is_super_admin = true`인 사용자:
- `confirmed`/`paid` 상태에서 급여 데이터 수정
- `paid` → `draft` 지급취소 (`unpay`)

---

## 6. MNG API 엔드포인트

| Method | URI | 설명 |
|--------|-----|------|
| GET | `/api/admin/hr/payrolls` | 급여 목록 (HTMX 테이블) |
| GET | `/api/admin/hr/payrolls/stats` | 월간 통계 카드 |
| GET | `/api/admin/hr/payrolls/export` | 엑셀 내보내기 |
| POST | `/api/admin/hr/payrolls` | 급여 등록 |
| PUT | `/api/admin/hr/payrolls/{id}` | 급여 수정 |
| DELETE | `/api/admin/hr/payrolls/{id}` | 급여 삭제 |
| POST | `/api/admin/hr/payrolls/{id}/confirm` | 확정 |
| POST | `/api/admin/hr/payrolls/{id}/unconfirm` | 확정 취소 |
| POST | `/api/admin/hr/payrolls/{id}/pay` | 지급 처리 |
| POST | `/api/admin/hr/payrolls/{id}/unpay` | 지급 취소 (슈퍼관리자) |
| POST | `/api/admin/hr/payrolls/bulk-generate` | 재직사원 일괄 생성 |
| POST | `/api/admin/hr/payrolls/copy-from-previous` | 전월 복사 |
| POST | `/api/admin/hr/payrolls/calculate` | 공제 자동 계산 |
| POST | `/api/admin/hr/payrolls/generate-journal-entry` | 일반전표 생성 |
| POST | `/api/admin/hr/payrolls/{id}/send-payslip` | 급여명세서 이메일 (PDF) |
| GET | `/api/admin/hr/payroll-settings` | 급여 설정 조회 |
| PUT | `/api/admin/hr/payroll-settings` | 급여 설정 수정 |

---

## 7. 멀티테넌트 확장 계획

> 현재 `tenant_id=1`에서만 운영. 추후 전체 테넌트에 확장 예정.

### 7.1 확장 시 변경 필요 항목

| 항목 | 현재 | 확장 후 |
|------|------|--------|
| 거래처 매핑 | 하드코딩 (`건강보험연금` 등) | 테넌트별 설정 테이블 |
| 계정과목 코드 | 하드코딩 (`801`/`207`/`205`) | 테넌트별 설정 |
| `AccountCode` 조회 | `tenant_id` 필터 없음 | `tenant_id` 필터 추가 필수 |
| 접근 권한 | `ALLOWED_PAYROLL_USERS` (이름 기반) | 역할(Role) 기반 권한 |
| `PayrollSetting` | 이미 테넌트별 분리 가능 | 변경 불필요 |

### 7.2 거래처/계정과목 설정 방안

`PayrollSetting` 또는 별도 설정에 테넌트별 매핑 저장:

```json
{
  "journal_accounts": {
    "salary": "801",
    "withholding": "207",
    "accrued": "205"
  },
  "journal_partners": {
    "pension": "건강보험연금",
    "health": "건강보험건강",
    "employment": "건강보험고용",
    "income_tax": "강서세무서",
    "resident_tax": "강서구청",
    "employee": "임직원"
  }
}
```

---

## 8. 관련 코드

| 프로젝트 | 경로 | 설명 |
|---------|------|------|
| MNG | `app/Http/Controllers/Api/Admin/HR/PayrollController.php` | API 컨트롤러 (전표 생성 포함) |
| MNG | `app/Services/HR/PayrollService.php` | 비즈니스 로직 |
| MNG | `app/Models/HR/Payroll.php` | 모델 (상태 헬퍼) |
| MNG | `resources/views/hr/payrolls/` | Blade 뷰 |
| API | `database/migrations/2025_12_18_100001_create_payrolls_table.php` | 테이블 정의 |

---

## 관련 문서

- [재무관리 개요](README.md)
- [DB 스키마 — 인사](../../system/database/hr.md)
- [DB 스키마 — 재무](../../system/database/finance.md)

---

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