Skip to content

HR·전자결재 Company/Office 계약 — BE 핸드오프

소유권

  • 모든 레코드는 company_id 필수.
  • 직원·조직·계약·급여 run·인사문서·퇴직정산·전자결재 문서는 Company 원장.
  • 출퇴근·업무일지·휴가 발생은 office_id 필수. 사용자의 office_memberships 안에서만 처리.
  • 급여/문서/퇴직 조회 시 Office 필터를 제공할 수 있으나 원장의 소유권을 Office로 바꾸지 않는다.

HR 불변식

  1. 활성 직원과 유효한 근로계약이 있어야 근태/급여 대상이 된다.
  2. 급여는 payroll-owned 입력이 준비되고 unresolved input이 0건일 때 확정한다. 근태 마감본은 선택 provider다.
  3. 급여 paid는 종결 상태다. 정정은 원 run 변조가 아니라 별도 조정 run/다음 달 소급으로 남긴다.
  4. 명세서·증명서·퇴직정산서는 발급 시점 스냅샷이며 재계산으로 바뀌지 않는다.
  5. 민감정보·첨부는 필드별 암호화, 최소권한, 다운로드 감사로그, 보존·파기 정책을 적용한다.

전자결재 불변식

  1. 현재 단계 결재자 또는 기간·범위가 유효한 수임자만 처리한다.
  2. 완료/반려는 종결 상태이며 이후 단계 전이를 거부한다.
  3. 처리 이력은 append-only, 문서 본문/결재선은 상신 시점 스냅샷이다.
  4. company_id + request_id 멱등키와 row version으로 중복 승인·동시 처리를 막는다.
  5. 알림 실패는 결재 상태를 되돌리지 않고 별도 outbox에서 재시도한다.

API 오류 계약

권장 코드: COMPANY_SCOPE_MISMATCH, OFFICE_ACCESS_DENIED, ATTENDANCE_NOT_CLOSED, PAYROLL_ALREADY_PAID, APPROVAL_NOT_CURRENT_ACTOR, APPROVAL_TERMINAL, VERSION_CONFLICT. FE가 빈 상태·권한 차단·재시도 가능 오류를 구분할 수 있도록 HTTP 상태와 함께 안정적으로 반환한다.

근태 원장과 월 마감 계약

마이그레이션 20260717061000_attendance_period_close_gate.sql이 다음 경계를 구현한다.

  • attendance_events: Company·Office·직원·근무일 기준 append-only 출퇴근 원장. 로그인 사용자는 활성 workforce_identityworkforce_employee, 해당 일자에 유효한 Office assignment가 모두 있어야 record_attendance_event를 실행할 수 있다.
  • attendance_periods: (company_id, office_id, period_start)당 한 행. open | closed 상태와 단조 증가 revision을 가진다.
  • attendance_period_transitions: close/reopen causal history. (company_id, request_key)가 재시도를 멱등 처리하고, update/delete trigger가 과거 변조를 거부한다.
  • private.enforce_attendance_period_open: attendance_events INSERT 직전 현재 월이 닫혔는지 재확인한다. 브라우저 RPC뿐 아니라 이후 service 경로의 직접 INSERT도 닫힌 월에는 실패한다.

close_attendance_period는 Office 행 잠금과 expected_revision을 사용한다. 마지막 이벤트가 clock_in인 직원·일자가 하나라도 있거나 live 미해결 예외가 있으면 전체 트랜잭션을 거부한다. 성공 시 이벤트 수와 전체·해결·미해결 예외 수를 close_summary에 고정하고 attendance.period_closed 감사 이벤트를 같은 트랜잭션에서 남긴다.

reopen_attendance_period는 닫힌 기간에만 허용하며 5자 이상의 사유, 최신 revision, HR Company 관리자 또는 Office 관리자/운영자 권한을 요구한다. 성공 시 attendance.period_reopened 감사 이벤트와 append-only transition에 사유를 남긴다.

RLS는 Company 관리자, 해당 Office membership, 유효한 HR role grant만 기간을 읽게 한다. 일반 직원은 자신의 출퇴근만 읽고, 관리자는 Office 이벤트를 읽는다. authenticated의 테이블 직접 INSERT/UPDATE/DELETE는 허용하지 않는다.

교대·근무 일정 계약

