Skip to content

시트 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건 구현 완료:

  1. SUMIFS / COUNTIFS (UP-006 2차) — 다중 조건 세로 집계, 엑셀 인자 순서 (SUMIFS(합계컬럼, 조건컬럼1, 조건1, …)). 한 호출의 모든 컬럼은 같은 시트 강제. 카탈로그 등록(자동완성·인수 가이드), 테스트 8종 (formula.test.ts 103/103).
  2. 「수식이 참일 때」 조건 (UP-007) — conditionalRules 스키마에 op: "formula" + formula 필드. 클라이언트 미니 평가기 evalCondFormula(숫자·[colKey]·문자열·사칙·비교· 괄호, 실패는 조용히 거짓 — 서식 규칙이 시트를 못 깨뜨리게 fail-closed). 엑셀 검증 컬럼(L4=(K4=I4))을 1:1 이관할 수 있다. 모달에 조건식 입력 + 미리보기 연동. 테스트 8종 (conditional-format.test.ts 17/17).
  3. ⚠ 오류 배지 (UP-002-S3) — 리졸버가 수식 컬럼의 참조를 구조 분석(수식별 1회 캐시 + 행별 직원-행 존재 검사)해 구조적 실패가 조용한 0으로 대체된 셀에만 cellErrors: {colKey: "원인\n→ 다음 행동"} 을 와이어로 내려준다. 원인 3종: 시트 없음 · 컬럼 없음(크로스시트/로컬) · 이 직원의 행 없음. 같은 시트 빈 셀의 0 처리는 정상 동작이라 제외(노이즈 방지). 클라이언트는 데코레이션 패스(_applyFormulaErrorBadges)로 우상단 빨간 모서리 + title 멀티라인 툴팁, 헤더에 ⚠N 카운트. 값 표시는 0 유지(합계 불변). API 검증: 로컬 미해결 → 「참조한 컬럼 …이 이 시트에 없어요」, 시트 미해결 → 「참조한 시트를 찾을 수 없어요」 정확 문구 확인. 잔여: 헤더 ⚠N 클릭 시 경고 행 필터 · 배지 클릭 시 원천 시트 이동 · 셀 수식(R3)/R5 재평가분 배지.
  4. 커스텀 수식 템플릿 저장 (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) ​

  1. ⚠ 배지 분석기 보정 — SUMIF(S)/COUNTIF(S) 인자의 컬럼 참조는 세로 집계라 "같은 직원 행 없음" 검사에서 제외(구조 검사만) — 크로스시트 집계 수식의 오탐 제거. 라이브 확인: 같은 크로스시트를 SUMIF 로 집계한 컬럼은 배지 없음, 같은 시트를 행 참조한 컬럼만 「행이 없어 0으로 계산」 배지.
  2. ⚠ 배지 확장 — 셀 수식(R3)·R5 재평가분에도 동일 판정 부착. cellErrorTargets (colKey → 원천 시트 id) 추가: 배지 모서리(12×12 히트영역) 클릭 시 원천 시트로 이동.
  3. 헤더 ⚠N 클릭 필터 — 클릭 토글로 그 컬럼 경고 행만 표시(adapter errorFilterColKey 훅 + bumpExtraFilter), 활성 시 배지가 빨강 반전. 카운트는 전체 행 기준(필터 중에도 불변). 시트 전환 시 자동 해제.
  4. 조건부 서식 규칙 편집·순서 — 모달 목록에 「수정」(폼 로드 → 같은 위치 교체 저장, 우선순위 유지) · ↑/↓(순서=우선순위 이동 즉시 저장) 추가.
  5. 컬럼 삭제 안내 — 규칙 있는 컬럼 삭제 확인문에 「조건부 서식 규칙 N개도 함께 삭제됩니다」 1회 안내(규칙은 컬럼 레이아웃에 살아 자동 소멸).
  6. 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 「설정 중」으로 열린다. 특징 없이 「빈 시트로 만들기」 = 현행 동작 그대로.

  1. 서버: SheetCategory 코어 enum + sheet_views.category(varchar 32, 마이그레이션 1882) + createSheetViewSchema.category. 표시용 저장만 — 생성 후 변경 불가(update 스키마에 없음). 골격·수식은 기존 columnLayout 경로 그대로(서버 신규 로직 0).
  2. 카탈로그 (sheet-category-catalog.ts): 특징 4종 × {골격, 수식, 슬롯}. 목업 수식을 엔진 실측으로 교정 — time 셀은 이미 시(hours) 평가라 ×24 시리얼 보정 제거 (IF([퇴근]-[출근]<0, 24+…)), QUOTIENT → ROUNDDOWN(a/b,0), 명부형 순번은 # 열 활용(ROW() 미도입 — 2026-08-10 결정). 슬롯 3종: column(이름 자동 매칭, 미연결 ⚠) · formula(칩 결과 인라인 치환 🔒 — ① 근무시간이 ② 인정시간에 괄호로 들어감) · system(통상시급=identity hourlyRate 컬럼 자동 추가 · 최저시급=[$급여.최저시급] 상수 토큰). 야간 가산 [야간시간]·기준형 [월 근로시간]은 골격에 없어 기본 ⚠ — 의도된 시연(미연결=제외 규칙·직접 연결·+새 컬럼 경로 데모, 2026-08-10 결정). 빌더 단위 테스트 8건(자동 매칭·인라인 치환·부활·제외·상수 치환·카탈로그 정합).
  3. 모달 2단계 (new-sheet-modal.ts + modals/new-sheet.html): S1 특징 카드(단일 선택, 미선택 시 「특징 없이 빈 시트로 만들기」만 유효) → S2 골격 미리보기(더블클릭 rename — 슬롯은 key 기준이라 자동 추종) + 수식 칩(기본 전부 체크, ⚠ 연결 필요 뱃지, 「참조 연결」 펼침 — 슬롯 드롭다운 + +새 컬럼 만들기). 미연결 수식은 [만들기] 차단 없이 제외(토스트 안내).
  4. 생성 경로 (sheet-crud.ts): createSheetFromForm(form, categorized?) — 골격 columnLayout + category 로 생성, 제외/생성 토스트 2단. UP-008 설정 중 띠는 특징 시트에서 "특징 「○○형」으로 만들어졌어요…" 문구로 대체(sheetCategoryName 게터).
  5. 라이브 검증: worklog 기본 빌드로 생성 → category=worklog · setupCompletedAt=null 확인 → 22:00→02:00·휴게 1 입력 → 인정시간 3(자정 넘김 인라인 수식 정상), 수당액은 시급 미연결 행에서 0(fail-safe). 검증 시트 삭제 완료.

남은 후속: E2E 스펙(신규 testid sheet-category-* 등록됨, spec 13 확장 후보) · 기준형 상수 키($급여.최저시급) 시드 여부는 조직 온보딩 정책과 함께 결정.