Skip to content

현재 사용법 (2026-09-10, 6fbaee7f): 법정공제·계산 기준·확정 검사 · 기능 마스터. 아래는 작성 당시의 변경 설명이며 현재 전체 동작·재측정 결과를 뜻하지 않습니다.

법정공제 엔진 (Phase 1 PoC) — 4대보험·소득세 자동계산 ​

PR: feat(payroll): statutory deduction engine — rates, judgment matrix, simplified tax table 설계 문서: docs/internal/design-statutory-deduction-phase1.md (§0~§7, 결정 8건 확정)

급여 담당자가 「법정공제 — 4대보험 (월별)」 시트를 만들면 재직자 전원의 국민연금·건강보험·장기요양·고용보험·소득세·지방소득세가 자동으로 계산되고, ✓ 커밋 한 번으로 급여대장에 반영됩니다. 지금까지는 이 여섯 항목을 외부 시스템(eCount)이나 수기로 계산해 붙여넣어야 했습니다.

배경 — 이 변경이 딛고 선 지형 ​

이 리포의 급여는 시트(스프레드시트형 화면) 위에서 만들어집니다. 시트의 열에는 종류가 있는데, 이 PR이 주로 다루는 것은 세 가지입니다. 수식 열은 =ROUND([기본급] * 4.5 / 100) 같은 식을 행마다 평가하는 열이고, computed 열은 서버의 계산 모듈(codeModule)이 값을 채우는 열이며, 집계(aggregate) 열은 급여대장에서 커밋된 레코드를 합산하는 열입니다. 시트에서 「✓ 커밋」을 누르면 커밋 표시가 붙은 열의 값이 급여의 원장인 AllowanceRecord(수당)·DeductionRecord(공제) 레코드로 확정됩니다.

변경 전에는 법정공제를 계산해서 이 원장에 넣는 살아 있는 경로가 하나도 없었습니다. 4대보험·소득세 computed 열은 서버 입력 로더의 분기 문자열("insurance")과 모듈 등록 키("payroll_rate_config")가 달라 아무 셀도 쓰지 못한 채 조용히 끝났고, 유일하게 계산되는 곳은 시드 템플릿 수식에 하드코딩된 2024년 요율이었으며, 소득세는 실제 원천징수 방식(간이세액표 조회)이 아니라 월급을 12배 해 누진세율을 직접 적용하는 잘못된 산식이었습니다.

이 PR의 설계 원칙은 두 가지입니다. 첫째, 요율은 코드가 아니라 시행일별 파라미터입니다. 조직 파라미터 저장소(OrganizationParameter + 이력 테이블의 effectiveFrom)에 statutory.* 네임스페이스로 저장하고, 시트 수식이 [$statutory.national_pension.employee_rate] 형태로 참조하면 그 시트의 귀속월에 유효했던 값이 자동 선택됩니다. 요율이 개정돼도 시트는 수정할 필요가 없고, 과거 월을 다시 계산하면 당시 요율이 적용됩니다. 둘째, 조용한 0을 금지합니다. 계산할 수 없는 상황(파라미터 미등록, 세액표 미적재)은 0원으로 깔리는 대신 커밋 거부 또는 빈 셀 + 경고로 드러납니다.

변경 전 / 변경 후 — 공제 금액이 만들어지는 경로 ​

변경 전

변경 후

개념 흐름별 변경 ​

1. 요율 저장소와 시점 조회. 2026년 기준 요율·상하한 7종을 statutory.* 파라미터로 시드하고(seed-statutory-parameters.ts — 각 값에 근거 고시·법령을 기록), 화면 평가와 커밋이 같은 시점 해석 함수(resolveOrgConstantValues, resolver에서 추출)를 공유하게 했습니다. 신규 조직 프로비저닝이 이 시드를 자동 호출하고, 개정 반영은 update-statutory-parameter.ts 한 번 실행으로 전 조직에 새 시행일 버전을 붙입니다.