마이그레이션 20260717075727_workforce_shift_schedule.sql이 출퇴근 이전의 예정 근무 기준을 구현한다.

  • workforce_shift_schedules: (company_id, office_id, period_start)당 월 근무표 한 행. draft | published, 단조 증가 revision, 확정 actor/시각을 보존한다.
  • workforce_scheduled_shifts: 직원·유효 Office assignment·근무일·시작/종료·무급 휴게분·역할을 보존하는 append-only 근무 구간이다. 익일 종료 교대는 허용하고 한 구간은 최대 36시간이다.
  • workforce_shift_schedule_transitions: draft → published 원인 이력. update/delete는 trigger가 거부한다.
  • private.workforce_shift_schedule_commands: create/publish 입력과 응답을 Company 요청키로 보존해 재시도를 멱등 처리한다.

create_workforce_scheduled_shift는 Office 관리권한, 월/Revision, 직원의 해당 일자 Office assignment, draft 상태, 근태 open 상태를 확인한다. 직원 UUID를 키로 transaction advisory lock을 잡고 기존 구간과 existing.starts_at < new.ends_at AND existing.ends_at > new.starts_at을 검사해 동시 겹침을 차단한다. INSERT trigger도 같은 assignment·draft·마감·겹침 검사를 실행하므로 privileged 직접 INSERT가 도메인 불변식을 우회하지 못한다. 성공 시 schedule revision을 1 증가시키고 workforce.shift_scheduled 감사를 남긴다.

publish_workforce_shift_schedule은 최신 Revision과 한 건 이상의 근무, 근태 open을 확인한 뒤 근무표를 확정한다. 확정은 이번 slice에서 불가역이며 workforce.shift_schedule_published 감사와 append-only transition을 기록한다.

RLS는 HR/Office 관리자가 Office 근무표를 읽고, 일반 직원은 활성 identity로 연결된 자기 근무만 읽도록 한다. authenticated의 직접 write는 전부 revoke하고 두 RPC만 명시적으로 grant한다. 새 public 테이블에는 RLS와 explicit SELECT grant를 함께 적용했다.

SQL rollback smoke는 supabase/tests/workforce_shift_schedule_smoke.sql이다. 멱등 생성·익일 교대·겹침 거부·확정 재시도·확정 후 쓰기 거부·감사·append-only를 검증한다.

예정 근무 연동 출퇴근·근태 예외 계약

마이그레이션 20260717110723_workforce_attendance_exceptions.sql이 예정과 실제의 DB 경계를 잇는다.

  • attendance_events.scheduled_shift_id는 Company·Office·직원까지 포함한 composite FK로 확정 예정 근무를 참조한다. shift별 출근/퇴근은 각각 한 건만 허용한다.
  • record_scheduled_attendance_event는 invoker public wrapper와 최소권한 private definer 구현으로 나뉜다. 로그인 직원·published shift·시간창·마감 상태·요청키를 재검증한다. 야간 익일 퇴근은 열린 출근의 scheduled_shift_idwork_date를 승계하므로 달력 날짜가 바뀌어도 원 근무일에 묶인다.
  • attendance_exception_overview WITH (security_invoker=true)는 published shift와 append-only punch를 live 조합해 absent | missing_checkout | late | early_leave | unscheduled을 투영한다. 미래 근무는 결근/퇴근누락으로 만들지 않는다.
  • attendance_exception_resolutions는 append-only HR 판단 원장이다. exception_key + evidence_fingerprint에 판단을 묶어 출퇴근 증거가 바뀌면 기존 해결을 자동 무효화한다.
  • resolve_attendance_exception은 Office HR 관리권한, open 기간, 현재 fingerprint, 5자 이상 사유, idempotency와 advisory lock을 검증하고 attendance.exception_resolved 감사를 같은 트랜잭션에 남긴다.
  • attendance close trigger는 live 미해결 건수를 재검사하고 1건 이상이면 attendance_period_unresolved_exceptions로 거부한다. 성공한 close_summary에는 exceptionCount, resolvedExceptionCount, unresolvedExceptionCount가 저장된다.
  • 마감·재오픈은 payroll table을 조회하지 않고 각각 time.payroll-input.v1 ready/invalidation event를 남긴다. 선택 adapter가 payroll-owned input으로 전달하며, consumer 장애는 근태 로컬 전이를 되돌리지 않는다.

resolution 테이블은 self/관리자 SELECT RLS, authenticated direct write revoke를 적용한다. 두 public RPC는 authenticated/service role만 실행하고 private definer는 정확한 signature grant만 유지한다. SQL rollback smoke supabase/tests/workforce_attendance_exceptions_smoke.sql은 익일 퇴근 pairing, 5종 projection, fingerprint stale 방어, 멱등 해결, 마감 gate/summary, 직접 write 차단과 감사를 검증한다. Payroll 연결은 time_payroll_input_provider_smoke.sql이 별도로 검증한다.

