인센티브 특화 요구사항 — 정책 대비 구현 분석 및 개발 요구사항 (2026-07)
정책 출처: 라온 미팅록 2026-03-26 (이용구 원장 피드백 — 인센티브 등록 프로세스 핵심 변경). 분석 관점: 시트 중심 정산 — 사용자는 인센티브·급여정산을 전부 시트에서 처리한다. 이 문서는 정책 5원칙이 시트 중심 흐름에 실제로 반영됐는지, 그리고 개념을 아는 사용자에게 직관적으로 동작하는지를 축으로 현 소스코드를 분석해 개발 요구사항으로 정리한 것이다.
1. 정책 원칙 (미팅록 → 5원칙 요약)
| # | 원칙 | 원문 근거 |
|---|---|---|
| P1 | 개인 직접 등록 + 당일 마감: 본인이 당일 인센티브를 직접 입력, 당일 24시 마감(늦은 입력 불가). 정정은 관리자가 월말에만 | "본인한테 맡겨놓는 게 제일 정확하다" / "당일 24시까지 입력" |
| P2 | 직종별 화면 분리: 간호사↔치료사 로그인 시 각자 직종의 코드화된 항목만 표시 | "간호사 로그인 → 간호 관련 수당만" |
| P3 | 관리자는 합산만 확인: 팀장/수간호사는 월말에 항목별 합산 개수만 검증 ("이브닝 70개") — 개별 건 검토 없음 | "중간 단계에 손이 안 들어가게" |
| P4 | 실시간 집계: 입력 즉시 본인 누적 카운트, 원장은 당일/익일 발생 현황 | "당일 마감에 얼마 벌었고 얼마 나갈지" |
| P5 | 치료실 인센티브(베스트 고유): 주말 당직 전용 입력 → 항목별 건수 × 단가 = 청구금액 → 병원 50%/치료사 50% → 당직 인원수로 분할(2명=0.25). 치료 인센에만 0.5 단위 | §4 전체 |
2. 현 시스템의 시트 중심 인센티브 흐름 (구현 현황)
[직원앱] 프리셋 선택·건수 입력 ──create──▶ fixed_incentive (append-only, chainId+version)
[시트] 치료 세션 모달 ──upsertTreatmentSession──▶ fixed_incentive (treatment_incentive)
│ auto-populate (source=fixed_incentive)
▼
[인센티브 시트] ref 열(건수·단가·총액·분배율·포지션) + therapy_daily 세션 행
│ 검토·정정(시트 편집) · 승인/지급 행 워크플로우
│ 인원 분배 수식 [anchor:amount:member] · 커밋 열
▼
✓ 커밋 ──▶ AllowanceRecord (시트 lineage) ──▶ 급여대장구현돼 있는 것 (근거 코드):
- 자기 등록:
apps/employee-app/src/components/incentive.ts— 프리셋 목록, 등록 모달, 삭제, 월 누적monthlyTotal/Count표시 - 치료 세션 자동 분배:
fixed-incentive.router.tsupsertTreatmentSession—hospitalShare = totalBilling × hospitalRatio,splitRatio = 1/치료사수(2명=0.5, 4명=0.25 자동, 2자리 반올림). 치료 카테고리별 병원/치료사 비율은TreatmentCategory(hospitalRatio/therapistRatio)마스터 - 시트 통합:
fixed_incentivesourceTable ref 열(unitCount·unitPrice·totalAmount·splitRatio·position), therapy_daily 세션 행 + 세션 편집 모달(workbook/treatment.ts), 인원 분배 수식[시트:금액열:인원열], 커밋 파이프라인, 승인 행 일괄 지급 처리 - 팀매니저: 치료 인센 월 피벗·목록(
listIncentives+ treatment_incentive 필터), 승인/반려 - 프리셋 마스터: 인센티브 프리셋 시트(마스터에 적용) +
IncentivePresetCategory
3. 정책 대비 갭 분석
| 원칙 | 상태 | 분석 (근거) |
|---|---|---|
| P1 직접 등록 | ◐ 부분 | 등록 자체는 구현. 당일 24시 마감이 어디에도 없음 — fixedIncentive.create가 workDate를 검증하지 않아 아무 날짜나 소급 등록 가능 (buildIncentiveCreatePayload는 today를 넣지만 서버 강제 없음). "마감 후 관리자 정정만" 권한 구분도 없음 |
| P2 직종 분리 | ✗ 미구현 | listPresets가 전 직원에게 전체 프리셋 반환. IncentivePreset/IncentivePresetCategory에 직종·팀 매핑 필드 자체가 없음 — 간호사에게 치료 항목이 그대로 보임 |
| P3 합산 확인 | ✗ 방향 불일치 | 현 구현은 개별 건 승인/반려 워크플로우(fixed-incentive approve/reject + 시트 행 단위 승인). 정책은 반대: "팀×항목 월 합산 개수 확인 → 일괄 승인". 합산 피벗은 치료 인센(팀매니저)에만 있고 일반 인센·시트에는 없음 |
| P4 실시간 집계 | ◐ 부분 | 본인 누적(월 합계·건수)은 직원앱에 있음. 원장용 당일/익일 발생 현황(오늘 조직 전체 인센 발생액)은 없음 — management-dashboard에도 인센 당일 뷰 부재 |
| P5 치료 인센 | ◐ 부분 | 분배 엔진(비율·인원 분할)은 서버에 정확히 구현. 그러나 입력 진입점이 시트/팀매니저에만 있고 정책이 요구한 "주말 당직자 본인이 입력하는 전용 화면"이 직원앱에 없음 — 당직 치료사가 자기 건수(게이트 12건, FeS 2건)를 직접 못 넣고 관리자 대행 입력 중. unitCount는 z.int()로 0.5 불가하나, 분할은 splitRatio가 흡수하므로 정책 충족으로 해석 (단, 명시 확인 필요) |
4. 직관성 분석 — 시트 중심 사용자 관점
개념(프리셋·분배율·커밋)을 아는 사용자가 시트에서 막히는 지점:
| # | 문제 | 근거 |
|---|---|---|
| U1 | 인센 행의 편집 가능 셀이 안 보임: ref 열 일부만 편집 허용(edit/incentive.ts — 그 외는 "Cell X is not editable on incentive rows" throw). 어떤 셀이 되고 안 되는지 시각 구분 없음 → 에러로 학습하게 됨 | 호봉 시트와 같은 계열의 문제 (2026-07-22 QA에서 이미 한 번 터진 패턴) |
| U2 | 분배 산식의 불투명성: splitRatio·position·totalAmount가 개별 열로 나열될 뿐, "청구액 → 병원 50% → 인원 분할"의 계산 사슬이 시트에서 안 보임. 세션 모달을 열어야 맥락 파악 가능 | 셀 상세 패널의 수식 치환 표시(formulaSubstituted)가 인센 분배에는 미적용 |
| U3 | 프리셋 마스터 ↔ 직원앱의 연결 비가시: 시트에서 프리셋 단가를 바꿔 "마스터에 적용"해도, 그것이 직원앱 등록 화면에 반영된다는 사실이 UI 어디에도 없음 | 프리셋 시트·직원앱이 같은 incentive_presets를 읽지만 상호 참조 표시 없음 |
| U4 | 승인 상태의 이중 표현: fixed_incentive.status(개별)와 시트 행 워크플로우(승인 행 지급 처리)가 별개 축 — 무엇이 급여 반영의 진실인지 혼동 여지 | grid.html 승인/지급 CTA + fixed-incentive approve 공존 |
5. 개발 요구사항
우선순위: A(정책 위반 해소) > B(정책 완성) > C(직관성). 난이도: 하/중/상.
IR-1 [A·중] 당일 마감 정책 — 서버 강제 (확정 2026-07-29) ✅ 구현 (2026-08-10)
- 확정 모델: 일단위 마감 + 유예(grace) 설정. 기본은 당일 24:00 마감. 단, 3교대 근무는 전일 인센티브가 익일에 마감되므로(나이트 근무자가 퇴근 후 입력) 마감 유예를 설정할 수 있어야 한다.
- 요구:
fixedIncentive.create(자기 등록)에서workDate가 [오늘 − 유예일수, 오늘] 범위만 허용. 유예는 조직 기본값 + 팀 단위 오버라이드(3교대 팀만 D+1) — 상수 스토어organization_parameters재사용:incentive.cutoff_grace_hours(기본 0 = 당일 24시, 3교대 팀 예: 30 = 익일 06시까지 전일 입력 허용). - 정정 경로 보존: 시트/HR·팀매니저의 관리자 경로(권한
incentive:manage)는 마감 무관 — "관리자는 월말 정정" 정책 그대로. - 수용 기준: 일반 팀 직원이 어제 날짜 등록 → 4xx + "당일 인센티브만 등록할 수 있어요. 지난 건은 관리자에게 요청하세요." 3교대 팀 직원은 익일 유예 시각까지 전일 등록 통과 / 유예 후 거부 / 관리자 시트 편집은 항상 통과.
- 터치: fixed-incentive.router(create 분기 + 팀 유예 조회), core schema, 직원앱 에러 문구·유예 안내.
IR-2 [A·중] 직종별 프리셋 노출 ✅ 구현 (2026-08-10)
- 요구:
IncentivePresetCategory에 적용 대상(팀 or 직무 태그, 다중) 필드 추가 →listPresets가 로그인 직원의 소속 기준으로 필터. 미지정 카테고리는 전체 노출(하위호환). - 시트 연동: 프리셋 마스터 시트에 대상 열 추가(마스터에 적용 writable 세트에 포함) — 직종 매핑도 시트에서 관리하게 해 시트 중심 원칙 유지.
- 수용 기준: 간호팀 직원 로그인 → 치료 카테고리 미노출. 프리셋 시트에서 대상 변경 → 마스터 적용 → 직원앱 반영.
- 터치: 엔티티+마이그레이션, listPresets, commitMasterOverrides writable, 프리셋 시트 템플릿.
IR-3 [B·상] 관리자 "합산 확인" 승인 모델 (확정 2026-07-29) ✅ 구현 (2026-08-10)
- 요구: 개별 건 승인을 대체하는 월 합산 검증 뷰 — 팀 × 프리셋 항목 피벗(개수·금액)을 보고 항목 단위로 일괄 승인. "이브닝 70개 → 맞음 → 승인" 한 번.
- 시트 중심 구현 제안: 신규 화면 대신 인센티브 집계 시트 템플릿(행=프리셋 항목, 열=팀원, 셀=개수 — 기존 인원 분배·집계 수식 재사용) + "항목 일괄 승인" 액션(해당 항목의 pending 레코드 전부 승인). 개별 승인 API는 예외 처리용으로 유지.
- 확정 — 개별 건 이탈 처리: 합산 승인 후 특정 건을 빼야 하는 경우는 시스템이 지원하지 않는다. HR 담당자의 수기 업무 — 합산에서 1건 빼고 검증 후 차월에 추가 반영하는 흐름이며, 근거는 시트 메모 기능(스레드·해결)으로 남긴다. 별도 반려-후-이월 자동화를 만들지 않는다.
- 수용 기준: 팀장이 시트에서 항목 행 선택 → 일괄 승인 → 해당 월 그 항목 pending 전건 approved. 개별 건을 열지 않고 월 마감 가능.
- 터치: fixed-incentive.router(batch approve by preset+month+team), 시트 템플릿, 시트 액션 버튼.
IR-4 [B·상] 주말 당직 치료 세션 — 전자결재 플로우 (확정 2026-07-29) ✅ 구현 (2026-08-10, 프리필 포함)
- 확정 모델: 자동 판별이 아닌 일반 전자결재(연장/휴일근무)와 동일한 신청→승인 플로우. 당직자 지정은 스케줄러에서 수동 지정(의사 당직 방식)이 기본이고, **본인 신고(신청)**도 가능 — 둘 다 결재 승인을 거쳐 확정된다.
- 요구: ① 직원앱에 치료 세션 신청 화면 — 당일 항목별 건수(게이트 12, FeS 2…)와 당직 인원 입력 → 기존 연장/휴일근무 결재 패턴으로 상신. ② 승인 시
upsertTreatmentSession실행 (청구액·병원분·splitRatio 자동) → 시트 therapy_daily 반영. ③ 스케줄러 수동 지정자는 신청 단계 프리필(당직일·인원)로 마찰 최소화. - 수용 기준: 토요일 당직 치료사가 앱에서 세션 신청 → 팀장 승인 → therapy_daily 행 생성, splitRatio 자동(2명=0.5), 팀매니저 피벗 반영. 미승인 신청은 시트/정산에 나타나지 않는다.
- 터치: 직원앱 신규 신청 섹션(approval 컴포넌트 패턴 재사용), 결재 훅(기존 leave/overtime notification 훅 패턴), 승인 핸들러 → upsertTreatmentSession, 스케줄러 당직 지정 연동(프리필).
IR-5 [B·하] 원장 당일 현황 ✅ 구현 (2026-08-10)
- 요구: management-dashboard(또는 시트 상단 요약)에 오늘/어제 인센 발생: 건수·발생액·치료 세션 청구액/병원분. "당일 마감에 얼마 벌었고 얼마 나갈지".
- 터치: management-dashboard 위젯 1개 + 집계 쿼리 (기존 listIncentives 집계 재사용 가능).
IR-6 [C·하] 시트 인센 행 편집 직관성 ✅ 구현 (2026-08-10)
- 요구: ① 인센 행에서 편집 불가 셀은 미리 시각 구분(derived 셀과 동일한 톤) + 더블클릭 시 throw 대신 안내 토스트("이 값은 등록 원본에서만 수정…"). ②
edit/incentive.ts의 catch-all throw 제거. - 비고: 호봉 시트 버그(2026-07-22)와 동일 계열 — "편집 가능성은 에러가 아니라 렌더에서 보여준다" 원칙으로 통일.
IR-7 [C·하] 분배 산식 가시화 ✅ 구현 (2026-08-10)
- 요구: therapy_daily/인센 행 선택 시 셀 상세 패널에 분배 사슬 표시:
청구 1,240,000 × 50% = 620,000 ÷ 2명 = 310,000 (본인 0.5). 기존formulaSubstituted패턴을 인센 분배에 확장.
IR-8 [C·하] 프리셋 마스터 ↔ 앱 연결 가시화 ✅ 구현 (2026-08-10, 직원앱 표시 포함)
- 요구: 프리셋 시트 헤더/안내에 "이 시트의 단가·항목이 직원앱 등록 화면에 그대로 나갑니다" 명시 + 직원앱 프리셋에 최근 변경 시각 표시. (U3)
IR-9 [C·중] 승인 축 단일화 안내 ✅ 확인·명시 (2026-08-10)
- 요구: 시트의 승인/지급 워크플로우와 fixed_incentive.status의 관계를 한 축으로 정리 — 시트 행 승인 = 레코드 approved가 되도록 동기화 확인(이미 동작하면 문서화만), 급여 반영 조건("승인 행만 커밋됨" 여부)을 시트 UI에 명시. (U4)
IR-10 [A·중] 치료 인센 N분할 원화 라운딩 — 합계 보존 (확정 2026-07-29 신설) ✅ 구현 (2026-08-10)
- 확정 해석: "0.5 단위"는 치료 인센티브의 **1/2 분할(치료사 2인 공동 치료)**을 뜻하는 N분할 케이스의 하나다. unitCount에 소수를 여는 것이 아니라 splitRatio(분할) 체계가 맞다.
- 현 구현의 결함 (검증 완료):
upsertTreatmentSession의 인당 금액이perPerson = round(therapistShare / N)— 3분할 등 나누어떨어지지 않는 경우 합계가 ±1~2원 어긋난다. 예: 치료사분 200,000원 ÷ 3 = 66,666.67 → 66,667 × 3 = 200,001원 (1원 초과 지급). splitRatio도round(1/3, 2) = 0.33으로 0.99만 표현된다. - 원화 정산 관행 (조사 요약): 인센티브 내부 분배의 라운딩에는 법정 규칙이 없다(임금 전액 지급 원칙상 총액이 보존되면 내부 배분은 사규 재량). 급여 실무 관행은 ① 원 미만 절사 후 잔여를 선임/대표 1인에게 귀속(합계 보존, 가장 흔함) ② 잔여를 회사(병원)분에 흡수 ③ 10원 단위 절사. 참고로 세금·4대보험은 별도 규칙(원 단위 절사 등)이 이미 급여 계산기에서 처리되므로 여기선 분배만 다룬다.
- 요구: largest-remainder(합계 보존) 방식을 기본으로 — 각자
floor(share/N)후 잔여 원을 소수부 큰 순서(동률이면 입력 순서 선임)에게 1원씩 배분.therapistShare = Σ perPerson이 항상 성립. 잔여 귀속 규칙(선임 귀속/병원 흡수)은 조직 상수incentive.split_remainder로 선택 가능하게. splitRatio 표시는 분수 표기(1/3) 또는 소수 4자리로 확장해 0.33×3≠1 혼란 제거. - 수용 기준: 3분할 200,000원 → 66,667/66,667/66,666 (합 200,000) 또는 설정에 따라 66,666×3 + 병원분 +2원. 단위 테스트로 2·3·4·7분할 합계 보존 고정.
- 터치: fixed-incentive.router(upsertTreatmentSession 분배 로직), splitRatio 표시(시트 ref 열· 팀매니저 피벗·직원앱), 단위 테스트.
6. 우선순위 로드맵 (제안)
- 1차 (정책 위반 해소): IR-1 당일 마감 · IR-2 직종 필터 — 정책의 전제 조건이자 소형~중형
- 2차 (정책 완성): IR-10 N분할 라운딩(치료 정산 정확성 — 소형이며 선행 가치 큼) → IR-4 주말 세션 결재 플로우 → IR-3 합산 승인 (IR-4가 데이터 입력을 완성해야 IR-3 집계가 의미 있음) · IR-5 원장 현황
- 3차 (직관성): IR-6 → IR-7 → IR-8/9 — 시트 사용자 마찰 제거
7. 확정 사항 (2026-07-29 사용자 결정)
- 0.5 단위 = N분할: unitCount 소수 개방이 아니라 분할 체계. 나누어떨어지지 않는 N분할의 소수 처리는 IR-10으로 신설 (현 구현에 합계 ±1원 결함 확인됨).
- IR-3 개별 건 이탈: 시스템 미지원 확정 — HR 수기(합산에서 제외 → 차월 반영), 근거는 시트 메모.
- IR-1 마감: 일단위 마감 + 3교대 익일 마감 유예 설정 (팀 단위 오버라이드).
- IR-4 당직자: 스케줄러 수동 지정 + 본인 신고, 일반 전자결재와 동일 플로우 (자동 판별 아님).
8. 구현 기록
1차 (2026-08-10) — IR-1 · IR-2 ✅
- IR-1 당일 마감:
fixedIncentive.create에서 SELF scope(자기 등록) +workDate있을 때만 검증 — 마감 시각 = workDate 00:00(조직 타임존) + max(24, grace)h, 미래 날짜 거부. grace 는organization_parameters의incentive.cutoff_grace_hours(조직 기본) +incentive.cutoff_grace_hours.<teamId>(팀 오버라이드 — 3교대 익일 마감, 예 30 = 익일 06시), 복수 팀은 최대값. 팀장/관리자(TEAM/ALL)·workDate 없는 입력은 통과(정정 경로 보존). 순수 판정incentiveCutoffAllows(utils/incentive-cutoff.ts) — 단위 테스트 12종(타임존 경계 포함). 라이브: 어제 등록 → 400 「당일 인센티브만 등록할 수 있어요…」 / 오늘 → 성공 / 팀 유예 설정 후 어제 → 성공. - IR-2 직종별 프리셋: 기존
IncentivePresetCategory.applicableTeams(jsonb, 팀 uuid[]) 활용 — 마이그레이션 불필요.listPresets가 SELF scope 에서만 소속 팀 교집합으로 필터(미지정 카테고리·무카테고리는 전체 노출, 팀장/관리자는 전체). 시트 연동: 프리셋 시트에 「카테고리」(읽기)·「적용 대상(팀)」(팀 이름 콤마 목록) 열 추가 — 시드 + 마이그레이션 1878 (시스템 템플릿·기적용 SheetView 멱등 패치). ⚙️ 마스터에 적용 시 팀 이름→id 해석 후 카테고리에 기록(같은 카테고리 프리셋 전체에 적용 — headerNote 로 안내), 미존재 팀 이름은 한글 오류. 라이브: 타팀 제한 → 픽커 미노출 / 자기 팀 → 노출 / 해제 → 전체 / 시트 write-back·재조회·오류 문구 확인. - 남김: 직원앱의 마감 안내 문구(등록 화면에 유예 시각 표시)는 서버 메시지 노출로 갈음 — 전용 UI 는 후속. IR-2 "직무 태그" 축은 미구현(팀 축만 — 확정 수용 기준이 팀 기준).
2차 착수 (2026-08-10) — IR-10 ✅
- IR-10 N분할 라운딩:
splitTherapistShare(utils/incentive-split.ts) — largest-remainder 합계 보존. 각자 floor(share/N), 잔여 원은 입력 순서(선임) 앞에서부터 1원씩 —Σ perPerson === therapistShare항상 성립. 잔여 귀속은 조직 상수incentive.split_remainder("senior" 기본 / "hospital" = 병원분 흡수).splitRatio는 소수 4자리(0.3333)로 확장 — 마이그레이션 1879(numeric 5,2 → 7,4).upsertTreatmentSession이 인당 금액 배열로 저장하고 metadata 에 분배 모드 기록. hr-system 세션 모달 미리보기도 동일 분할로 표시(잔여 있으면 「66,667원 × 1명 · 66,666원 × 2명」). 단위 테스트 15종(2·3·4·7분할 합계 보존·hospital 모드·경계). 라이브: 3분할 200,000 → 66,667/66,667/66,666 (합 200,000, 종전 200,001 결함 해소) / hospital 모드 → 66,666×3 + 병원분 +2원.
2차 계속 (2026-08-10) — IR-5 ✅
- IR-5 원장 당일 현황:
managementDashboard.incentiveDailySnapshot— 오늘/어제(조직 타임존) 인센티브 요약: 발생 건수·치료사 지급 발생액("얼마 나갈지") + 치료 세션 수·청구액· 병원분("얼마 벌었는지"). 세션 지표는 sessionId distinct 로 집계(구성원 레코드 복제 metadata 부풀림 방지), 취소 건 제외·live 버전만. management-dashboard 인센티브 현황 카드 상단에 오늘/어제 2칸 스트립(발생 없음이면 문구, 로드 실패 시 스트립 숨김 — 메인 데이터 비영향). 라이브: 3분할 세션 1건 등록 → 오늘 = 발생 3건·지급 200,000(합계 보존 분배 반영)·세션 1건· 청구 400,000·병원분 200,000 확인.
2차 계속 (2026-08-10) — IR-3 ✅
- IR-3 합산 확인 승인: 제안대로 신규 화면 없이 시트 중심 구현. ①
fixedIncentive.approveByType(year, month, incentiveType)— PENDING 전건 일괄 승인 (INCENTIVE:APPROVE scope — 팀장은 scopedEmployeeIds 로 자기 팀원 건만), 직원별 알림, 잠긴 시트 가드. 대기 0건이면 「승인 대기 건이 없어요」. ② 「인센티브 합산 확인」 시스템 템플릿(slugincentive_pivot_approval, 마이그레이션 1880 시드) — 행=프리셋(마스터), 지표는 인센티브 세션 전체(incentive_all)를 새 세로 집계 함수로 피벗: 건수 COUNTIF · 수량 합 SUMIF(unitCount) · 승인 대기/완료 COUNTIFS(status) · 발생액 합 SUMIF(totalAmount). 조건은 같은 행 텍스트 참조[name]— buildSumifPredicate 에 동일-행 원시값 비교 경로 추가(숫자 평가기 우회, 테스트 3종). ③ 행 우클릭 → 「이 항목 일괄 승인」 — 인센 세션 행(유형) / 합산 확인 피벗 행(프리셋 이름) 양쪽에서 노출. 컨펌에 "개별 이탈 미지원 — 시트 메모 + 차월 반영" 정책 문구 포함. 라이브: 피벗 「코드1 건수 2·수량합 4·대기 2·발생액 20,000」 → 일괄 승인 → 「대기 0·완료 2」, 재호출 시 0건 안내. 개별 승인 API 는 예외 처리용으로 유지.
2차 계속 (2026-08-10) — IR-4 ✅ (스케줄러 프리필 제외)
- IR-4 주말 당직 세션 전자결재: 확정 모델대로 자동 판별 없이 신청→승인 플로우로 구현. ① 서버 —
TreatmentSessionRequest엔티티(마이그레이션 1881, varchar enum 관행) + 프로시저 6종:listMyTeammates(당직 인원 픽커) ·requestTreatmentSession(본인 포함 강제, 청구 0원 거부, 승인자 알림) ·listTreatmentSessionRequests(scope 필터, 연·월·상태) ·approveTreatmentSessionRequest(잠긴 시트 가드 →performTreatmentSessionUpsert공유 헬퍼 → 세션 생성·sessionId링크) ·reject(사유 필수) ·cancel(본인·대기만). 승인 시 청구액·병원분·splitRatio(IR-10 라운딩 포함)는 기존 세션 업서트가 그대로 계산. ② 직원앱 — approval 섹션 인센티브 탭에 「주말 당직 치료 세션 신청」 버튼 + 신청 내역 카드(상태 배지·반려 사유·대기 취소) + 신청 모달(modals/session-request.html: 근무일, 항목별 건수 동적 행, 당직 인원 칩 — 선택 순서 = 잔여 배분 순서, 청구 합계 미리보기). ③ 팀매니저 — 인센티브 탭 상단 「주말 당직 세션 신청」 대기 카드(신청자·근무일·항목· 청구 합계·N명 분배) + 승인(askConfirm)/반려(showPrompt 사유 필수) 버튼, 연·월 필터와 연동(loadIncentives에서 함께 갱신). 라이브: 신청(40,000원 1인) → 팀매니저 필터 조합(연·월·pending) 목록 확인 → 승인 → 세션 생성·fixed_incentives 팬아웃 확인. 검증 데이터는 soft-delete 로 정리. - 남김(후속): 스케줄러 당직 지정 → 신청 프리필(당직일·인원) 연동. 미승인 신청은 시트/정산에 나타나지 않음(수용 기준 충족).
3차 (2026-08-10) — IR-6·7·8·9 직관성 소형 4건 ✅
- IR-6 인센 행 편집 직관성: ① 편집 불가 셀 사전 시각 구분 — 데코레이션 패스
_applyRecordLockTint(cell-styles.ts)가 인센티브 레코드 행에서 저장 디스패치 미러 (_isCellEditableForRow) 기준으로.cell-record-locked(글자 neutral-400 톤)를 칠한다. therapy_daily 행은 제외(세션 편집 모달이라는 별도 어포던스). ② 더블클릭 시 조용한 무시 대신 안내 토스트("등록 원본에서 수정하면 자동 반영"). ③edit/incentive.tscatch-all throw 제거 — notify 토스트 + true 반환(수당 catch-all 로 흘러가지 않게 디스패치 종료). - IR-7 분배 산식 가시화: 셀 상세 패널
cellDetailInfo.splitChain— 치료 인센 레코드 행은청구 X − 병원 Y(50%) = 치료사분 Z × 본인 0.5 = A원, therapy_daily 세션 행은… ÷ N명사슬을 한 줄로 표시. 서버 계산 값을 조립만 하고 재계산하지 않는다 (largest-remainder ±1원은 서버 값이 진실). - IR-8 프리셋 마스터↔앱 연결 가시화: ① 프리셋 마스터 시트(autoPopulate.source = incentive_preset)에서만 그리드 상단
ds-notice--info— "이 시트의 항목·단가·적용 대상은 직원앱 등록 화면에 그대로 나가요. 「마스터에 적용」 실행 시 반영". ②listPresets응답에updatedAt추가(단가 기준 시점 표시용). 직원앱 표시는 보류: 현행 직원앱 결재 화면은 조회 전용 + 세션 신청뿐이고 프리셋 픽커를 쓰는 자기 등록 화면(_incentive)은 미마운트 레거시 — 자기 등록 UI 복원 시updatedAt을 그대로 쓰면 된다. - IR-9 승인 축 단일화: 동기화 검증 결과 이미 단일 축 — 시트 상태 셀 편집이
fixedIncentive.update(status)로 레코드 status 를 직접 쓰고(별도 시트 워크플로우 상태 없음), 정산 집계(compute.ts)는approved/paid만 읽는다(2026-06-11 결정). UI 명시: 인센티브 행 상태 셀 선택 시 셀 상세 패널에 "이 상태가 레코드 승인 상태 그 자체 — 집계에는 승인·지급만 반영" 안내(ds-notice).
4차 (2026-08-10) — 후속 2건 완결: IR-4 프리필 · IR-8 직원앱 표시 ✅
- IR-4 당직 인원 프리필: 스케줄러에는 치료사 당직 지정 데이터가 없어(간호 전용 D/E/N) 해당 날짜의 출근 기록을 원천으로 확정.
listMyTeammates가workDate(optional)를 받아 그 날짜에 출근 기록이 있는 동료를onDuty: true로 표시하고 (org 필터 + deletedAt 가드), 세션 신청 모달은 모달 열기·근무일 변경 시마다 onDuty 동료를 당직 인원으로 자동 선택한다(본인 항상 포함, 출근 기록 0건이면 기존 선택 유지). 칩에 「근무」 배지로 자동 선택 근거를 표시. 라이브: 출근 기록 시드 → onDuty true → 정리 완료. 스케줄러에 치료사 당직 지정 기능이 생기면 그 데이터를 우선 원천으로 바꾸면 된다. - IR-8 직원앱 표시 + 자기 등록 진입점 복원: 결재 화면 개편 때 사라졌던 프리셋 자기 등록(P1)을 신청 시트에 「인센티브 등록」 항목으로 복원 — 레거시
_incentive의 incentive/* 모듈(fetchIncentivePresetGroups·createIncentive) 재사용, 미사용 상태였던modals/incentive-register.html을 approval 바인딩으로 재작성. 모달: 카테고리 optgroup 프리셋 select(IR-2 직종 필터 반영) + 「단가 기준: N월 N일 변경」 (listPresets 의 updatedAt 최대값 — IR-8 핵심 표시) + 근무일 오늘 고정(IR-1) + 건수·메모·예상 금액. 라이브: 오늘 등록 성공 / 이틀 전 날짜는 마감 문구로 거부 / updatedAt 응답 확인. 검증 데이터 정리 완료.
5차 (2026-08-10) — 자기 등록 병원별 스위치 ✅
- 프리셋 자기 등록 on/off 를 병원(조직)별 설정으로: 상수 스토어
incentive.self_registration(booleanValue, admin 설정 → 상수 스토어 화면에서 관리 — cutoff_grace_hours·split_remainder 와 같은 관행). 파라미터가 없으면 기본 꺼짐 (opt-in, 2026-08-10 사용자 결정) — 쓰려는 병원만 booleanValue=true 로 켠다. 문자열 값은 명시적 "true"/"on"/"1" 만 켜짐으로 해석. - 이중 방어: ① 서버 —
fixedIncentive.create가 SELF scope 일 때 꺼진 조직이면 FORBIDDEN 「이 병원은 인센티브 자기 등록을 사용하지 않아요」. 관리자/시트 경로 (TEAM/ALL)는 무관. ② 직원앱 —getSelfRegistrationConfig조회로 신청 시트의 「인센티브 등록」 그룹을 꺼진 병원에서 통째로 숨김(조회 실패 시에도 숨김 — 기본 안 보임에서 켜짐 확인 후 노출). - admin 설정 화면 토글: 설정 탭(org) 에 「인센티브」 카드 — 「직원앱 인센티브 자기 등록」 ds-switch (알림 설정 카드와 같은 즉시 저장 패턴). 값의 정본은 그대로 상수 스토어 파라미터 — 토글이 organizationParameter.create/update(SETTING:MANAGE, 변경 이력 자동)를 호출해 파라미터를 만들거나 수정한다. 글로벌 파라미터 메뉴가 없는 배포에서도 설정 탭에서 관리 가능.
- 라이브: 파라미터 없음 → enabled false(기본 꺼짐) + SELF create FORBIDDEN / true 파라미터 → enabled true / 토글 경로(update false↔true) 즉시 반영 확인. 관리자 create 는 스위치와 무관하게 통과. 검증 데이터 정리 완료(demo org 는 opt-in 파라미터 유지 = 켜짐).