Files
sam-docs/plans/payroll-api-implementation-plan.md
김보곤 593bef9e5d docs: [payroll] 급여관리 API 구현 기획서 작성
- MNG 급여관리 시스템 → API 이식 3단계 계획 수립
- Phase 1: 핵심 계산 엔진 (소득세, 4대보험, 공제 오버라이드)
- Phase 2: 상태 관리 + 일괄 처리 (unconfirm, unpay, bulkGenerate)
- Phase 3: 문서 생성 (PDF 명세서, 전표 변환, 엑셀 내보내기)
- INDEX.md에 문서 등록
2026-03-11 18:04:10 +09:00

13 KiB
Raw Blame History

급여관리 API 구현 계획

작성일: 2026-03-11 상태: 계획 수립 참조: MNG 급여관리 시스템 (mng/app/Services/HR/PayrollService.php)


1. 개요

1.1 목적

MNG에서 운영 중인 급여관리 시스템의 핵심 비즈니스 로직을 API 서버에 구현한다. React 프론트엔드에서 급여 관리 기능을 사용할 수 있도록 완전한 REST API를 제공한다.

1.2 배경

  • MNG 급여관리: 완성도 100% (CRUD, 자동계산, 일괄생성, PDF 명세서, 전표변환)
  • API 급여관리: 완성도 ~50% (기본 CRUD만 구현, 핵심 계산 로직 누락)
  • React에서 급여관리 화면을 구현하려면 API에 동일한 비즈니스 로직이 필요하다

1.3 원칙

  • MNG의 검증된 로직을 API 컨벤션에 맞게 이식한다
  • API 프로젝트의 Service-First 아키텍처, i18n, FormRequest 패턴을 준수한다
  • 기존 payrolls 테이블 스키마를 그대로 사용한다 (추가 마이그레이션 최소화)

2. 현황 분석 (GAP)

2.1 기능 비교

기능 MNG API GAP
급여 CRUD -
급여 설정 CRUD -
목록 조회 (필터/페이지네이션) -
월별 통계 -
확정 (confirm) -
지급 처리 (pay) -
일괄 확정 (bulkConfirm) -
소득세 자동 계산 간이세액표 기반 계산 로직 전체 누락
4대보험 자동 계산 ⚠️ 설정값만 존재, calculateAmounts() 미구현
공제 오버라이드 수동 공제 수정 후 재계산 미지원
확정 취소 (unconfirm) 상태 복구 불가
지급 취소 (unpay) 슈퍼관리자 기능 누락
일괄 생성 (bulkGenerate) 재직사원 기반 신규 생성 미구현
전월 복사 (copyFromPrevious) 이전 월 데이터 복사 미구현
급여명세서 PDF 생성 데이터 조회만 가능, PDF 미생성
급여명세서 이메일 발송 이메일 발송 미구현
전표 자동 생성 generateJournalEntry() 미구현
엑셀 내보내기 export 미구현
공제대상가족수 자동 산출 피부양자 기반 가족수 미산출

2.2 API 기존 코드 현황

파일 상태 비고
Controllers/Api/V1/PayrollController.php 기본 CRUD 구현 누락 엔드포인트 추가 필요
Services/PayrollService.php 기본 CRUD + 제한적 계산 핵심 로직 이식 필요
Models/Tenants/Payroll.php 모델 정의 완료 상태 헬퍼 메서드 보강 필요
Models/Tenants/PayrollSetting.php 설정 모델 완료 -
Requests/V1/Payroll/ FormRequest 5개 존재 추가 Request 필요
routes/api/v1/finance.php 기본 라우트 정의 누락 엔드포인트 추가

3. 구현 범위

Phase 1: 핵심 계산 엔진 (필수)

목표: 급여 자동 계산이 동작하도록 핵심 비즈니스 로직을 이식한다.

# 작업 참조 (MNG) 대상 파일 (API)
1-1 calculateAmounts() 메서드 구현 PayrollService:529-590 Services/PayrollService.php
1-2 calculateIncomeTax() 소득세 계산 PayrollService:592-670 Services/PayrollService.php
1-3 4대보험 개별 계산 메서드 PayrollService:672-720 Services/PayrollService.php
1-4 applyDeductionOverrides() 공제 수동 수정 PayrollService:722-760 Services/PayrollService.php
1-5 resolveFamilyCount() 가족수 산출 PayrollService:762-800 Services/PayrollService.php
1-6 IncomeTaxBracket 모델 생성 Models/HR/IncomeTaxBracket.php Models/Tenants/IncomeTaxBracket.php
1-7 income_tax_brackets 마이그레이션 실행 확인 이미 존재 확인 필요 database/migrations/
1-8 store()/update() 에서 자동 계산 적용 PayrollService:150-250 Services/PayrollService.php