휴가 로컬 결정·선택 근태 snapshot 계약

forward migration 20260717160116_workforce_local_decision_ledgers.sql은 휴가 핵심 흐름을 Human Capital 단독으로 전환한다.

  • workforce_leave_requests.approval_document_id는 nullable opaque legacy reference이며 provider FK가 없다.
  • workforce_leave_decisionsreview_requested → approved | rejected 두 event를 보존하는 HR append-only ledger다.
  • create_workforce_leave_request는 self 또는 HR 관리자, Office human-capital entitlement, 활성 assignment, 승인 근로계약, 기존 진행/승인 휴가 비중복을 검증한다. 결재선 입력은 없다.
  • decide_workforce_leave_request는 HR 관리자, expected revision, pending 상태를 검증한다. 승인 시 중복 휴가와 연차 잔여를 다시 검사하고 balance consume, terminal decision, 휴가 상태, 감사를 같은 HR 트랜잭션에 저장한다.
  • Time capability가 available일 때만 workforce_leave_time_check_snapshot이 published shift 충돌 건수를 캡처하고 승인 시 재검사한다. unavailable이면 { availability: 'unavailable' } snapshot을 보존하고 휴가 핵심 흐름은 계속한다.
  • 기존 Approval private outbox trigger/consumer와 consumption 원장은 제거했다. Approval 장애가 HR 상태를 롤백하는 경로가 없다.
  • workforce_leave_request_overviewsecurity_invoker=true이고 time_availability, nullable conflict count, integration_status를 제공한다.
  • private.workforce_local_decision_commands와 employee advisory lock이 요청키 재시도·동시 중복 신청/승인을 막는다.

SQL rollback smoke supabase/tests/workforce_local_decision_ledgers_smoke.sql은 Approval entitlement 없이 계약·휴가 review/approve, Time unavailable no-op, 잔여 차감, overdraft rollback, append-only/direct-write 거부를 검증한다.

휴가 발생·잔여 원장 계약

마이그레이션 20260717104943_workforce_leave_balance_ledger.sql은 법정 발생 정책을 추정하지 않고 Company·직원·귀속연도의 append-only 연차 원장을 추가한다.

  • workforce_leave_balance_entries: grant | consume | adjustment의 signed delta_days 원장이다. 연차와 반차는 leave_bank='annual'을 공유한다.
  • 잔액은 Σ delta_days, Revision은 동일 Company·직원·연도·bank의 단조 증가 entry revision이다. 과거 update/delete는 trigger가 거부한다.
  • grant_workforce_leave_balance / adjust_workforce_leave_balance: HR·Office 관리권한, 현재 assignment, 대상 연도와 겹치는 승인 근로계약, expected revision, 0.5일 단위, 멱등 요청키를 검증한다. 조정 후 음수 잔액은 거부한다.
  • 휴가 local decision RPC는 기존 employee lock 안에서 annual/half-day 잔액을 다시 읽고 consume entry를 추가한다. 부족하면 HR terminal event·휴가 상태·잔액 소진이 함께 rollback되고 요청은 검토 대기로 남는다.
  • 연도 경계 annual/half-day 요청은 귀속 원장이 모호해지지 않도록 분할 신청을 요구한다. rejected와 비잔액 휴가 종류는 소비하지 않는다.
  • workforce_leave_balance_overview WITH (security_invoker=true)는 현재 Office assignment별 Company 직원 합계만 제공한다. self, Company HR, 현재 배치 Office 관리자만 읽고 authenticated direct write는 없다.

공통 감사 키는 workforce.leave_balance_granted|adjusted|consumed이다. 신규 local decision smoke와 기존 잔액 원장 테스트가 부여 멱등, half → half_day 호환, 승인 소진, 잔액 부족 HR decision rollback, 조정, projection, 직접 write 차단과 감사를 검증한다.

자동 법정 발생, 입사일/회계연도 정책, carry-over/expiry는 effective-dated 정책과 휴일·근무일 계약이 필요하므로 후속이다. 기본 15일을 서버에 하드코딩하지 않는다.

현재 미구현 범위는 법정 자동 휴가 발생·이월·소멸, 확정 근무 수정/취소, 반차 시간대별 충돌, 위치·기기 증명, 수정요청 승인, 예외 자동 시정과 근무표 기반 급여 시간 계산이다. 외부 API나 타사 서비스 계약은 추가하지 않았다.