시트 UP 목업 구현 — UP-006 · UP-002 · UP-007 (2026-08-10 수령)
원본: UP-006-mockup.html(함수 확충) · UP-002-mockup.html(수식 작성 UX) · UP-007-mockup.html(조건부 서식). 적용 순서: UP-006(엔진) → UP-002(에디터 UX가 함수 목록·오류 모델에 의존) → UP-007(조건부 서식).
UP-006 — 함수 확충: IFERROR · SUMIF/COUNTIF ✅
엔진: packages/api/src/routers/sheet-rows/resolver.ts (evaluateFormula).
- IFERROR(값, 대체값) — 문자열 레벨 lazy 프리패스. 1번째 인자는 strict 모드로 평가 (참조 미해결 = 오류, 0 나눗셈 = 오류 — 숫자 컬럼의 "미해결→0" 관대 모드도 무시)라 목업의 "시스템의 조용한 0"과 "담당자가 정한 0"이 구분된다. 대체값도 수식 가능, 중첩·함수 합성(ROUND(IFERROR(…))) 지원. IFERROR 없는 기존 수식은 현행 동작 그대로 (0 나눗셈의 조용한 0 포함 — 하위 호환).
- SUMIF(조건컬럼, 조건, 합계컬럼) / COUNTIF(조건컬럼, 조건) — 컬럼 기반 세로(전 행) 집계. 조건:
"문자열"완전 일치 ·">=100000"비교식(따옴표 유무 모두) · 스칼라 수식(다른 셀 참조). 크로스시트 컬럼 참조는 기존crossSheetAllRows프리로드 재사용, 현재 시트 세로 집계는 신설 R5 포스트패스(모든 행 리졸브 후 재평가)로 동작. 합계컬럼은 조건컬럼과 같은 시트여야 함. - 클라이언트:
formulaFunctionCatalog에 3종 등록(자동완성·fx 인수 가이드·함수 그리드 일괄 반영), 「IFERROR로 감싸기」 버튼 — 헤더 수식 편집기(formula-editor.html) + 수식바(초안이 수식일 때, 이미 감싸져 있으면 비활성).wrapExprInIferror헬퍼(formula-refs.ts). - 테스트:
formula.test.ts+22 (95/95) ·formula-refs.test.ts+4 (63/63). 라이브 검증:IFERROR([없는열]*2, 123)→123 ·IFERROR(10/0, -7)→-7 ·COUNTIF→2 ·SUMIF→400 (API+UI). - 미지원(명시): SUMIFS/COUNTIFS·문자열 함수(목업 명시 2차) · SUMIF-of-SUMIF 중첩 · @-행고정 참조와 SUMIF 조합.
UP-002 — 수식 작성 UX ◐
- S1 참조 픽커: 기존 기능으로 커버 — fx 팝오버의 「다른 시트 참조」 빌더(시트→컬럼 선택, LOOKUP/단순/인원분배) + 열 헤더 클릭 참조 삽입 + 참조 확인 패널. 목업의 2단 팝오버·칩 UI 로의 리스킨은 후속(기능 동등, 모양 차이).
- S2 수식 템플릿: ✅ 기본 세트 5종(
formulaTemplateCatalog— 연장 1.5배·야간 0.5배·휴일 1.5배· 통상시급·검증 대사) — 헤더 수식 편집기 더보기 영역 카드. 표시형(라벨 참조)이라 같은 라벨 열이 있으면 즉시 연결, 없으면 참조 확인 ⚠ → 재매핑(= 목업의 "미연결 슬롯"을 기존 흐름으로). 후속: 「+지금 수식을 템플릿으로 저장」(병원 공통 커스텀 — 서버 저장소 필요). - S3 자동완성·시그니처: 기존 fx 팝오버가 담당(카탈로그 확장으로 신규 함수 포함). ⚠ 오류 배지(참조 미해결로 0 대체된 셀 구분 + 원인·다음 행동 hover + 헤더 ⚠ N)는 후속 — 셀 단위 오류 메타데이터를 와이어에 실어야 함(리졸버가 strict 재평가로 coerced-0 여부를 계산해
cellErrors맵으로 내려주는 설계 제안). IFERROR 도입으로 "통제된 0"은 이미 구분 가능.
UP-007 — 조건부 서식 ✅ (1차 범위)
- 저장: 컬럼 단위 규칙
columnLayout[].conditionalRules(jsonb — 마이그레이션 불필요,sheetColumnLayoutEntrySchema확장, 컬럼당 최대 8개, 배열 순서 = 우선순위·먼저 맞은 규칙 승). - 조건: 큼/작음/같음/같지 않음/비어 있음 + 비교값(고정값 또는
[colKey]다른 컬럼 참조 — 검증 컬럼 문화는 "수식 템플릿 검증 대사(1/0) + 같음 1 → 빨강" 조합으로 커버). "수식이 참일 때" 조건은 후속(클라이언트 평가기 필요). - UI: 열 헤더 우클릭 → 「🎨 조건부 서식」 모달(
modals/conditional-format.html, DS 일반 모달) — 조건 + 4색 프리셋 + 굵게 + 저장 전 해당 셀 수 미리보기. 그리드 상단 규칙 칩(클릭=개인 켜기/끄기 localStorage — 색만 사라지고 규칙 보존, ✕=삭제), 규칙 있는 컬럼 헤더 🎨. - 렌더:
_applyConditionalFormats데코레이션 패스 —_applyCellStyles뒤에 실행되어 수동 셀 색과 충돌 시 조건부 서식이 이김(매칭 셀만 인라인 색 제거 —!important불사용), 해제 시 다음 렌더에서 수동 색 복원. 가상화 스크롤 재렌더에도 동일 수명. 색은 DS 토큰(.cond-fmt-*). - 슬라이스:
workbook/conditional-format.ts(+condRuleMatches/condCompareValue순수 함수, 테스트 9종). 라이브 검증: 규칙 생성(미리보기 2건) → 저장 → 칩 「A > 150 → 빨강」 + 200·300 셀 페인팅 → 토글 off(0)/on(2) → 삭제 원복. - 후속: 규칙 편집(현재 삭제 후 재생성) · 규칙 순서 변경 UI · 대상 컬럼 삭제 시 규칙 자동 정리 안내 · XLSX 내보내기에 서식 포함 · UP-003 마감 3규칙 프리셋 통합.
게이트
pnpm build 22/22 green · 변경 파일 biome 클린 · vitest: formula 95 / formula-refs 63 / conditional-format 9. (레포 전체 lint:check 실패는 클린 develop에서도 동일한 기존 부채 — scripts/phase5-plan.mjs noConsole 등. admin/app.ts 무효 suppression 1건은 이번에 수정.)
후속작업 1차 반영 (2026-08-10)
리스트업했던 미구현 잔여 중 4건 구현 완료:
- SUMIFS / COUNTIFS (UP-006 2차) — 다중 조건 세로 집계, 엑셀 인자 순서 (
SUMIFS(합계컬럼, 조건컬럼1, 조건1, …)). 한 호출의 모든 컬럼은 같은 시트 강제. 카탈로그 등록(자동완성·인수 가이드), 테스트 8종 (formula.test.ts103/103). - 「수식이 참일 때」 조건 (UP-007) —
conditionalRules스키마에op: "formula"+formula필드. 클라이언트 미니 평가기evalCondFormula(숫자·[colKey]·문자열·사칙·비교· 괄호, 실패는 조용히 거짓 — 서식 규칙이 시트를 못 깨뜨리게 fail-closed). 엑셀 검증 컬럼(L4=(K4=I4))을 1:1 이관할 수 있다. 모달에 조건식 입력 + 미리보기 연동. 테스트 8종 (conditional-format.test.ts17/17). - ⚠ 오류 배지 (UP-002-S3) — 리졸버가 수식 컬럼의 참조를 구조 분석(수식별 1회 캐시 + 행별 직원-행 존재 검사)해 구조적 실패가 조용한 0으로 대체된 셀에만
cellErrors: {colKey: "원인\n→ 다음 행동"}을 와이어로 내려준다. 원인 3종: 시트 없음 · 컬럼 없음(크로스시트/로컬) · 이 직원의 행 없음. 같은 시트 빈 셀의 0 처리는 정상 동작이라 제외(노이즈 방지). 클라이언트는 데코레이션 패스(_applyFormulaErrorBadges)로 우상단 빨간 모서리 + title 멀티라인 툴팁, 헤더에⚠N카운트. 값 표시는 0 유지(합계 불변). API 검증: 로컬 미해결 → 「참조한 컬럼 …이 이 시트에 없어요」, 시트 미해결 → 「참조한 시트를 찾을 수 없어요」 정확 문구 확인. 잔여: 헤더 ⚠N 클릭 시 경고 행 필터 · 배지 클릭 시 원천 시트 이동 · 셀 수식(R3)/R5 재평가분 배지. - 커스텀 수식 템플릿 저장 (UP-002-S2-b) —
sheet_formula_templates테이블(마이그레이션 1877, org·이름 유니크) +sheetTemplate.listFormulaTemplates/createFormulaTemplate/ deleteFormulaTemplate(생성·삭제는 PAYROLL MANAGE) + SDK + 수식 편집기 「+지금 수식을 템플릿으로 저장」(이름 입력 프롬프트) · 「우리 병원」 그룹 카드 · 삭제(컨펌). 표시형 저장이라 적용 시 라벨 매칭 연결. API 검증: 생성 → 중복 이름 한글 CONFLICT → 목록 → 삭제.
여전히 남은 후속: 참조 픽커 리스킨(기능 동등) · 미리보기 대입값 전개 · 문자열 함수(LEFT/ CONCAT) · 조건부 서식 규칙 편집/순서 변경 UI · 컬럼 삭제 시 규칙 정리 · XLSX 서식 내보내기 · UP-003 마감 3규칙 프리셋 · ⚠ 배지 잔여(위 3번).
후속작업 2차 반영 (2026-08-10)
- ⚠ 배지 분석기 보정 — SUMIF(S)/COUNTIF(S) 인자의 컬럼 참조는 세로 집계라 "같은 직원 행 없음" 검사에서 제외(구조 검사만) — 크로스시트 집계 수식의 오탐 제거. 라이브 확인: 같은 크로스시트를 SUMIF 로 집계한 컬럼은 배지 없음, 같은 시트를 행 참조한 컬럼만 「행이 없어 0으로 계산」 배지.
- ⚠ 배지 확장 — 셀 수식(R3)·R5 재평가분에도 동일 판정 부착.
cellErrorTargets(colKey → 원천 시트 id) 추가: 배지 모서리(12×12 히트영역) 클릭 시 원천 시트로 이동. - 헤더 ⚠N 클릭 필터 — 클릭 토글로 그 컬럼 경고 행만 표시(adapter
errorFilterColKey훅 +bumpExtraFilter), 활성 시 배지가 빨강 반전. 카운트는 전체 행 기준(필터 중에도 불변). 시트 전환 시 자동 해제. - 조건부 서식 규칙 편집·순서 — 모달 목록에 「수정」(폼 로드 → 같은 위치 교체 저장, 우선순위 유지) · ↑/↓(순서=우선순위 이동 즉시 저장) 추가.
- 컬럼 삭제 안내 — 규칙 있는 컬럼 삭제 확인문에 「조건부 서식 규칙 N개도 함께 삭제됩니다」 1회 안내(규칙은 컬럼 레이아웃에 살아 자동 소멸).
- XLSX 조건부 서식 포함 — 내보내기에서 값 기준 규칙을 서버 매처로 평가해 프리셋 색(fill+font, DS 토큰 hex)과 굵게를 셀에 입힌다. 수동 셀 스타일 다음에 적용(화면과 같은 우선순위). 검증: 내보낸 xlsx 의 styles.xml 에 red-50/600 반영 확인. 한계(문서화):
수식이 참일 때규칙은 클라이언트 평가기 전용이라 내보내기에선 건너뜀 · 규칙 켜기/끄기는 개인 설정이라 내보내기엔 시트 소유 규칙 전체 적용.
남은 후속(변동 없음): 참조 픽커 리스킨 · 미리보기 대입값 전개 · 문자열 함수 · UP-003 마감 3규칙 프리셋(전월 대비는 서버 지원 필요).
UP-009 — 시트 특징 기반 생성 ✅ (2026-08-11)
목업 UP-009-mockup (3).html 구현. scratch(새 시트 만들기) 경로 확장 — 시트 이름 뒤 「이 시트의 특징」 4종 카드(🕐 근무기록형 · 📊 실적형 · 📋 명부형 · 📐 기준형)를 고르면 컬럼 골격 + 디폴트 수식이 제안되고, 체크·참조 연결 후 생성하면 UP-008 「설정 중」으로 열린다. 특징 없이 「빈 시트로 만들기」 = 현행 동작 그대로.
- 서버:
SheetCategory코어 enum +sheet_views.category(varchar 32, 마이그레이션 1882) +createSheetViewSchema.category. 표시용 저장만 — 생성 후 변경 불가(update 스키마에 없음). 골격·수식은 기존 columnLayout 경로 그대로(서버 신규 로직 0). - 카탈로그 (
sheet-category-catalog.ts): 특징 4종 × {골격, 수식, 슬롯}. 목업 수식을 엔진 실측으로 교정 — time 셀은 이미 시(hours) 평가라 ×24 시리얼 보정 제거 (IF([퇴근]-[출근]<0, 24+…)), QUOTIENT →ROUNDDOWN(a/b,0), 명부형 순번은 # 열 활용(ROW() 미도입 — 2026-08-10 결정). 슬롯 3종: column(이름 자동 매칭, 미연결 ⚠) · formula(칩 결과 인라인 치환 🔒 — ① 근무시간이 ② 인정시간에 괄호로 들어감) · system(통상시급=identityhourlyRate컬럼 자동 추가 · 최저시급=[$급여.최저시급]상수 토큰). 야간 가산 [야간시간]·기준형 [월 근로시간]은 골격에 없어 기본 ⚠ — 의도된 시연(미연결=제외 규칙·직접 연결·+새 컬럼 경로 데모, 2026-08-10 결정). 빌더 단위 테스트 8건(자동 매칭·인라인 치환·부활·제외·상수 치환·카탈로그 정합). - 모달 2단계 (new-sheet-modal.ts + modals/new-sheet.html): S1 특징 카드(단일 선택, 미선택 시 「특징 없이 빈 시트로 만들기」만 유효) → S2 골격 미리보기(더블클릭 rename — 슬롯은 key 기준이라 자동 추종) + 수식 칩(기본 전부 체크, ⚠ 연결 필요 뱃지, 「참조 연결」 펼침 — 슬롯 드롭다운 + +새 컬럼 만들기). 미연결 수식은 [만들기] 차단 없이 제외(토스트 안내).
- 생성 경로 (sheet-crud.ts):
createSheetFromForm(form, categorized?)— 골격 columnLayout + category 로 생성, 제외/생성 토스트 2단. UP-008 설정 중 띠는 특징 시트에서 "특징 「○○형」으로 만들어졌어요…" 문구로 대체(sheetCategoryName게터). - 라이브 검증: worklog 기본 빌드로 생성 → category=worklog · setupCompletedAt=null 확인 → 22:00→02:00·휴게 1 입력 → 인정시간 3(자정 넘김 인라인 수식 정상), 수당액은 시급 미연결 행에서 0(fail-safe). 검증 시트 삭제 완료.
남은 후속: E2E 스펙(신규 testid sheet-category-* 등록됨, spec 13 확장 후보) · 기준형 상수 키($급여.최저시급) 시드 여부는 조직 온보딩 정책과 함께 결정.