계산 흐름:

입력: base_salary, overtime_pay, bonus, allowances, deductions
    │
    ├─ Step 1: 총 지급액 = base_salary + overtime_pay + bonus + Σ(allowances)
    ├─ Step 2: 과세표준 = 총 지급액 - bonus (비과세)
    ├─ Step 3: 4대보험 = 과세표준 × 요율 (PayrollSetting 참조)
    │   ├─ 건강보험 = 과세표준 × 3.545%
    │   ├─ 장기요양 = 건강보험 × 0.9082%
    │   ├─ 국민연금 = clamp(min, max, 과세표준) × 4.5%
    │   └─ 고용보험 = 과세표준 × 0.9%
    ├─ Step 4: 근로소득세 = 간이세액표 조회 (가족수 반영)
    │   ├─ < 770천원: 0원
    │   ├─ 770~10,000천원: DB 간이세액표
    │   └─ > 10,000천원: 소득세법 시행령 별표2 공식
    ├─ Step 5: 지방소득세 = 근로소득세 × 10%
    ├─ Step 6: 총 공제액 = 4대보험 + 세금 + Σ(deductions)
    └─ Step 7: 실수령액 = 총 지급액 - 총 공제액

    ※ 모든 금액: 10원 단위 절삭 (floor)

Phase 2: 상태 관리 + 일괄 처리

# 작업 참조 (MNG) 비고
2-1 unconfirm() 확정 취소 PayrollService:340-360 confirmed → draft
2-2 unpay() 지급 취소 PayrollService:380-400 paid → draft (슈퍼관리자)
2-3 bulkGenerate() 재직사원 일괄 생성 PayrollService:442-521 Employee 연봉 기반
2-4 copyFromPreviousMonth() 전월 복사 PayrollService:402-440 soft-delete 처리 포함
2-5 Payroll 모델에 상태 헬퍼 메서드 추가 Models/HR/Payroll.php isEditable(), isConfirmable()

일괄 생성 로직:

bulkGenerate(year, month)
    │
    ├─ 1. PayrollSetting 조회
    ├─ 2. 활성 재직사원 전체 조회
    ├─ 3. 각 사원별:
    │   ├─ 이미 존재 → skip
    │   ├─ soft-deleted 존재 → forceDelete 후 재생성
    │   ├─ 기본급 = 연봉 / 12
    │   ├─ calculateAmounts() 호출
    │   └─ Payroll 생성 (status: draft)
    └─ 4. 결과: {created: N, skipped: M}

Phase 3: 문서 생성 + 내보내기

# 작업 참조 (MNG) 비고
3-1 sendPayslip() 급여명세서 PDF + 이메일 PayrollService:820-920 DomPDF + Pretendard
3-2 generateJournalEntry() 전표 자동 생성 PayrollController:900-1088 분개 구조 동일
3-3 export() 엑셀 내보내기 PayrollService:100-140 동적 열 포함
3-4 급여명세서 Blade 뷰 생성 emails/payslip.blade.php PDF 폰트 정책 준수
3-5 PayslipMail Mailable 생성 Mail/PayslipMail.php

4. API 엔드포인트 설계

4.1 추가 엔드포인트

기존 라우트(routes/api/v1/finance.php)에 추가할 엔드포인트:

Method URI 설명 Phase
POST /v1/payrolls/{id}/unconfirm 확정 취소 2
POST /v1/payrolls/{id}/unpay 지급 취소 (슈퍼관리자) 2
POST /v1/payrolls/bulk-generate 재직사원 일괄 생성 2
POST /v1/payrolls/copy-from-previous 전월 복사 2
POST /v1/payrolls/{id}/send-payslip 급여명세서 이메일 발송 3
POST /v1/payrolls/generate-journal-entry 전표 자동 생성 3
GET /v1/payrolls/export 엑셀 내보내기 3

4.2 기존 엔드포인트 수정

URI 변경 내용 Phase
POST /v1/payrolls calculateAmounts() 자동 적용 1
PUT /v1/payrolls/{id} 공제 오버라이드 지원 1
POST /v1/payrolls/calculate 소득세 포함 전체 계산으로 개선 1

4.3 요청/응답 예시

급여 등록 요청 (POST /v1/payrolls):

{
  "user_id": 15,
  "pay_year": 2026,
  "pay_month": 3,
  "base_salary": 3500000,
  "overtime_pay": 500000,
  "bonus": 200000,
  "allowances": [
    {"name": "교통비", "amount": 100000}
  ],
  "deductions": [
    {"name": "대출상환", "amount": 300000}
  ],
  "deduction_overrides": {
    "pension": 180000,
    "health_insurance": null
  }
}

