HBHR280_법정공제 시트 (4대보험·소득세 월별)
(Hereby) HR 급여 관리 > 시트 워크스페이스 > 법정공제 시트
| 앱 | HR 급여 관리 (hr-system, 포트 4000) |
| 대상 사용자 | 공제 시트 조회·편집·확정 capability 보유자(실제 RBAC 토큰 TBU) |
| 프로토타입 | mockup-deduction-v2.2.html 장면 ⓪→① — HBHR280 법정공제 시트 |
| 관련 정책 | [[POL_공제_경계]] · [[POL_공제_항목·요율]] · [[POL_공제_산정기초·비과세]] · [[POL_공제_판정]] · [[POL_공제_계산·확정]] · [[POL_공제_잠정·정산]] |
| Status | draft |
| Owner | 카나 |
| 부모 | [[HBHR100_시트 워크스페이스 (공통 Shell)]] |
| 하위 또는 관련 | [[HBHR281_판정 상세·수동 판정 모달]] · [[HBHR282_계산 진단 패널 드로어]] · [[HBHR290_병행운영 대사 리포트]] · [[HBAD271_법정 요율 안내]] · [[HBAD272_공제 산정 모드]] |
개요
- 정책상 법정공제 시트는 재직자 전원 × 상시 6종(국민연금·건강보험·장기요양보험·고용보험·소득세·지방소득세)의 판정과 계산을 확인하는 작업공간이다. 판정·계산 규칙은 이 문서가 재정의하지 않고 위 정책과 FIG 설계를 따른다.
- 구현 상태를 구분한다.
feat/statutory-deduction-poc에는 계산·API·커밋 PoC 근거가 있으나 dev 미배포다. 판정 배지, 요율 hover, 진단·대사 진입을 포함한 본 UI는 목업/사양이며 라이브 구현으로 간주하지 않는다. - 시트의 계산값은 preview이고, 한 번의 커밋에서 상시 6종 모두
DeductionRecord가 된다.seed-sheet-templates.ts에서 4대보험 4열과income_tax·local_income_tax2열 모두commitToKind="deduction"으로 확인됐다. - 고용보험 fixture는 귀속월 유효 요율이 있는 상태로 통일한다. 유효 요율 부재 시에는 금액과 정상 상태를 동시에 보여 주지 않는다.
- 코드 근거 시점: 로컬
hereby-dhubcommit0840824d38051acf1dba547015c7f70614f5a930(2026-08-22 13:26:38 KST). 카나의 변경 완료 보고는 현재 Confluence 저장본문 근거로 채택하지 않고, 생성 동선은 사용자 채널 지적/본 개정 요청으로 추적한다. insurable_gross는 현재 템플릿·급여대장의 보험료 산정기초 구현 키다. 이를 과세소득과 법적으로 동치라고 정의하지 않는다.
문서 히스토리
| 날짜 | 작성자 | 팀 | 변경 내용 |
|---|---|---|---|
| 2026-08-24 | 카나 | PM | 최초 작성 |
| 2026-08-25 | Ruby | Product | 인수 리뷰 반영: 구현 표기, 6종 커밋, 항목별 판정, fail-safe, 공통 계약·상태 교정 |
| 2026-09-06 | Ruby | Product | 로컬 리뷰 v2.1: 누락된 생성 동선·데이터 방향 추가, system template 시작 상태 불일치와 미결 제안 분리 |
| 2026-09-29 | 카나 | PM | 공제 베타 dev 배포 관측(#907/#912, v0.14.0) — "dev 미배포·PoC" 전제 붕괴. okbest dev 추천 템플릿 21종에 「법정공제 — 4대보험 (월별)」 [베타] 노출 실측. 구현 대비 차이는 OQ로만 기록(본문 재작성은 정규 배치에서) |
정책 → 로직 → UI 경계
| 층위 | 이 문서에서 따르는 내용 |
|---|---|
| 정책 | 전 직원 × 6종 판정, 제외 사유 기록, 유효 요율·세액표가 없는 확정 금지, 확정 시 근거 동결 |
| 비즈니스 로직 | 귀속월 기준 판정·요율/표 조회, 6열 계산, 게이트 검증, 6종 원장 커밋, stale 판정 |
| UI 로직 | 판정·경고·계산값을 표시하고 상세/진단/대사로 연결하며 커밋 결과를 피드백 |
| 사양 | 아래 필드·상태·분기. procedure명·RBAC 토큰·문구 원문은 확인 전 TBU |
실제 Hereby 화면 구조 재사용
- [[HBHR100_시트 워크스페이스 (공통 Shell)]]의 실제 구조를 그대로 쓴다: 상단 월 선택 툴바 → 좌측 시트 사이드바 → 가운데
sheet-grid→ 우측 고정 레일의 3단 구조다. 법정공제 전용 페이지 Shell을 새로 만들지 않는다. - 시트명·수명주기 배지·검색·메모/셀 상세·히스토리·반영 상태는 공통 시트 헤더/툴바를 재사용한다.
- 확정 CTA 명칭·위치는 실제 그리드의 툴바 우측
급여대장에 반영버튼과반영 필요/반영됨/수당 변경됨 — 재반영 필요라벨을 따른다. 별도 하단 커밋 바를 만들지 않는다. - 판정/진단 상세가 레일형이면 기존
w-80우측 레일 슬롯을 사용하고 셀 상세·메모·열 설정·급여 반영·행 정보·히스토리 레일과 상호 배제한다. 정확한 진입 버튼과 레일 콘텐츠는 미구현/TBU다. - 상태 배지는 기존
.ds-label, 버튼은 기존ds-btn, 툴팁은 기존 DS tooltip을 사용한다. 이 사양은 새 배지·버튼·테이블 계열을 정의하지 않는다.
생성·진입 프로세스 (v2.1 추가)
⚠ 별도 "공제 시트 없음" 진입 화면은 없다 (콸 지적 2026-09-06). 공제 시트는 도메인 전용 빈 화면/준비 화면으로 진입하지 않고, 급여 워크스페이스의 기존 시트 생성 동선([[HBHR101_새 시트 만들기 모달]])을 그대로 재사용한다 — 다른 시트 유형과 동일. 시트가 하나도 없을 때 뜨는 것은 워크스페이스 공통 빈 상태("시트가 없어요")뿐이며, "이 달에는 공제 시트가 없어요" 같은 공제 전용 빈 화면을 새로 만들지 않는다(실존하지 않는 UI 금지 — 기획 대원칙).
검증된 현재 구현
- 워크스페이스의 + 새 시트 만들기로 DS 모달을 연다 (HBHR101 실존 모달 — 공제 전용 진입점 아님).
- 모달의 4개 탭 중 추천 템플릿(
mode="system")을 선택한다.sheetTemplate.list({ includeSystem: true })가 active system template을 조직 템플릿과 함께 조회한다. - 「법정공제 — 4대보험 (월별)」 카드를 클릭한다. UI
applyTemplate(tpl)이 현재 연·월로sheetTemplate.apply(SDK alias; router 구현applyToPeriod)를 호출한다. 카드 클릭 자체가 적용 액션이며 별도 확인 CTA는 코드에서 확인되지 않았다. - 서버가 템플릿 레이아웃·slug(
statutory_insurance)를 현재 기간SheetView로 복제한다. slug가 이미 있으면 같은 기간 기존 시트를 재사용한다. 완료 후 목록을 새로 읽고 생성/재사용 시트를 연다. - 템플릿 identity 열의
sourceTable:"employee"가 materializer를 구동한다. materializer는 조직 범위에서 비활성 직원을 제외하고, 해당 월 말보다 먼저 입사한 직원을 1인 1행으로 만든다. 따라서 재직자 auto-populate는 시트 단위defaultAutoPopulate가 아니라 컬럼 source binding 근거다.
시작 상태 — 확정: 사용 중 시작 (2026-09-06 코드 확증)
- 확정: system template(법정공제 포함)은 적용 즉시
setupCompletedAt = new Date()로 저장 → 생성 즉시 「사용 중」 시작. develop 코드 확인(packages/api/src/routers/sheet-template.router.ts:252·391—tpl.isSystem ? new Date() : null). 기획 초기의 「설정 중 시작」 표현은 폐기(일반 시트 기준 오인계, 카나 정정). - 템플릿 구조를 사용자가 조정해야 하는 제품 결정이 확정되면 system template에도
setupCompletedAt=null을 적용할지, 시스템 소유 구조를 유지할지 TBU다. 설정 중이면commitToRecords가 서버에서 반영을 거부하므로 설정 완료 → 반영 순서를 함께 정의해야 한다.
데이터 방향
- 급여대장 → 공제시트는 산정기초 읽기, 공제시트 → 공제기록 → 급여대장은 반영 결과 집계 방향이다.
insurable_gross명칭만으로 과세소득과 법적 동일성을 주장하지 않는다.
Data I/O
Server → Client
| 변수 | 근거/의미 | 값 없을 때 | 비고 |
|---|---|---|---|
${sheet.rows} | 조직 범위의 재직자 auto-populate 행 | Empty | 다른 조직 데이터 혼입 금지 |
${judgment.matrix} | 직원 × 상시 6종의 자동/수동 판정, 근거, 판정 입력 완전성 | 입력 부재면 계산 유지 + 경고 | 커밋 전 미해소 판정불가는 fail-safe 차단 |
${statutory.rates} | 귀속월에 유효한 4대보험 요율·상하한 | 해당 항목 N/A + 커밋 차단 | 고용보험 fixture는 유효 상태 |
${tax.table} | 귀속월에 유효한 간이세액표·자녀 차감 파라미터 | 소득세/지방소득세 미계산 + 커밋 차단 | 조용한 0 금지 |
${insurable_gross} | 급여대장 「보험료 기초(과세보수)」 | 목표: 실패/경고 | 현재 PoC fallback과 목표 분리 |
${commitStatus} | 커밋 여부, upstream 신선도, version/lock | stale/conflict/locked 표시 | 조직·귀속월 단위 |
현재 PoC와 목표 동작
| 상황 | 현재 PoC 관찰 | 제품 목표 사양 |
|---|---|---|
| 급여대장 미커밋/기초 부재 | 계약 기본급 fallback | fallback하지 않고 계산/확정 전 명시적 실패 또는 경고. 최종 차단 시점·문구 TBU |
| 판정 입력 부재 | 제외가 명시적으로 참이 아니면 계산 유지 | 계산 유지 + 판정불가 경고, 커밋 전 반드시 해소 |
| 귀속월 요율 부재 | N/A/커밋 게이트 근거 있음 | 해당 항목 N/A, 한 커밋 전체 차단, 기존 확정분 불변 |
| 소득세 표/구간 부재 | 셀 비움·warn | 소득세/지방소득세 미계산, 한 커밋 전체 차단 |
Client → Server와 공통 mutation 계약
| 액션 | 제품 capability | 전달 값 | 계약 |
|---|---|---|---|
| 리터럴 셀 편집 | 공제 시트 편집 | organization context, sheetViewId, rowId, columnKey, value, expectedVersion, idempotencyKey | procedure·토큰 TBU |
| 재직자 채우기/재계산 | 공제 시트 편집 | organization context, sheetViewId, expectedVersion, idempotencyKey | 서버가 조직 scope 재검증 |
| 6종 커밋 | 공제 확정 | organization context, sheetViewId, effectiveMonth, expectedVersion, idempotencyKey | 게이트 통과 후 단일 트랜잭션 |
organizationId는 인증 컨텍스트에서 확정하며 클라이언트 값만 신뢰하지 않는다.- capability는 제품 의미로만 적는다. 실제 RBAC 토큰은 census 전 확정하지 않는다.
- mutation은
idempotencyKey로 재시도 중복을 막고expectedVersion으로 낙관적 잠금을 수행한다. 불일치는conflict, 마감/외부 잠금은locked로 응답한다. - audit envelope 최소 필드:
organizationId,actorId,occurredAt,action,resourceType/resourceId,effectiveMonth,before,after,reason(해당 시),source/provenance(해당 시),idempotencyKey,requestId,version.
6종 커밋 앵커
| 항목 | source column | target column | 결과 |
|---|---|---|---|
| 국민연금 | nps | ded_statutory_nps | DeductionRecord |
| 건강보험 | hi | ded_statutory_hi | DeductionRecord |
| 장기요양보험 | ltc | ded_statutory_ltc | DeductionRecord |
| 고용보험 | ui | ded_statutory_ui | DeductionRecord |
| 소득세 | incomeTax / income_tax | ded_statutory_income_tax | DeductionRecord |
| 지방소득세 | localTax / local_income_tax | ded_statutory_local_tax | DeductionRecord |
모든 앵커는 payroll_ledger를 대상으로 하고 commitToKind="deduction"이다. 따라서 예상 건수는 대상 직원 n명 × 6종이며, 17명 fixture이면 102건이다. 일부 항목이 제외 판정으로 0원이어도 판정 기록 요건과 실제 레코드 생성/0원 보존 규칙은 별도 확인이 필요하다(Open Question).
화면 플로우 (Mermaid)
컬럼 정의
| 그룹 | 컬럼 | 동작 |
|---|---|---|
| 시스템 | #·사번·성명·팀 | 조직 범위 read-only |
| 판정 | 국민연금·건강보험·장기요양보험·고용보험·소득세·지방소득세 판정 | 각 항목 적용/제외/판정불가와 근거. 클릭 시 [[HBHR281_판정 상세·수동 판정 모달]] |
| 판정 입력 | 생년월일·입사일·주 소정근로시간·급여 마감일 등 | 숨김 입력. 항목마다 필요한 입력이 다름 |
| 기초 | 보험료 기초(과세보수) | [payroll_ledger:insurable_gross] 근거. 정확 slug는 TBU |
| 계산 | 국민연금·건강보험·장기요양보험·고용보험 | 귀속월 요율·상하한. 고용보험 유효 fixture 0.9% 예시 |
| 세액 입력 | 부양가족수·8~20세 자녀수·원천징수비율 | 입력 완전성 검증 |
| 세액 계산 | 소득세·지방소득세 | 간이세액표/초과 산식·지방세 계산 |
| 커밋 | 툴바 우측 급여대장에 반영 | 상시 6종 단일 커밋 |
법정공제 시트 사양표
| 번호 | 항목/기능명 | 사양 및 기능 설명 | 구현 표기 |
|---|---|---|---|
| 00 | 노출 조건 | 공제 시트 조회 capability와 organization scope 확인 | UI TBU |
| 01 | 생성 진입 | + 새 시트 만들기 → 추천 템플릿 → 법정공제 카드 클릭 적용 → 현재 월 시트 열기 | UI·router 코드 검증 |
| 02 | 재직자 행 구성 | employee source binding으로 조직·기간 범위의 재직자 1인 1행 materialize | 템플릿·materializer 코드 검증 |
| 03 | 시작 상태 | system template은 적용 즉시 「사용 중」 시작 (setupCompletedAt 즉시 설정) | ✅ 코드 확증(sheet-template.router.ts:252·391) |
| 03b | 시트 생성 방식 | 현재 수동 생성(+새 시트 만들기 → 추천 템플릿). 급여대장은 자동 생성이나 공제 시트는 수동 → 미생성 월 통누락 리스크 | 📋 미결: 월 자동 생성+미반영 마감 경고 제안([[POL_공제_판정]]·기획서 §7) |
| 10 | 항목별 판정 | 6종 각각 적용/제외/판정불가. 공통 배지 하나로 4대보험을 함께 0 처리하지 않음 | 판정 계산 PoC 근거 / UI 목업 |
| 20 | 판정 상세 | 행/항목 클릭 시 [[HBHR281_판정 상세·수동 판정 모달]] | 미배포 UI |
| 30 | 요율·표 상태 | 귀속월 유효 요율/표와 출처를 표시. 부재 시 N/A/미계산과 원인코드 | UI TBU |
| 40 | 계산 진단 | 경고 집계에서 [[HBHR282_계산 진단 패널 드로어]]로 진입 | 제안 UI |
| 50 | 대사 | [[HBHR290_병행운영 대사 리포트]]로 진입 | 제안 UI |
| 60 | 6종 커밋 | n×6 record를 단일 트랜잭션으로 확정. 판정불가·요율/표/기초 부재·stale·lock이면 차단 | 계산/API/커밋 PoC 근거 / UI 미배포 |
| 70 | 성공 | 생성/갱신 record 수, 귀속월, 커밋 version 표시 | 문구 TBU |
공통 상태
| 상태 | 표시·행동 |
|---|---|
| loading | 기존 내용 유지 가능 시 유지하고 로딩 표시; mutation 비활성 |
| empty | 대상 재직자 0명 안내; 커밋 비활성 |
| dirty | 편집 후 미저장/미커밋 표시 |
| saving | 중복 입력 방지, 동일 idempotencyKey 재사용 |
| success | 6종 커밋 건수·귀속월·version 표시 |
| error | 원인코드와 재시도/해결 경로; 조용한 0 금지 |
| forbidden | capability 또는 organization scope 불충족, 데이터 미노출 |
| conflict | expectedVersion 불일치, 최신값 재조회 후 사용자 재확인 |
| stale | 상류 수당/판정/요율 변경 표시, 재계산·재커밋 유도 |
| locked | 급여 마감·외부 잠금으로 편집/커밋 차단, 잠금 주체·시각 표시 |
권한 요청 / 분기 요약
| 시나리오 | 결과 |
|---|---|
| 조회 capability 없음/다른 조직 | forbidden, 데이터 미노출 |
| 판정 입력 부재 | 계산 유지 + 항목별 경고, 커밋 차단 |
| 6종 입력·요율·표 정상 | 한 커밋에서 n×6 DeductionRecord |
| version 충돌 | 저장하지 않고 conflict |
| stale/locked | 원인 해소 전 커밋 차단 |
미결 제안 — 현재 구현 아님
- 매월 자동 생성: 월 변경/월초에 법정공제 시트를 자동 provision할지, 사용자 명시 적용을 유지할지 미결. 트리거·중복방지·실패 복구·권한·감사 계약 TBU.
- 미반영 월마감 경고: 마감 시 법정공제 시트 미생성/미반영/stale을 경고할지 미결. 마감의 정본, 차단/경고 수준, 해결 CTA, 예외권한 TBU.
Open Question
- 0원 제외 항목도 DeductionRecord를 생성해 판정과 함께 보존할지, 0원 레코드를 생략하고 별도 판정 정본을 둘지.
- 급여대장 기초 부재의 계산 차단 시점(즉시 계산 실패 vs preview 경고 후 커밋 차단)과 확정 문구.
- 수동 금액 보정의 사유 강제 여부와 자동 계산값/채택값/audit 저장 계약([[POL_공제_계산·확정]] 미결).
- 실제 SDK alias(
sheetTemplate.apply)와 router key(applyToPeriod)의 공개 API 명칭 정합, RBAC 토큰·lock/version 응답 schema·UI 원문. [payroll_ledger:insurable_gross]의 라이브 slug와 6종 판정 사유코드 전체 enum. 이 키와 세법상 과세소득/보수월액의 법적 관계는 정책·법률 근거 확인 전 TBU.- ⚠ 베타 배포로 전제 붕괴(9/29 실측, #907/#912) — 본 문서의 "dev 미배포·PoC" 전제가 무효. 구현 vs 문서 차이:
- 템플릿명 "법정공제 — 4대보험 (월별)" [베타] vs 문서 제목 "4대보험·소득세 월별" — 명칭 정합 필요
- 템플릿 설명문은 "4대보험만 커밋"처럼 읽히나 실제는 소득세·지방소득세 2열 포함 6종 모두
commitToKind=deduction커밋 — 사용자 오해 소지, 설명문 수정 여부 - 구현 템플릿 35열(판정 수동 오버라이드 포함), 간이세액표 2024-02-29 기준 7,128행,
statutory.*파라미터 — 문서 컬럼 정의와 대사 필요 - opt-in 방식: #912 본문 "켠 조직에서만 보임" vs 실측 "전 조직에 템플릿 노출, 시트를 만들어야 동작"(커밋 본문 기준) — 표현 불일치, 실제 게이트 확인 필요
- → 본문 전면 개정은 시트 생성 실사(쓰기 금지 해제 필요) 포함 정규 배치에서 진행