Skip to content

시트 아키텍처

시트(워크북) 시스템의 구현 전모 — hr-system의 Alpine.js 그리드부터 최종 급여 레코드까지. 시트는 데이터 입력과 급여 조립의 중심 화면입니다: 급여대장에 도달하는 모든 수당/공제는 시트 커밋(✓ 커밋)에서 태어납니다.

소스 코드 위치는 파일 맵 참고. 급여 레코드 모델 자체는 Payroll Schema Refactor 문서와 CLAUDE.md의 Payroll 규칙 참고. (영문판: Sheet Architecture)

1. 레이어 개요

핵심 아이디어: 시트는 프로젝션(projection) 레이어입니다. 소스 테이블은 관측값(시간, 세션, 상태)만 보관하고, sheet_cells에는 사용자 수정값과 계산 캐시만 저장되며, 금액 결과는 오직 allowance_records / deduction_records에만 존재합니다 — 이 레코드들은 시트 커밋(✓ 커밋)으로만 생성되고 항상 시트 계보(lineage)를 가집니다.

2. 데이터 모델

컬럼 종류 (columnLayout[].kind)

kind의미값 저장 위치편집
identityrow.employeeId 조인에서 오는 사번/이름/팀— (파생)불가
literal사용자가 직접 입력한 값SheetCell.literalValue가능
ref소스 레코드 필드 포인터SheetCell.ref*RecordId → 레코드제한적
formula=SUM(...) 형식 수식formulaExpression + computedValue 캐시수식만
computed코드 모듈 출력 (예: 보험료 계산)computedValue + inputSnapshot불가 (수동 오버라이드는 스킵)
aggregate커밋된 레코드의 SUM — 급여대장 컬럼저장 안 함, 조회 시 파생불가
multiselect / employeeselect / treatmentcount특수 에디터literalValue가능

행 값 결정 순서 (resolver.ts)

행 × 컬럼마다 resolver가 아래 우선순위로 첫 번째 값을 선택합니다:

즉 수동 수정은 소스 값을 변형하지 않고 *가리기(shadow)*만 하며, 셀을 지우면 소스 값 폴스루가 복원됩니다.

3. 한 달 동안의 시트 라이프사이클

4. 주요 흐름 (시퀀스)

4.1 시트 열기 — materialize → resolve

4.2 셀 편집

4.3 ✓ 커밋 → 레코드 → 급여대장

계보 3요소 덕분에 급여대장의 모든 금액은 그 값을 만든 정확한 시트 셀까지 역추적할 수 있고, 커밋은 멱등입니다(재커밋은 갱신만 하고 중복 생성하지 않음).

5. 프론트엔드 구성

워크북은 2026-06 리팩터로 18,000줄 단일 파일에서 30개의 관심사 슬라이스로 분리되었고, compose()(getter 를 보존하는 descriptor 병합)로 합쳐집니다:

  • 엔진 vs 워크북: 엔진(src/sheet/)은 프레임워크 무관 그리드 상태 머신(렌더, 가상화, 선택, 히스토리)이고, 워크북 슬라이스는 이를 SDK·도메인 로직과 잇는 Alpine 글루입니다.
  • 어댑터SourceTable을 기본 컬럼 + 컬럼별 렌더러에 매핑합니다. 그래서 자동 채움된 연장근무 시트가 시트별 설정 없이도 연장/야간연장 라벨을 알 수 있습니다.

6. 정체성 · 재사용 규칙

  • slug — 시트의 기간 간 안정 정체성(payroll_ledger, overtime_all, attendance_summary_all 등). live 행 기준 (org, year, month) 당 유일(부분 인덱스 UQ_sheet_views_org_period_slug). aggregate 와 시트 간 참조는 UUID 가 아니라 slug 에 바인딩되므로 월이 바뀌어도 유지됩니다.
  • 템플릿: 시스템 템플릿(isSystem)은 SHARED 로, 사용자 템플릿은 PRIVATE 으로 적용됩니다. 템플릿 세트(setSlug + isSetUmbrella)는 여러 시트를 픽커의 항목 하나로 함께 적용합니다(예: 퇴직 정산 3종).
  • 이전월에서 가져오기설정(layout, slug, autoPopulate, computeConfig)만 복제하고 데이터는 복제하지 않습니다 — 행은 기간 스코프 소스 테이블에서 다시 materialize 되고, 레코드는 (year, month) 별입니다.
  • 휴지통: deletedBy 소유권이 있는 soft delete. 복원은 삭제자 본인만 가능하며, live 시트가 slug 슬롯을 재점유했으면 slug 를 비워 복원합니다(데이터 복구는 절대 하드 실패하지 않음).

파일 맵

레이어경로내용
엔티티packages/database/src/entities/sheet-view.entity.tsSheetView (기간, slug, layout, autoPopulate, computeConfig, 커밋 스탬프)
엔티티packages/database/src/entities/sheet-row.entity.tsSheetRow (manual/auto, 소스 판별자, 행 보호)
엔티티packages/database/src/entities/sheet-cell.entity.tsSheetCell (kind 판별자, UQ(rowId, columnKey), style)
엔티티packages/database/src/entities/sheet-template.entity.tsSheetTemplate (system/user, defaultSlug, 세트)
계보packages/database/src/entities/allowance-record.entity.tssourceSheet* NOT NULL 계보 + target* aggregate 키
라우터packages/api/src/routers/sheet-view.router.tsCRUD, queryRows, 커밋, 이전월 가져오기, 휴지통, import/export
라우터packages/api/src/routers/sheet-row.router.ts / sheet-cell.router.ts수동 행 / 셀 upsert + 가드
서비스packages/api/src/routers/sheet-rows/auto-populate.ts, materializer.ts소스 → auto 행 동기화
서비스packages/api/src/routers/sheet-rows/resolver.tsResolvedRow 프로젝션 + aggregate 사전 집계
서비스packages/api/src/routers/sheet-rows/compute.tsrunCompute (코드 모듈) / commitSheet (레코드 + 계보)
시드packages/database/src/seeds/seed-sheet-templates.tsSYSTEM_TEMPLATES (이름 기준 멱등)
SDKpackages/sdk/src/services/SheetViewService.ts (+ Row/Cell/Template)타입 안전 클라이언트 표면
프론트엔드apps/hr-system/src/sheets/workbook/슬라이스 30개 + compose.ts + types.ts
프론트엔드apps/hr-system/src/sheet/그리드 엔진 (render, virtualizer, selection, history, escape-html)
프론트엔드apps/hr-system/src/views/tabs/sheets/grid.html, 사이드바, 모달, 컨텍스트 메뉴