급여관리 스펙
개요
급여관리 기능은 급여대장(Payroll Ledger)과 급여명세서(Payslip) 두 축으로 구성됩니다. 관련 전반적인 프로젝트 맥락은 Simple HR 프로젝트 개요를 참고하세요.
현재 급여 계산은 여러 필드가 하드코딩되어 있으며, 근무 정책 스펙에서 정의한 근로정책 데이터와의 연동이 부분적입니다. 본 문서는 급여 계산 흐름, 미연동 사항, 그리고 개선 방향을 상세히 다룹니다.
기능 현황
1. 현재 급여 계산 흐름
generate_payslips() 함수의 동작 요약: fix-task-0623 (26년 06월 23일 수정사항)
1. 대상 직원 조회 (퇴직자 제외)
2. 일괄 데이터 로딩 (N+1 제거)
- 기존 명세서 확인 → 중복 방지
- EmployeePayBase (급여기초: monthly_work_hours, hourly_wage, standard_wage)
- EmployeePayDetail + PayItem (급여상세: 각 수당/공제 항목)
- 근태 데이터 (_attendance_data_map)
3. 직원별 명세서 생성 루프
a. 급여기초에서 hourly_wage, daily_wage(= hourly_wage × 8) 산출
b. 고정 수당/공제 항목 순회 (category == "ATTENDANCE"는 스킵)
c. 근태 연동 수당 자동 계산 (PAY_OVERTIME, PAY_NIGHT, PAY_HOLIDAY, PAY_HOLIDAY_OT)
d. 연차수당 계산 (daily_wage × 미사용연차일수)
e. 4대보험 자동 계산
f. Payslip + PayslipItem INSERT핵심 데이터 흐름
2. 근로정책 ↔ 급여 연동 현황 매트릭스
| 항목 | 근로정책 필드 | 급여 계산 참조 | 상태 | 문제점 |
|---|---|---|---|---|
| 월 소정근로시간 | monthly_standard_hours (자동계산) | EmployeePayBase.monthly_work_hours (수동, 기본 209) | 미연동 | 비표준 근로(주 4일 등) 미반영 |
| 연장근로 할증률 | *(별도 필드 없음, 법정 1.5배)* | 1.5 하드코딩 | 법정 고정 | — |
| 야간근로 할증률 | night_surcharge_rate (기본 1.5) | 0.5 하드코딩 (가산분만) | 미연동 | 배율 정의 불명확 |
| 휴일근로 할증률 | holiday_surcharge_rate (기본 1.5) | 1.5 하드코딩 | 미연동 | 정책 반영 안 됨 |
| 휴일초과 할증률 | *(별도 필드 없음)* | 2.0 하드코딩 | 법정 고정 | — |
| 1일 소정근로시간 | start_time, end_time, break_minutes → 계산 가능 | 8시간 하드코딩 | 미연동 | 비표준 근무시간 미반영 |
| 고정연장 vs 실제연장 | — | 독립 처리 (이중지급 가능) | 미연동 | 이중지급 위험 |
| 근태 데이터 (시간) | — | _attendance_data_map() 자동 연동 | 연동됨 | — |
| 만근수당 판단 | — | has_absence_or_late 연동 | 연동됨 | — |
| 미사용연차 | — | unused_leave_days 연동 | 연동됨 | — |
3. 미연동 항목 상세 분석
3-1. 소정근로시간 하드코딩 (209시간)
현상: EmployeePayBase.monthly_work_hours는 기본값 209시간으로 설정되며, 관리자가 급여기초 저장 시 수동 입력하지 않으면 그대로 사용됩니다. fix-task-0623 (26년 06월 23일 수정사항)
근로정책과의 갭: WorkPolicy.monthly_standard_hours는 work_days + start_time/end_time/break_minutes로 자동 계산됩니다. 주 4일 근무(174시간) 등 비표준 정책이 반영되지 않습니다.
영향: 시급 = standard_wage / monthly_work_hours. 소정근로시간이 다르면 시급이 달라지고, 시급 기반 모든 수당(연장·야간·휴일·연차)이 잘못 계산됩니다.
권장사항: 근로정책 변경 시 해당 정책이 적용된 직원의 EmployeePayBase.monthly_work_hours를 자동 갱신하거나, 명세서 생성 시점에 동적으로 조회하도록 개선이 필요합니다.
3-2. 고정연장수당(FIXED_OVERTIME) vs 실제연장수당(PAY_OVERTIME) 이중지급
현상: 급여 상세에 FIXED_OVERTIME(고정연장수당, category=ALLOWANCE)이 등록된 직원의 경우: fix-task-0623 (26년 06월 23일 수정사항)
- 1.고정 수당 루프에서 FIXED_OVERTIME 금액이 그대로 포함됨
- 2.근태 연동 수당 루프에서 PAY_OVERTIME이
overtime_hours × hourly_wage × 1.5로 별도 추가됨
이중지급 조건: FIXED_OVERTIME이 급여기초에 등록되어 있고, 동시에 해당 월에 실제 연장근로가 발생한 경우 → 둘 다 명세서에 포함됨.
해결 방향:
- (A) 포괄임금제 해석: FIXED_OVERTIME은 월 고정 연장수당 약정. 실제 연장이 고정분을 초과하면 초과분만 PAY_OVERTIME 지급. 미초과 시 PAY_OVERTIME = 0.
- (B) 현행 유지: 고정연장은 고정 수당, 실제연장은 근태 기반 별도 수당. 이중지급 허용 (회사 정책에 따라).
- (C) 관리자 설정: PayItem 또는 근무 정책에 "고정연장 적용 시 근태연장 스킵" 플래그 추가.
3-3. 할증률 하드코딩
현상: 근태 수당 계산 시 할증률이 코드에 고정되어 있습니다. fix-task-0623 (26년 06월 23일 수정사항)
PAY_OVERTIME → 1.5배 (연장근로)
PAY_NIGHT → 0.5배 (야간근로, 가산분만)
PAY_HOLIDAY → 1.5배 (휴일근로)
PAY_HOLIDAY_OT → 2.0배 (휴일초과)근로정책 필드:
WorkPolicy.night_surcharge_rate(기본 1.5) — 전체 배율. 가산분(0.5)과 의미 차이 있음WorkPolicy.holiday_surcharge_rate(기본 1.5) — 전체 배율
법정 고정성: 연장(1.5), 야간 가산(0.5), 휴일(1.5), 휴일초과(2.0)은 근로기준법에 고정되어 있으므로, 정책별로 다르게 설정할 실익이 제한적입니다. 다만, 회사 자체 규정으로 법정 이상 할증을 적용하는 경우에는 정책 참조가 필요합니다.
3-4. 근로정책 변경 시 급여기초 미갱신
현상: 근로정책의 monthly_standard_hours가 변경되어도(work_days·시간 수정 등) 해당 정책이 적용된 직원의 EmployeePayBase.monthly_work_hours는 갱신되지 않습니다. fix-task-0623 (26년 06월 23일 수정사항)
흐름:
- 1.
update_work_policy()→monthly_standard_hours재계산 후 WorkPolicy 업데이트 - 2.끝. 직원별 EmployeePayBase에 대한 후처리 없음.
권장 개선:
- (A) 자동 갱신:
update_work_policy()후처리로 해당 정책 적용 직원의 EmployeePayBase 일괄 업데이트. - (B) 알림만: 관리자에게 "근로정책 변경됨, 급여기초 확인 필요" 알림 발송. 수동 갱신.
- (C) 동적 조회: EmployeePayBase의 monthly_work_hours를 사용하지 않고, 명세서 생성 시 직원별 유효 근로정책에서 월 소정근로시간을 가져옴.
3-5. 1일 통상임금 8시간 하드코딩
현상: 연차수당 계산의 기초인 daily_wage가 hourly_wage × 8로 고정되어 있습니다. fix-task-0623 (26년 06월 23일 수정사항)
근로정책과의 갭: 근로정책에서 start_time, end_time, break_minutes로 1일 소정근로시간을 계산할 수 있습니다(예: 09:00~17:00, 휴게 1시간 = 7시간). 7시간 근무자의 연차수당이 8시간 기준으로 과다 지급됩니다.
개선 방향: 연차 자동계산 로직 스펙 참고하여 근로정책의 실제 1일 근로시간을 동적으로 계산하고 적용해야 합니다.
4. 완료된 기능 상세
4-1. 급여명세서 상세 모달 (기업 사용자)
모바일 UI/UX 개선 작업 (기업 사용자)와 연계되는 기능으로, 기업 사용자가 자신의 급여명세서를 열람하는 상세 모달에서 다음 세 가지 기능이 구현되었습니다. fix-task-0619.md
| 기능 | 설명 |
|---|---|
| PDF 다운로드 | 급여명세서 상세 모달에서 PDF 파일로 내려받기 |
| 인쇄하기 | 브라우저 인쇄 기능 연동 |
| 이의 제기 | 기존에 모달이 다른 요소에 감춰지던 버그 수정 + 실제 제출 기능 구현 |
4-2. 급여 대장 자동 생성 — 오류 안내 개선
/admin/payroll/ledger에서 급여 대장 자동 생성 버튼 클릭 시 실패하는 케이스가 있었으며, 실패 원인을 사용자가 인지할 수 없었습니다. fix-task-0619.md
- 원인 예시:
{"detail": "발행/확정된 급여명세서가 없습니다."} - 수정 내용: 서버가 반환하는
detail메시지를 토스트 알림 공통 컴포넌트를 통해 화면에 표시하도록 변경
4-3. 급여 기초관리 — 계약형태 표시 오류 수정
급여관리 > 급여 기초관리 > 급여 기초 페이지에서 계약형태가 OUTSOURCE로 노출되는 버그가 수정되었습니다. fix-task-0622.md
4-4. 4대보험 요율 관리자 설정
4대보험은 세무사에게 급여 서류를 받아 급여 기초에서 엑셀로 등록하는 방식으로 운영됩니다. 별도의 요율 계산 없이 엑셀 입력값 기반으로 계산 및 명세서에 표시됩니다. 현재 구현(엑셀 입력값 우선, fallback만 요율 자동계산)이 이미 요구사항을 충족하므로 별도 설정 UI는 불필요한 것으로 확인되었습니다. fix-task-0629 (26년 06월 29일 수정사항)
4-5. 회계·급여 정책
기본설정 > 회계·급여 정책 메뉴의 UI/CRUD가 완비되었으며, 주요 항목이 payroll_service.py:238-254에 이미 반영된 것을 확인하였습니다. 반영된 항목: salary_calc_start_day / salary_calc_end_day, salary_pay_day, salary_holiday_rule, annual_leave_basis. fix-task-0629 (26년 06월 29일 수정사항)
4-6. 중소기업 감면
중소기업 소득세 감면 기능이 UI→API→DB→급여계산까지 end-to-end로 동작합니다. fix-task-0629 (26년 06월 29일 수정사항)
| 항목 | 내용 |
|---|---|
| 대상 여부 + 감면율 | UI·API·DB·급여계산 전 구간 연동 완료 |
| 감면기간 날짜 입력 UI | 시작일/종료일 date picker + 백엔드 필드 추가 |
| 급여명세서 표시 | 소득세 항목에 감면율·원래세액·감면액 표시 추가 |
| 연간 한도 200만원 누적 체크 | 미구현 (대부분 직원 한도 미도달, 추후 필요 시 구현) |
5. 미완료 항목
급여대장 엑셀 다운로드 및 나야넷 양식
/admin/payroll/ledger 페이지에서 다음 두 가지 기능은 아직 미구현 상태입니다. fix-task-0623 (26년 06월 23일 수정사항)
- 1.엑셀 다운로드 — 급여대장 전체 데이터를 엑셀 파일로 내려받는 기능
- 2.나야넷 기본틀 양식 다운로드 — 나야넷 연동을 위한 표준 양식 파일 제공 기능
간이세액 비율 선택 UI 미구현
백엔드에 tax_rate_pct 컬럼(기본값 100)과 소득세 계산 시 비율 적용 로직은 구현되어 있으나, 프론트엔드 급여기초 페이지(/admin/payroll/base)의 "간이세액 (부양가족)" 영역에 비율 선택 입력이 없어 항상 100%로만 동작합니다. fix-task-0713 (26년 07월 13일 수정사항)
수정 대상: frontend/src/app/admin/payroll/base/page.tsx — "간이세액 (부양가족)" 영역에 80/100/120% 선택 드롭다운 또는 라디오 추가, 저장 시 tax_rate_pct 값 전송
도급직 auto_renew 기본값 수정 완료
도급직(CONTRACT_OUTSOURCE) 계약 생성 시 auto_renew=true가 기본값이었으나 법적 근거가 없어, 모든 계약 유형에서 auto_renew=False 기본값으로 통일되었습니다. 관리자가 명시적으로 선택한 경우에만 자동연장이 적용됩니다. 관련 내용은 근로계약서 스펙 참고. fix-task-0715 수정사항
수정 완료: backend/app/services/contract_service.py:627-629 — 도급직 자동연장 기본값 로직 제거
급여기초 관리 — 간이세액 비율 선택 UI 미구현 (07-14·07-15 재확인)
fix-task-0714 (26년 07월 14일 수정사항)와 fix-task-0715 수정사항에서도 동일 이슈가 재확인되었습니다. 백엔드 tax_rate_pct 컬럼 및 비율 적용 로직은 구현되어 있으나, 프론트엔드 /admin/payroll/base 페이지의 "간이세액 (부양가족)" 영역에 80/100/120% 선택 드롭다운 또는 라디오 입력이 없어 항상 100%로만 동작합니다. fix-task-0714 (26년 07월 14일 수정사항)
6. 개발자 의사결정 사항 (4가지)
Q1. 고정연장수당 + 실제연장수당 처리 정책
고정연장수당(FIXED_OVERTIME)이 있는 직원에게 실제 연장수당(PAY_OVERTIME)도 지급해야 하는 케이스가 있는가? 아니면 둘 중 하나만? fix-task-0623 (26년 06월 23일 수정사항)
선택지:
- (A) 포괄임금제 해석: FIXED_OVERTIME은 월 고정 연장수당 약정. 실제 연장이 고정분을 초과하면 초과분만 PAY_OVERTIME 지급. 미초과 시 PAY_OVERTIME = 0.
- (B) 현행 유지: 고정연장은 고정 수당, 실제연장은 근태 기반 별도 수당. 이중지급 허용 (회사 정책에 따라).
- (C) 관리자 설정: PayItem 또는 근무 정책에 "고정연장 적용 시 근태연장 스킵" 플래그 추가.
Q2. 근로정책 변경 시 급여기초 자동갱신 vs 알림
근로정책이 변경되면 기존 직원의 급여기초(monthly_work_hours, hourly_wage)를 어떻게 처리할 것인가? fix-task-0623 (26년 06월 23일 수정사항)
선택지:
- (A) 자동 갱신:
update_work_policy()후처리로 해당 정책 적용 직원의 EmployeePayBase 일괄 업데이트. - (B) 알림만: 관리자에게 "근로정책 변경됨, 급여기초 확인 필요" 알림 발송. 수동 갱신.
- (C) 명세서 생성 시점 동적 조회: EmployeePayBase의 monthly_work_hours를 사용하지 않고, 명세서 생성 시 직원별 유효 근로정책에서 월 소정근로시간을 동적으로 가져옴.
Q3. 야간수당 배율 해석
야간수당 0.5배는 "추가 가산"의 의미이며, 근로정책의 night_surcharge_rate는 1.5(전체 배율)를 저장합니다. fix-task-0623 (26년 06월 23일 수정사항)
해석 혼동:
- 일반 직원 야간근로: 기본급에 야간 미포함 → 가산분 0.5배만 지급 (현행)
- 야간 전용 근무자: 기본급에 야간 미포함 → 1.5배 지급? 아니면 현행과 동일하게 0.5배?
night_surcharge_rate를 급여 계산에 사용할 경우, 가산분 =night_surcharge_rate - 1.0으로 변환해야 하는지?
검토 필요: 근무 정책 스펙에서 night_surcharge_rate의 의미 정의와 급여 계산 로직 간 일관성 확보.
Q4. 근로정책 할증률 직접 사용 여부
근로정책의 holiday_surcharge_rate, night_surcharge_rate를 급여 계산에 직접 사용할 것인가? fix-task-0623 (26년 06월 23일 수정사항)
현황:
- 이 필드들은 근로정책 UI 표시용으로만 사용되고, 급여 계산에는 참조되지 않음.
- 법정 할증률(연장 1.5, 야간 0.5 가산, 휴일 1.5, 휴일초과 2.0)은 근로기준법에 고정이므로, 정책별로 다르게 설정할 실익이 제한적.
- 다만, 회사 자체 규정으로 법정 이상 할증을 적용하는 경우(예: 휴일 2.0배)에는 정책 참조가 필요.
결정 기준:
- 1.법정 최저 할증 보장 필수인가? (예: 휴일은 최소 1.5배)
- 2.회사 정책이 법정 이상 할증을 설정할 수 있도록 지원할 것인가?
- 3.근로정책 변경 → 급여 계산 자동 영향 범위는 어디까지인가?
7. 관련 페이지 및 스펙
- 근무 정책 스펙 — 근로정책 정의 및 구조
- 근태 관리 스펙 — 급여 산정의 근간 (연장, 야간, 휴일 시간)
- 휴가 관리 스펙 — 연차 미사용 일수 조회
- 연차 자동계산 로직 스펙 — 연차수당 계산 기준
- 만근 수당 스펙 — 만근 판단 로직
- 결재 관리 스펙 — 급여명세서 이의 제기 결재 연동
- 모바일 UI/UX 개선 작업 (기업 사용자) — 급여명세서 모달 개선 맥락
- 토스트 알림 공통 컴포넌트 스펙 — 오류 메시지 표시 방식
- 급여 계산 하드코딩 이슈 목록 및 개선 방향 — 본 문서의 보완 가이드
- 근무 정책 ↔ 급여 연동 현황 및 이슈 — 연동 상태 심화 분석
- fix-task-0713 (26년 07월 13일 수정사항) — 간이세액 UI·도급직 auto_renew 등 급여 관련 미구현 사항 원본
- fix-task-0715 수정사항 — 근로정책 적용시점·이력 덮어쓰기 수정, 도급직 auto_renew 기본값 확정, work_days OFF 출근 차단 등 07월 15일 수정사항 원본
8. E2E 테스트 시나리오 — 급여 정산·명세서 흐름
관리자(허빈)↔직원(김소민) 연계 시나리오 중 급여 관련 흐름입니다. 전체 시나리오는 E2E 테스트 시나리오 명세를 참고하세요. E2E 테스트 시나리오 - Part 3. 관리자↔사용자 연계 시나리오
5-1. 급여 정산 및 명세서 발행
| # | 액터 | 시나리오 | 기대 결과 | 비고 |
|---|---|---|---|---|
| 1 | 허빈 | 급여관리 > 급여기초에서 김소민의 급여 항목을 설정한다 | EmployeePayBase + EmployeePayDetail이 저장된다 | |
| 2 | 허빈 | 급여관리 > 명세서에서 해당 월 명세서를 생성한다 (정산 기간, 지급일 설정) | 비동기 생성 잡이 실행되고, 김소민의 Payslip이 PAY_DRAFT 상태로 생성된다 | |
| 3 | 허빈 | 명세서를 확정(confirm)한다 | PAY_CONFIRMED 상태로 변경된다 | |
| 4 | 허빈 | 명세서를 발행(issue)한다 | PAY_ISSUED 상태로 변경되고, 김소민에게 "명세서 발행" 알림이 전송된다 |
5-2. 직원 명세서 확인 및 이의제기
| # | 액터 | 시나리오 | 기대 결과 | 비고 |
|---|---|---|---|---|
| 5 | 김소민 | 급여관리에서 해당 월 명세서를 조회한다 | 급여 항목, 공제 항목, 실수령액이 표시된다 | PAY_ISSUED 상태부터 조회 가능 |
| 6 | 김소민 | 명세서 수신 확인 서명을 한다 | AuditSignature가 기록된다 | 근기법 48조 교부 입증 |
| 7 | 김소민 | 명세서에 이의를 제기한다 (사유 입력) | PayslipObjection이 생성되고, 관리자에게 알림이 전송된다 | |
| 8 | 허빈 | 급여관리 > 이의제기에서 김소민의 이의를 확인하고 응답한다 | 이의가 처리 완료된다 |