다크모드
HR 급여 (BE 참고)
2026-07-18 as-built. 계산 정본은
payrollMock.js, 상태·저장 seam은usePayrollRun.js와payrollRepo.js, 입력 경계 정본은20260822000000_organization_workspace_canonical_baseline.sql이다. 근태 provider는 선택 계약이며 운영 Supabase 배포는 별도 단계다.
상태와 선행 게이트
text
manual/csv/optional-provider ──capture──▶ payroll-input.ready
│
▼
payroll.draft ──confirm-v2──▶ payroll.confirmed ──mark_paid──▶ payroll.paid
▲ │
└────────── revert ────────────┘confirm-v2: 같은 Organization·Workspace·월의 유효한 payroll-owned input snapshot이 필수다.- 근태 상품은
time.payroll-input.v1read/event만 제공한다. payroll은 time table·FK·RPC를 참조하지 않는다. - 근태 entitlement가 없어도
manual또는csv입력으로 급여 핵심 흐름을 완료한다. revert: confirmed에서만 허용하며 사유 5자 이상이 필요하다. current snapshot 연결만 해제하고 과거 snapshot은 삭제하지 않는다.mark_paid: confirmed에서만 허용하며 paid는 불가역이다.- optional provider의 원본 변경은 input invalidation을 추가한다. 이미 confirmed/paid인 payroll snapshot은 변조하지 않는다.
- 모든 전이는
expected_revision낙관적 잠금과 Organization-scopedrequest_key멱등성을 사용한다.
데이터 모델
payroll_runs
- Organization·Workspace·월당 1행:
unique(organization_id, workspace_id, period_start). - 내부 UUID와 Organization-scoped 불변
management_code를 분리한다. - 현재 상태, revision, current snapshot FK, confirmed/paid actor와 시각만 소유한다.
- authenticated는 SELECT만 가능하며 direct INSERT/UPDATE/DELETE는 금지한다.
payroll_input_snapshots / payroll_input_invalidations
- payroll이 확정 선행 입력을 소유하며
manual | csv | time_adapter | legacy_bridge출처를 opaque metadata로 보존한다. - contract version, payload, summary, canonical hash, actor를 append-only로 보존한다.
- invalidation도 별도 append-only 원장이다. 무효화된 입력은 새 confirm에 사용할 수 없다.
- 기존 attendance FK는 제거했고 과거 confirmation은
legacy_bridge입력으로 backfill했다.
payroll_run_snapshots
- 매 confirm마다 1행을 추가하는 append-only 원장이다.
input_snapshot_idpayroll-local FK와 입력 hash·출처를 함께 보존한다.calculation_version, 직원별 지급·공제·계좌 snapshot, 서버가 재집계한 totals를 보존한다.- update/delete trigger는
payroll_history_append_only로 거부한다.
payroll_run_transitions
- confirm/revert/mark_paid의 from/to status, expected/result revision, actor, reason, request key를 append-only로 보존한다.
private.payroll_run_commands가 동일 요청 재시도 응답과 payload 충돌을 판정한다.
확정 RPC
sql
confirm_payroll_run_v2(
p_organization_id uuid,
p_workspace_id uuid,
p_period_start date,
p_input_snapshot_id uuid,
p_expected_revision integer,
p_calculation_version text,
p_snapshot jsonb,
p_request_key text
) returns jsonb처리 순서:
- auth, 유효 access Role
manager이상, Workspace의 Human Capital 상품 활성화를 확인한다. - request payload 멱등성을 확인한다.
- payroll input이 같은 Organization·Workspace·월이고 무효화되지 않았으며
ready=true,unresolvedCount=0인지 확인한다. - run을 생성하거나
FOR UPDATE로 잠근 뒤 draft와 revision을 확인한다. - snapshot 직원 중복, 금액 비음수,
net = gross - deductionTotal을 확인한다. - 각 직원이 Organization 소속이며 급여기간과 겹치는 유효한 Workspace 배치와 approved 근로계약을 갖는지 확인한다. 월중 퇴직자는
termination_date가 급여기간과 겹치면 포함할 수 있다. - totals는 client 값을 신뢰하지 않고 lines에서 서버가 재집계한다.
- immutable snapshot, current run, transition, operational audit, command 응답을 한 트랜잭션에 기록한다.
브라우저가 계산한 snapshot을 전달하는 것은 현재 Prototype 계산 엔진을 유지하기 위한 전환기 계약이다. 상용화 단계에서는 임금·공제 기준정보를 서버 원장으로 옮기고 서버 계산 결과만 확정해야 한다.
조회 권한과 민감정보
- 유효 access Role
manager이상은 서버가 허용한 Organization/Workspace 급여 조회·전이를 수행한다. viewer는 급여 mutation 권한을 받을 수 없다.- Organization Owner/Admin/Manager는 서버 정책이 허용한 Organization 범위 조회·전이를 수행한다.
- 일반 Workspace membership만으로는 급여를 읽을 수 없다.
- 급여 snapshot에는 계좌와 임금이 포함되므로 service role을 제외한 direct write를 모두 막고 RLS를 적용한다.
- 후속 필수: 계좌 column/envelope 암호화, 키 회전, 보존/파기, 조회 감사, export 통제.
프런트 계약
payrollRepo.js 서버 adapter:
loadAll({ organizationId, workspaceId })loadInputReadiness({ organizationId, workspaceId, periodStart })captureInput({ organizationId, workspaceId, periodStart, contractVersion, inputData, inputSummary, requestKey })confirmRun({ organizationId, workspaceId, periodStart, inputSnapshotId, expectedRevision, calculationVersion, snapshot, requestKey })revertRun({ organizationId, runId, expectedRevision, reason, requestKey })markPaid({ organizationId, runId, expectedRevision, requestKey })
테스트 모드 또는 Supabase 미설정 환경은 localStorage adapter를 사용한다. 운영 DB migration이 배포된 환경에서만 서버 RPC가 권위 원장이다.
이체·원천세 계약
- 이체 CSV: UTF-8 BOM +
은행코드,계좌번호,금액,예금주,내용; 금액은 snapshotnet. Σ(lines.net) = snapshot.totals.net = CSV amount 합계.- 원천세 요약: A01, headcount, taxablePaid, incomeTax, localIncomeTax, 익월 10일.
- 실제 은행 API, 전자신고 파일, 회계 분개는 이번 slice 밖이다.
검증
- SQL rollback smoke:
supabase/tests/payroll_local_input_snapshots_smoke.sql. - optional provider smoke:
supabase/tests/time_payroll_input_provider_smoke.sql. - migration contract:
src/composables/__tests__/payrollInputSnapshotMigration.spec.js. - repository:
src/composables/payrollRepo.test.js. - 상태·근태 게이트·스냅샷·CSV·원천세:
src/composables/usePayrollRun.test.js.
Smoke는 payroll-only entitlement의 manual capture·confirm-v2 재시도, legacy backfill, append-only, direct write 차단과 RLS를 검증한다. Provider smoke는 time-only entitlement의 close ready event, versioned read contract, reopen invalidation event와 payroll 객체 무참조를 검증한다.