2. 산정 기초 — 급여대장 연동과 비과세. 4대보험의 기초는 계약 기본급이 아니라 그 달 커밋된 수당 합산에서 비과세를 뺀 값입니다. 이를 위해 급여대장에 「보험료 기초(과세보수)」 열을 신설했는데, 이 열은 시트로 커밋된 전 수당을 합산하는 새 집계 방식(scope:"sheet")을 씁니다. 비과세는 열 메타가 마스터이고 커밋 시점에 레코드로 동결됩니다: AllowanceRecord.taxExempt/taxExemptType/taxExemptAmount(비과세 인정액 — 식대 30만 원 지급 시 한도 20만만 인정되고 초과 10만은 자동으로 과세 기초에 포함). 법정공제 시트의 보수월액은 이 열을 크로스시트 참조로 읽고, 급여대장이 아직 없으면 계약 기본급으로 폴백하며 폴백 여부가 ⚠ 열로 표시됩니다.

3. 판정 매트릭스 — 누락 방지의 구조화. 재직자 전원 × 항목별 적용/제외가 시트 열로 가시화됩니다(연령·초단시간·연금/건보/고용 제외). 제외 조건이 명시적으로 참일 때만 공제가 0이 되는 fail-safe 설계라, 판정 입력(생년월일 등)이 없으면 공제는 계산되고 이상값이 화면에 드러납니다. 연령 기준일은 오늘이 아니라 급여 마감일이어서 과거 월 재계산에도 정확합니다.

4. 소득세 — 간이세액표 조회로 교체. 국세청 공식 배포본(별표2 개정 2024-02-29)을 파싱한 7,128행(646구간 × 가족수 11 + 규칙행)을 simplified_tax_tables에 적재하는 파이프라인을 만들었습니다(CSV 원본과 출처·검증 README는 packages/database/data/). 세액 모듈은 "조견표 세액 − 8~20세 자녀 차감(시행일 파라미터, 2026-03-01 개정 자동 적용) → 80/100/120% 비율 → 원단위 절사·소액부징수" 순서로 계산하고, 월급여 1,000만 원 초과분은 별표2 하단 법정 산식으로 이어집니다. 표가 없으면 셀을 비워 두고 경고를 남깁니다.

5. 조용한 실패를 막는 가드 3종. 첫째, 귀속월에 유효한 파라미터가 없는 상수를 수식이 참조하면 커밋이 거부됩니다(에러 메시지에 누락 경로 명시). 둘째, 공제 커밋 후 수당이 재커밋되면 시트 헤더 칩에 「수당 변경됨 — 재반영 필요」 배지가 뜹니다(commitStatus에 상류 신선도 필드 추가). 셋째, columnLayout에 같은 키의 열이 두 번 저장되는 것을 서버가 거부합니다 — 중복 키는 Alpine x-for 충돌로 헤더 행 전체가 화면에서 사라지는 조용한 렌더 붕괴를 일으키는 것을 실측했습니다.

6. 감사(lineage). 커밋된 공제 레코드에는 계산에 쓰인 요율·상하한·자녀 차감액의 당시 값이 appliedParameters/inputSnapshot으로 동결되고, sourceType이 기록됩니다. 요율을 나중에 고쳐도 확정분은 불변이며, 세무사가 레코드만으로 "어떤 규칙 버전으로 계산됐나"를 재현할 수 있습니다.

7. 함께 수리한 기존 결함. 4대보험·소득세 computed 열을 죽여 놓았던 모듈 키 불일치, 미지원 모듈의 무증상 skip(경고 로그 추가), 소득세 계산의 prior-deduction 필터가 NULL 비교로 무력화되던 문제를 수리했습니다.

검증. 실제 템플릿 수식을 수식 엔진으로 직접 실행하는 golden test 30케이스(상하한 클램프·판정 게이트·1970년 이전 생년월일의 음수 날짜 직렬값 가드·절사·자녀 차감·초과 산식 경계 정합)와, API 레벨 E2E(2025-12 시트가 자동으로 2025년 요율로 계산되는 시점 정합, 식대 한도 분리, 실표 표본 대조 — 월 300만/1인 = 74,350원 공표값 일치)를 통과했습니다.

규모에 대한 참고 ​

+9,509줄 중 약 7,100줄은 간이세액표 CSV(정적 데이터), 약 1,100줄은 설계 문서·법률 검토 질의서입니다. 실행 코드 변경은 그 나머지이며 단일 관심사(법정공제 엔진)입니다.