자동 계산 응답 (POST /v1/payrolls/calculate):

{
  "success": true,
  "data": {
    "gross_salary": 4300000,
    "taxable_base": 4100000,
    "pension": 184500,
    "health_insurance": 145345,
    "long_term_care": 13200,
    "employment_insurance": 36900,
    "income_tax": 78340,
    "resident_tax": 7830,
    "total_deductions": 766115,
    "net_salary": 3533885,
    "family_count": 2
  }
}

5. 데이터베이스

5.1 기존 테이블 (변경 불필요)

  • payrolls — 이미 모든 필드 존재 (options JSON 컬럼 포함)
  • payroll_settings — 설정 테이블 완비

5.2 확인 필요

테이블 상태 조치
income_tax_brackets 마이그레이션 존재 확인 필요 없으면 생성 + 2024 간이세액표 시딩
payrolls.long_term_care 2026-02-27 추가 완료 -
payrolls.options 2026-03-10 추가 완료 -

5.3 간이세액표 시딩

income_tax_brackets 테이블에 2024년 국세청 간이세액표 데이터가 필요하다.

  • 770천원 ~ 10,000천원 구간
  • 가족수 1~11명별 세액
  • MNG에 이미 시더 존재 → API로 이관

6. 추가 생성 파일

6.1 Phase 1

파일 설명
app/Models/Tenants/IncomeTaxBracket.php 간이세액표 모델
app/Http/Requests/V1/Payroll/BulkGenerateRequest.php 일괄 생성 요청
app/Http/Requests/V1/Payroll/CopyFromPreviousRequest.php 전월 복사 요청

6.2 Phase 3

파일 설명
app/Mail/PayslipMail.php 급여명세서 Mailable
resources/views/emails/payslip.blade.php 급여명세서 PDF 뷰
resources/views/emails/payslip-notification.blade.php 이메일 본문
app/Exports/PayrollExport.php 엑셀 내보내기

7. 주의사항

7.1 필수 준수

  • 마이그레이션은 API 프로젝트에서만 생성 (CLAUDE.md 규칙)
  • PDF 생성 시 Pretendard 폰트 + ensureKoreanFont() 적용 (폰트 정책)
  • 모든 응답 메시지는 i18n 키 사용 (__('message.xxx'))
  • ApiResponse::handle() 패턴 사용
  • FormRequest로 입력 검증

7.2 MNG 코드 이식 시 변환 규칙

MNG 패턴 API 패턴
auth()->id() $this->apiUserId()
session('tenant_id') $this->tenantId()
직접 JSON 응답 ApiResponse::success() / ApiResponse::handle()
하드코딩 한글 메시지 __('message.payroll.xxx')
HTMX 부분 렌더링 JSON 응답 전용
Payroll::query() Payroll::query()->forTenant($this->tenantId())

7.3 Salary 모델과의 관계

  • Payroll = 상세 급여 관리 (세금/보험 자동 계산, MNG 연동)
  • Salary = React용 간소화 급여 현황 (별도 유지)
  • 두 모델은 독립적으로 운영하며, 추후 통합 여부 검토

8. 작업 순서 (권장)

Phase 1 (핵심 계산) ─────────────────────────────────────
  1-6. IncomeTaxBracket 모델 생성
  1-7. 간이세액표 마이그레이션/시딩 확인
  1-1. calculateAmounts() 구현
  1-2. calculateIncomeTax() 구현
  1-3. 4대보험 계산 메서드 구현
  1-4. applyDeductionOverrides() 구현
  1-5. resolveFamilyCount() 구현
  1-8. store()/update()에 자동 계산 적용
  ─── 테스트: 급여 등록 → 자동 계산 검증 ───

Phase 2 (상태 + 일괄) ──────────────────────────────────
  2-5. Payroll 모델 상태 헬퍼 추가
  2-1. unconfirm() 구현 + 라우트
  2-2. unpay() 구현 + 라우트
  2-3. bulkGenerate() 구현 + 라우트
  2-4. copyFromPreviousMonth() 구현 + 라우트
  ─── 테스트: 상태 전이, 일괄 생성 검증 ───

Phase 3 (문서 + 내보내기) ──────────────────────────────
  3-4. 급여명세서 Blade 뷰 생성
  3-1. sendPayslip() PDF + 이메일 구현
  3-2. generateJournalEntry() 전표 생성 구현
  3-3. export() 엑셀 내보내기 구현
  ─── 테스트: PDF 생성, 이메일 발송, 전표 검증 ───

관련 문서


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