# PRD — 가상 인사총무 ERP (교육용) > 🎓 **이 문서와 이 프로젝트의 모든 데이터는 교육용으로 창작된 가상 자료입니다.** > 실존하는 회사·개인·급여·인사 정보는 일절 사용하지 않았습니다. | | | |---|---| | 최초 작성 | 2026-09-14 | | **이번 개정** | 2026-09-14 (v2) — 급여 화면 추가 · 취소 상태 추가 · 빠진 규칙 명문화 | | 대상 사용자 | 인사·총무 담당자 (교육 수강생) | | 결과물 | 단일 HTML 파일 `인사총무ERP_교육용가상자료.html` | | 실행 방법 | 파일 더블클릭 → 브라우저에서 바로 열림 (설치·인터넷 불필요) | ### v2에서 바뀐 것 | # | 내용 | 왜 | |---|---|---| | 1 | **앱을 데이터 정본으로 확정** · `data/가상_인사총무.json`을 앱과 **똑같이** 재생성 | 같은 사람의 잔여연차가 13.0 / 12.0으로 갈려 수강생이 헷갈렸음 | | 2 | **급여명세 조회 화면 추가** (메뉴 4번째) | 인사·총무·급여 3개 표가 사번으로 연결되는 것을 화면으로 보여주기 위해 | | 3 | **신청 상태에 '취소' 추가** (기록을 지우지 않음) | 앱은 취소 시 줄을 삭제했는데, `데이터안내.md` §7은 "지우지 말라"고 가르쳐 **정반대**였음 | | 4 | 빠져 있던 동작 규칙 **7건 명문화** (§6) | 문서 어디에도 없어 판단이 사람마다 달라질 수 있었음 | --- # 1. 목적 "ERP가 실제로 어떻게 돌아가는가"를 **직접 눌러보며** 이해시킨다. 전달할 메시지는 딱 두 개다. > **① 휴가 신청(인사)과 비품 신청(총무)은 겉모습만 다를 뿐 뼈대가 똑같다.** > 신청 → 승인 → 숫자(잔여연차 / 재고)가 자동으로 줄어든다. > > **② 화면의 숫자는 저장된 값이 아니라 계산된 값이다.** > 그래서 승인 버튼 하나에 관련된 모든 숫자가 저절로 맞는다. 메시지 ②를 지키기 위해, 이 앱은 **잔여연차를 어디에도 저장하지 않는다.** (단 하나의 예외는 급여 — §6-7 참조) --- # 2. 사용자와 역할 역할 2개. 화면 오른쪽 위 버튼으로 즉시 전환한다. 로그인 절차는 없다. | 역할 | 고정 인물(가상) | 하는 일 | |---|---|---| | 👤 **직원** | 강도현 (E006, 영업팀 사원) | 휴가·비품을 **신청**하고, 내 신청·내 급여명세를 확인 | | 🧑‍💼 **인사총무 담당자** | 박준호 (E002, 경영지원팀 대리) | 신청을 **승인/반려**하고, 전사 현황·전 직원 급여를 관리 | **교육 포인트**: 똑같은 신청 1건이 직원 화면에서는 "내가 낸 것, 승인 대기", 담당자 화면에서는 "내가 처리해야 할 것"으로 보인다. 버튼 한 번으로 비교된다. --- # 3. 화면 구성 메뉴 **4개**. 왼쪽 세로 메뉴로 이동한다. (v2에서 💰 급여명세 추가) | 메뉴 | 영역 | 👤 직원 시점 | 🧑‍💼 담당자 시점 | |---|---|---|---| | 🏠 홈(현황) | 공통 | 내 잔여연차, 내 신청 상태, **내 연차 계산 내역** | 처리 대기 건수, 부서별 연차 소진율, 월별 휴가, 재고 부족, **사원 클릭 시 연차 계산 내역** | | 🌴 휴가 신청 | 인사 | 신청서 작성 · 내 신청 목록 · 대기 건 **취소** | 전체 신청 목록 · **승인/반려** | | 📦 비품 신청 | 총무 | 품목 선택 후 수량 신청 · 내 신청 목록 · 대기 건 **취소** | 전체 신청 처리 · 재고 현황표 | | 💰 **급여명세** | 급여 | **내 명세서 1장** (2026-09 귀속) | **12명 전체 목록** + 총 지급액 | **급여 화면은 조회 전용이다.** 입력·수정·계산 기능을 넣지 않는다 (§6-7). ## 3-1. 시작 화면 `index.html` (v3 신규) 앱과 **별개 파일**인 시작 화면. 로컬 서버의 첫 화면이자, 수업을 여는 안내 페이지다. | 담는 것 | 내용 | |---|---| | 앱 열기 | 앱으로 가는 주요 버튼 **하나** | | **현재 실행 방식** | `file://`인지 `http://`인지 감지해 📦/📂 로 표시 | | 교육 메시지 2가지 | §1의 메시지 ①② | | 화면 4개 × 시점 2개 | 표로 요약 | | 가상 데이터 요약 | 직원 12 · 휴가 24 · 비품 10종 · 급여 12 | | **수업 진행 순서 9단계** | 강사가 그대로 따라 할 수 있는 순서 | | 여는 방법 2가지 | 더블클릭 / 로컬 서버 | | 문서 6개 링크 | PRD · 데이터안내 · 확인예시 · DESIGN · 레퍼런스 · JSON | **규칙** - **자동 이동을 넣지 않는다.** 수강생이 읽을 시간이 필요하다. - 디자인은 **`DESIGN.md` 토큰을 그대로** 쓴다 (색·글꼴·여백·모서리·버튼). - **교육용 배너와 표 각주를 여기에도 둔다** (§8). - 앱 파일명(`인사총무ERP_교육용가상자료.html`)은 **바꾸지 않는다** — 파일명 자체가 §8의 표시 4곳 중 하나이기 때문이다. --- # 4. 데이터 명세 가상 회사명: **한빛상사(주)** — 실존하지 않는 창작 회사. 데이터는 HTML 파일 안에 `const DATA_*` 로 하드코딩한다. **이 앱이 데이터 정본이다.** | 묶음 | 건수 | 한 줄의 뜻 | |---|---|---| | `DATA_EMP` 직원 | 12 | 직원 1명 | | `DATA_LEAVE` 휴가신청 | 24 | 신청 1건 | | `DATA_ITEM` 비품 품목 | 10 | 품목 1종 | | `DATA_SUPPLY` 비품신청 | 18 | 신청 1건 | | `DATA_PAY` **급여명세** (v2 신규) | **12** | 직원 1명의 한 달치 급여 | ## 4-1. 직원 12명 (`DATA_EMP`) 필드: `empId`, `name`, `dept`, `grade`, `join`, `granted`(연차 부여일수) | 사번 | 이름 | 부서 | 직급 | 입사일 | 연차부여 | |---|---|---|---|---|---| | E001 | 김서연 | 경영지원팀 | 부장 | 2016-03-02 | 20 | | E002 | 박준호 | 경영지원팀 | 대리 | 2021-07-01 | 16 | | E003 | 이하늘 | 경영지원팀 | 사원 | 2024-01-15 | 15 | | E004 | 정민수 | 영업팀 | 차장 | 2018-05-14 | 18 | | E005 | 최유진 | 영업팀 | 과장 | 2019-09-02 | 17 | | E006 | 강도현 | 영업팀 | 사원 | 2023-03-06 | 15 | | E007 | 윤소라 | 물류팀 | 과장 | 2020-02-03 | 17 | | E008 | 임재원 | 물류팀 | 대리 | 2022-06-13 | 16 | | E009 | 한지우 | 물류팀 | 사원 | 2025-01-06 | 15 | | E010 | 오세훈 | 개발팀 | 차장 | 2017-11-20 | 19 | | E011 | 신예린 | 개발팀 | 대리 | 2022-09-19 | 16 | | E012 | 배동건 | 개발팀 | 사원 | 2024-08-05 | 15 | - 부서 4개 × 3명. **연차 부여일수 합계 = 199일** - ⚠️ **사용일수·잔여일수를 저장하지 않는다.** 휴가신청에서 계산한다 (§5-1). - 퇴사일·재직상태 필드는 **이번 범위 밖** (§7). ## 4-2. 휴가신청 24건 (`DATA_LEAVE`) 필드: `id`, `emp`, `type`(연차/반차/병가), `start`, `end`, `days`, `reason`, `status`(대기/승인/반려/**취소**), `by`, `on`, `note` **승인 14건** | 번호 | 사번 | 종류 | 기간 | 일수 | 사유 | |---|---|---|---|---|---| | L001 | E001 | 연차 | 06-15 ~ 06-17 | 3 | 가족 여행 | | L002 | E004 | 연차 | 06-22 ~ 06-26 | 5 | 여름 휴가 | | L003 | E005 | 연차 | 07-06 ~ 07-08 | 3 | 개인 사유 | | L004 | E007 | 연차 | 07-13 ~ 07-14 | 2 | 경조사 | | L005 | E010 | 연차 | 07-20 ~ 07-24 | 5 | 여름 휴가 | | L006 | E008 | 연차 | 07-27 ~ 07-31 | 5 | 여름 휴가 | | L007 | E011 | 연차 | 08-03 ~ 08-05 | 3 | 개인 사유 | | L008 | E006 | 연차 | 08-10 ~ 08-12 | 3 | 여름 휴가 | | L009 | E012 | 연차 | 08-17 ~ 08-18 | 2 | 개인 사유 | | L010 | E003 | 연차 | 08-24 ~ 08-26 | 3 | 가족 행사 | | L011 | E002 | 연차 | 09-01 ~ 09-03 | 3 | 개인 사유 | | L012 | E009 | 반차 | 09-04 | 0.5 | 병원 진료 | | L013 | E005 | **병가** | 09-07 ~ 09-08 | 2 | 입원 | | L014 | E001 | 반차 | 09-11 | 0.5 | 관공서 방문 | **대기 6건** | 번호 | 사번 | 종류 | 기간 | 일수 | 사유 | |---|---|---|---|---|---| | L015 | E006 | 연차 | 09-21 ~ 09-22 | 2 | 개인 사유 | | L016 | E003 | 연차 | 09-24 ~ 09-25 | 2 | 병원 진료 | | L017 | E009 | 연차 | 09-28 ~ 09-30 | 3 | 가족 행사 | | L018 | E011 | 반차 | 10-02 | 0.5 | 관공서 방문 | | L019 | E004 | 연차 | 10-05 ~ 10-07 | 3 | 휴식 | | L020 | E008 | **병가** | 09-16 ~ 09-17 | 2 | 입원 | **반려 4건** | 번호 | 사번 | 종류 | 기간 | 일수 | 반려 사유 | |---|---|---|---|---|---| | L021 | E006 | 연차 | 09-01 ~ 09-05 | 5 | 팀 성수기 | | L022 | E012 | 연차 | 08-10 ~ 08-14 | 5 | 인수인계 미완 | | L023 | E007 | 연차 | 09-08 ~ 09-12 | 5 | 동일 기간 중복 | | L024 | E010 | 반차 | 08-21 | 0.5 | 일정 조정 요청 | - 모든 날짜는 **2026년**. 처리자는 전부 E002(박준호). - 대기 건의 `by`/`on`은 **null** — 임의의 값으로 채우지 않는다. - **초기 데이터에 '취소' 건은 없다.** 수강생이 취소를 눌러야 생긴다. ## 4-3. 비품 품목 10종 (`DATA_ITEM`) 필드: `code`, `name`, `cat`, `price`(원), `stock`(현재고), `safe`(안전재고) | 코드 | 품목명 | 분류 | 단가 | 현재고 | 안전재고 | 비고 | |---|---|---|---|---|---|---| | S001 | A4 복사용지(1박스) | 사무용품 | 24,000 | 32 | 20 | | | S002 | 볼펜(12자루) | 사무용품 | 7,200 | 15 | 20 | ⚠️ 부족 | | S003 | 포스트잇(5패드) | 사무용품 | 5,500 | 48 | 15 | | | S004 | 토너 카트리지 | 사무용품 | 89,000 | 6 | 8 | ⚠️ 부족 | | S005 | 노트북 마우스 | 비품 | 18,000 | 21 | 10 | | | S006 | USB 메모리 64GB | 비품 | 15,000 | 9 | 10 | ⚠️ 부족 | | S007 | 모니터 받침대 | 비품 | 32,000 | 12 | 5 | | | S008 | 종이컵(1000개) | 소모품 | 21,000 | 27 | 15 | | | S009 | 커피믹스(100T) | 소모품 | 19,800 | 11 | 12 | ⚠️ 부족 | | S010 | 물티슈(10팩) | 소모품 | 12,000 | 40 | 20 | | **재고 부족(현재고 < 안전재고) = 4종** (S002, S004, S006, S009) ## 4-4. 비품신청 18건 (`DATA_SUPPLY`) 필드: `id`, `emp`, `item`, `qty`, `reason`, `status`, `by`, `on`, `note` **승인 10건** (집행액 **489,300원**) | 번호 | 사번 | 품목 | 수량 | 금액 | |---|---|---|---|---| | P001 | E006 | S001 | 2 | 48,000 | | P002 | E003 | S003 | 3 | 16,500 | | P003 | E008 | S008 | 4 | 84,000 | | P004 | E011 | S005 | 1 | 18,000 | | P005 | E004 | S001 | 3 | 72,000 | | P006 | E010 | S007 | 2 | 64,000 | | P007 | E005 | S010 | 2 | 24,000 | | P008 | E012 | S002 | 2 | 14,400 | | P009 | E007 | S009 | 3 | 59,400 | | P010 | E001 | S004 | 1 | 89,000 | **대기 5건** (예상 집행액 **368,200원**) | 번호 | 사번 | 품목 | 수량 | 금액 | |---|---|---|---|---| | P011 | E006 | S005 | 1 | 18,000 | | P012 | E009 | S001 | 2 | 48,000 | | P013 | E002 | S004 | 2 | 178,000 | | P014 | E003 | S006 | 3 | 45,000 | | P015 | E011 | S009 | 4 | 79,200 | **반려 3건**: P016(E006·S007·2), P017(E012·S006·5), P018(E008·S010·6) ## 4-5. 급여명세 12건 (`DATA_PAY`) — v2 신규 필드: `id`, `emp`, `month`(귀속연월), `payDate`(지급일), `base`(기본급), `allow`(수당), `deduct`(공제), `net`(지급액), `status` **12명 × 2026년 9월 귀속분 = 12건. 금액은 전 직원 동일.** | 항목 | 금액 | |---|---| | 귀속연월 | 2026-09 | | 지급일 | 2026-10-10 | | 기본급 | 3,000,000원 | | 수당 | + 200,000원 | | 공제 | − 100,000원 | | **지급액** | **3,100,000원** | | 상태 | 확정 | | **12명 지급 총액** | **37,200,000원** | 급여번호는 `PY202609-E001` ~ `PY202609-E012`. > ⚠️ **실제 세금·4대보험은 계산하지 않는다.** `공제`는 설명용 **고정 금액 한 항목**뿐이다. > 실제 급여대장에는 국민연금·건강보험·고용보험·소득세·지방소득세가 각각 들어간다. > 전 직원 금액을 같게 둔 이유는 **"직급별 급여 차이"가 아니라 "사번으로 표가 연결되는 것"** > 에 집중시키기 위해서다. ## 4-6. 표 사이의 연결 **모든 표에 `사번`이 들어가고, 사번이 같으면 같은 사람이다.** ``` DATA_EMP E006 강도현 ─┬─→ DATA_LEAVE L008 · L015 · L021 (3건) ├─→ DATA_SUPPLY P001 · P011 · P016 (3건) └─→ DATA_PAY PY202609-E006 (1건) ``` - 직원은 **1명당 1줄**, 나머지는 **1명당 여러 줄** (1대다). - `DATA_EMP`에 없는 사번을 다른 표에 쓰지 않는다. - 자세한 설명은 `데이터안내.md`. ## 4-7. `data/가상_인사총무.json` 과의 관계 **앱이 정본이고, JSON은 앱과 똑같은 내용의 사본이다.** (v2에서 확정) | | 앱 (`인사총무ERP_교육용가상자료.html`) | JSON (`data/가상_인사총무.json`) | |---|---|---| | 역할 | **정본** · 실습용 | 사본 · 데이터 구조 설명용 | | 항목 이름 | 영어 (`empId`, `granted`) | 한글 (`사번`, `연차부여일수`) | | 내용 | 직원12 · 휴가24 · 비품10/18 · 급여12 | **동일** | - JSON은 **앱을 고칠 때 함께 갱신한다.** 내용이 갈라지면 안 된다. ### 여는 방법에 따라 데이터 출처가 달라진다 (v3에서 추가) 앱은 **두 가지 방식 모두** 지원한다. 어느 쪽이든 **화면과 숫자는 완전히 같다.** | 여는 방법 | 데이터 출처 | 화면 표시 | |---|---|---| | 파일 **더블클릭** (`file://`) | 파일 안의 **내장 데이터** | 📦 내장 데이터 | | **로컬 서버** (`http://`) | **`data/가상_인사총무.json`** 파일 | 📂 데이터 파일 | - 브라우저 보안 정책상 `file://`로 연 HTML은 외부 파일을 읽을 수 없다. 그래서 `file://`이면 **불러오기를 시도조차 하지 않고** 내장 데이터로 실행한다. (시도하면 콘솔에 불필요한 오류가 남는다.) - 왼쪽 메뉴 아래에 **지금 어느 데이터를 쓰고 있는지 항상 표시**한다 — 교육 목적. - 로컬 서버는 `로컬서버.mjs` — 외부 패키지 없이 Node 기본 기능만 쓴다. ``` 실행: node 로컬서버.mjs 주소: http://127.0.0.1:8123/ 중지: Ctrl + C ``` --- # 5. 계산 규칙 ## 5-1. 연차 ``` 사원별 사용일수 = 그 사원의 휴가신청 중 (상태='승인' AND 종류가 연차 또는 반차) 인 건의 일수 합 사원별 잔여연차 = 연차부여일수 − 사용일수 부서 연차소진율 = 부서 사용일수 합 ÷ 부서 부여일수 합 × 100 ``` | 상태 / 종류 | 연차를 깎나? | |---|---| | 연차·반차 **승인** | ✅ 깎는다 | | 연차·반차 **대기** | ❌ 안 깎는다 (승인 시 깎임 — 화면에 "대기 중 N일" 별도 표시) | | 연차·반차 **반려** | ❌ 안 깎는다 | | 연차·반차 **취소** | ❌ 안 깎는다 | | **병가** (모든 상태) | ❌ 안 깎는다 | - **반차는 0.5일**로 계산한다. **초기 상태 계산 결과** (전사 부여 199일 / 사용 38.0일 / 잔여 161.0일) | 사번 | 이름 | 부여 | 사용 | 잔여 | |---|---|---|---|---| | E001 | 김서연 | 20 | 3.5 | 16.5 | | E002 | 박준호 | 16 | 3.0 | 13.0 | | E003 | 이하늘 | 15 | 3.0 | 12.0 | | E004 | 정민수 | 18 | 5.0 | 13.0 | | E005 | 최유진 | 17 | 3.0 | 14.0 | | E006 | 강도현 | 15 | 3.0 | 12.0 | | E007 | 윤소라 | 17 | 2.0 | 15.0 | | E008 | 임재원 | 16 | 5.0 | 11.0 | | E009 | 한지우 | 15 | 0.5 | 14.5 | | E010 | 오세훈 | 19 | 5.0 | 14.0 | | E011 | 신예린 | 16 | 3.0 | 13.0 | | E012 | 배동건 | 15 | 2.0 | 13.0 | | **계** | | **199** | **38.0** | **161.0** | **부서별 연차 소진율**: 경영지원 **18.6%** · 영업 **22.0%** · 물류 **15.6%** · 개발 **20.0%** · 전사 **19.1%** **월별 승인 휴가 일수** (연차+반차, 병가 제외, 시작일 기준): 6월 **8.0** · 7월 **15.0** · 8월 **11.0** · 9월 **4.0** ## 5-2. 연차 계산 내역 > 참고: flex 「휴가 보유 현황 · 휴가 히스토리」 → `레퍼런스.md` §1 잔여연차가 **왜 그 숫자인지**를 한 줄씩 따라가는 표. | 줄 | 증감 | 잔여 | 설명 | |---|---|---|---| | 2026년 연차 부여 | `+부여일수` | 부여일수 | 기초 | | 연차·반차 **승인** | `−일수` | 줄어듦 | 승인됨 → 차감 | | 연차·반차 **반려** | `—` | 그대로 | 반려됨 → 차감 안 함 | | 연차·반차 **대기** | `—` | 그대로 | 승인되면 N일 차감 예정 | | 연차·반차 **취소** | `—` | 그대로 | 신청자가 취소함 | | **병가** (모든 상태) | `—` | 그대로 | 연차는 차감하지 않음 | | 현재 남은 연차 | | 최종값 | 대기분이 모두 승인되면 얼마가 되는지 함께 표시 | - 정렬은 **휴가 시작일 순**. - 담당자: 사원 이름 클릭 → 펼침 / 다시 클릭 → 닫힘. 데이터 초기화 시 닫힘. - 직원: 홈 하단에 본인 내역 **항상** 표시. - flex의 **관리자 수동 가감 기능은 넣지 않는다** — 숫자가 손으로 바뀌면 메시지 ②가 무너진다. ## 5-3. 비품 재고 ``` 승인 시: 해당 품목 재고 = 재고 − 신청수량 반려·취소 시: 재고 변동 없음 신청 금액 = 단가 × 수량 ``` - 재고보다 많은 수량은 **신청 단계에서** 차단한다. - 승인 결과 `재고 < 안전재고`가 되면 ⚠️ 경고를 표시한다. ## 5-4. 급여 (조회 전용) ``` 지급액 = 기본급 + 수당 − 공제 ``` - 앱은 이 값을 **계산하지 않고, 저장된 값을 그대로 보여준다.** 이유는 §6-7. - 화면에는 계산식을 함께 보여주어, 어떻게 나온 숫자인지 알 수 있게 한다. --- # 6. 동작 규칙 (v2에서 명문화) ## 6-1. 신청 차단 규칙 | 상황 | 처리 | |---|---| | 휴가 신청일수 **>** 잔여연차 (연차·반차만) | ⛔ 경고 + 신청 버튼 비활성 | | 휴가 신청일수 **=** 잔여연차 | ✅ **통과** — "초과"는 "큰 경우"이지 "같은 경우"가 아니다 | | **병가**는 일수가 잔여를 넘어도 | ✅ **통과** — 연차를 깎지 않으므로 비교할 이유가 없다 | | 시작일 > 종료일 | ⛔ 경고 + 신청 불가 | | 비품 신청수량 > 현재고 | ⛔ 경고 + 신청 불가 | | 비품 신청수량이 0 이하 | ⛔ 신청 불가 | ## 6-2. 휴가 일수 계산 — **달력일 기준** 시작일부터 종료일까지의 **달력상 날짜 수**로 센다. **주말·공휴일을 빼지 않는다.** - 예) 10-05(월) ~ 10-09(금) = **5일**, 10-03(토) ~ 10-05(월) = **3일** - 반차는 종료일 = 시작일, 일수 = **0.5** - 실제 회사는 영업일 기준이지만, 교육용에서는 **규칙이 단순해야 이해가 빠르다.** 화면에 이 사실을 안내 문구로 표시한다. ## 6-3. 신청 상태 — 4개 (v2에서 '취소' 추가) | 상태 | 누가 만드나 | 잔여연차·재고 영향 | |---|---|---| | **대기** | 직원이 신청 | 없음 | | **승인** | 담당자가 승인 | ✅ 차감됨 | | **반려** | 담당자가 반려 | 없음 | | **취소** | **직원이 대기 건을 취소** | 없음 | **취소는 줄을 지우지 않는다.** 상태만 `대기 → 취소`로 바꾸고, 처리자에 신청자 본인을 남긴다. - 이유: `데이터안내.md` §7 — *"지우지 말고 상태로 표시하라"*. 앱과 문서가 같은 원칙을 따라야 한다. - 취소된 건은 담당자의 **「처리할 신청」 목록에서 빠진다** (이미 처리가 끝난 건이므로). - 취소된 건은 **「처리 완료된 신청」에 남아** 목록에서 사라지지 않는다. - 취소는 **대기 상태에서만** 가능하다. 승인·반려된 건은 취소할 수 없다. ## 6-4. 빈 값 처리 - 대기 상태의 처리자·처리일은 **null**. 화면에는 `—`로 표시. - 어떤 빈 값도 임의의 값으로 채우지 않는다. ## 6-5. 병가에는 상한이 없다 병가는 일수 상한을 두지 않는다. 실제로는 회사 규정에 따라 상한과 증빙이 필요하지만, 여기서는 **"연차를 깎는 휴가와 깎지 않는 휴가"의 대비**만 가르치는 것이 목적이다. ## 6-6. 데이터 저장 방식 - **새로고침하면 초기 상태로 되돌아간다.** 브라우저에 저장하지 않는다. - 화면에 **「↺ 데이터 초기화」 버튼**을 둔다. - 이유: 교육용이므로 다음 수업 때 항상 같은 출발점이어야 한다. ## 6-7. 급여만 계산 결과를 저장한다 — 규칙의 유일한 예외 메시지 ②는 "숫자를 저장하지 말고 계산하라"였다. 그런데 급여는 `지급액`을 **저장한다.** | | 잔여연차 | 지급액 | |---|---|---| | 성격 | 지금 상태를 보여주는 **현재값** | 그때 지급한 **과거 기록** | | 바뀌어야 하나 | 승인 한 번에 바뀌어야 정상 | **바뀌면 안 됨** (회계·세무 기록) | | 방식 | **계산한다** | **찍어서 보관한다** | > **"지금 얼마인가"는 계산하고, "그때 얼마였나"는 저장한다.** 그래서 급여 화면은 **조회 전용**이며, 수정·재계산 버튼을 두지 않는다. --- ## 6-8. 기술 제약 | 항목 | 규칙 | |---|---| | 파일 구성 | **단일 HTML 파일 1개.** 외부 CDN·라이브러리·빌드 도구 사용 안 함 | | **더블클릭 실행** | **반드시 유지한다.** 로컬 서버는 *선택*이지 필수 조건이 되면 안 된다 | | 로컬 서버 | `로컬서버.mjs` — **외부 패키지를 설치하지 않는다.** Node 기본 기능만 | | 차트 | 직접 만든 **인라인 SVG** 막대그래프. 차트 라이브러리 없음 | | 숫자 표기 | 천단위 쉼표(`ko-KR`) · 연차는 소수 1자리 · 금액은 정수 + "원" | | 테마 | 라이트/다크 자동 대응 (`prefers-color-scheme`) | | 반응형 | 화면이 좁아져도 **페이지 전체 가로 스크롤이 생기면 안 됨** | --- # 7. 이번 범위에서 제외하는 것 | 제외 항목 | 이유 | |---|---| | 급여 **계산·수정** | 확정된 급여를 고치는 건 실무 금기 — 잘못된 습관을 가르칠 위험 | | 세금·4대보험 계산 | 요율이 해마다 바뀌고 복잡함 — "ERP 구조"가 아니라 "급여 계산법" 수업이 됨 | | 퇴사일·재직상태 | 퇴사자 제외 집계까지 가면 규칙이 두 배로 늘어남 | | 연차 **이월·소멸·촉진** | 법정 제도 설명이 길어져 초점이 흐려짐 | | 연차 **부여 표 분리** | 연도별 이력이 필요해질 때 분리한다 (`레퍼런스.md` §2) | | 비품 **예산 한도** | 재고 차감만으로 "숫자가 줄어든다"는 메시지는 충분히 전달됨 | | 인사평가 · 채용 · 자산 감가상각 | 별개 업무 영역 | | 로그인/권한 · 엑셀 내보내기 · 알림/메일 | 단일 HTML 파일 범위 밖 | | **2단계 결재** (팀장 → 인사) | 상태 경우의 수가 2배 — 교육 난이도 급증 | → 필요해지면 **별도 건으로** 추가한다. --- # 8. 교육용 가상 자료 표시 (필수) 4곳에 빠짐없이 표시한다. 1. **파일명** — `인사총무ERP_교육용가상자료.html` 2. **화면 최상단 고정 배너** (스크롤해도 항상 보임) > 🎓 교육용 가상 자료 · 실제 인사·급여 정보가 아닙니다 · 가상 회사 「한빛상사(주)」 3. **모든 데이터 표 바로 아래 각주** — "본 데이터는 교육 목적으로 창작된 가상 자료입니다." 4. **HTML 파일 첫 줄 주석** 5. (추가) **JSON 파일 최상단** `_안내` 항목 급여 화면에는 **추가 경고**를 둔다. > ⚠️ 실제 세금·4대보험은 계산하지 않은 가상 금액입니다. --- # 9. 확인 기준 (완성 판정 체크리스트) ## 기존 기준 (v1 — 이미 통과 확인됨) | # | 확인 내용 | 기대값 | |---|---|---| | **S1** | 담당자 홈 초기 상태 | 대기 휴가 **6건** / 비품 **5건**, 전사 소진율 **19.1%**, 재고부족 **4종**, 대기 비품 **368,200원** | | **S2** | 직원(강도현) 홈 초기 상태 | 잔여연차 **12.0일**, 휴가·비품 각각 승인1/대기1/반려1 | | **S3** | 부서별 소진율 그래프 | 경영지원 **18.6%** / 영업 **22.0%** / 물류 **15.6%** / 개발 **20.0%** | | **S4** | 월별 휴가 그래프 | 6월 **8.0** / 7월 **15.0** / 8월 **11.0** / 9월 **4.0** | | **S5** | 휴가 승인 (L015) | 강도현 잔여 12.0 → **10.0**, 대기 6 → **5건**, 소진율 → **20.1%** | | **S6** | 휴가 반려 (L016) | 이하늘 잔여 **12.0 유지** | | **S7** | 병가 승인 (L020) | 임재원 잔여 **11.0 유지**, 소진율 **불변** | | **S8** | 잔여 초과 차단 | 강도현(잔여 12)이 13일 신청 → 경고 + 버튼 비활성 | | **S9** | 비품 승인 (P013) | S004 재고 6 → **4**, 안전재고(8) 미달 ⚠️ | | **S10** | 재고 초과 차단 | S006(재고 9)에 10개 → 신청 불가 | | **S11** | 역할 전환 | 버튼 1회로 전환, 같은 신청이 양쪽에서 다르게 보임 | | **S12** | 데이터 초기화 | 모든 숫자가 S1/S2 상태로 복귀 | | **S13** | 교육용 표시 | 파일명·배너·표 각주·코드 첫 줄 **4곳 모두** | | **S14** | 연차 계산 내역 (강도현) | `부여 +15.0 → 15.0` / `L008 −3.0 → 12.0` / `L021 반려 — → 12.0` / `L015 대기 — → 12.0` / **현재 12.0** | | **S15** | 차감 안 되는 건의 사유 | 반려 "차감하지 않음" · 대기 "승인되면 N일 차감 예정" · **병가 "연차는 차감하지 않음"** | | **S16** | 내역이 승인에 반응 | L015 승인 후 해당 줄이 `−2.0 / 잔여 10.0 / 승인됨`으로 변함 | ## v2 신규 기준 | # | 확인 내용 | 기대값 | |---|---|---| | **S17** | 💰 급여 화면 — 직원 시점 | 강도현 명세서 **1장**, 귀속 **2026-09**, 지급일 **2026-10-10**, 기본급 3,000,000 + 수당 200,000 − 공제 100,000 = **지급액 3,100,000원** | | **S18** | 💰 급여 화면 — 담당자 시점 | **12건** 목록, **총 지급 합계 37,200,000원**, 세금 미계산 경고 문구 표시 | | **S19** | 급여 화면은 조회 전용 | 입력란·수정 버튼·계산 버튼이 **하나도 없음** | | **S20** | 대기 건 취소 → **기록이 남음** | 상태가 **'취소'**로 바뀜 · 목록에서 **사라지지 않음** · 처리자에 본인 표시 | | **S21** | 취소는 숫자를 바꾸지 않음 | 취소 후 잔여연차·재고 **변동 없음** | | **S22** | 취소된 건은 처리 목록에서 빠짐 | 담당자 「처리할 신청」 건수가 1 줄고, 「처리 완료」에 나타남 | | **S23** | 취소 버튼은 대기 건에만 | 승인·반려·취소된 건에는 취소 버튼이 **없음** | | **S24** | 경계값 통과 | 강도현(잔여 12.0)이 **정확히 12일** 신청 → **통과**, 승인 후 잔여 **0.0** | | **S25** | 병가 상한 없음 | 강도현(잔여 12.0)이 **병가 20일** 신청 → **통과**, 잔여 **12.0 유지** | | **S26** | 달력일 기준 안내 | 휴가 신청 화면에 "주말·공휴일을 빼지 않는 달력일 기준" 안내 표시 | | **S27** | JSON이 앱과 일치 | `data/가상_인사총무.json`의 직원12·휴가24·비품10/18·급여12가 앱 데이터와 **완전히 동일** | | **S28** | JSON 무결성 | 주인 없는 줄 **0건** · 고유번호 중복 **없음** · 지급액 = 기본급+수당−공제 **12건 일치** | ## v3 신규 기준 — 데이터 파일 읽기 (2026-09-14) | # | 확인 내용 | 기대값 | |---|---|---| | **S29** | 로컬 서버(`http://`)로 열기 | 왼쪽 아래에 **📂 데이터 파일** 표시 · `data/가상_인사총무.json 에서 불러옴` | | **S30** | 더블클릭(`file://`)으로 열기 | 왼쪽 아래에 **📦 내장 데이터** 표시 · **콘솔 에러 0건** (불러오기를 시도하지 않음) | | **S31** | 두 방식의 숫자가 같은가 | 양쪽 모두 강도현 잔여 **12.0** · 소진율 **19.1%** · 재고부족 **4종** · 급여총액 **37,200,000원** | | **S32** | 서버 모드에서 기존 기준 회귀 | S1~S28이 **서버로 열어도 전부 통과** | | **S33** | 시작 화면 `index.html` | 교육용 배너(고정) · 표 각주 · **외부 리소스 0건** · 자동 이동 **없음** | | **S34** | 시작 화면의 실행 방식 감지 | `http://` → 📂 "로컬 서버로 열었습니다" · `file://` → 📦 "파일을 직접 열었습니다" | | **S35** | 시작 화면 → 앱 이동 | 「앱 열기」 버튼 1회로 앱이 열리고, 데이터·숫자가 정상 | | **S36** | 시작 화면 문서 링크 | 6개(PRD·데이터안내·확인예시·DESIGN·레퍼런스·JSON) 모두 실제 파일을 가리킴 | --- # 10. 만드는 순서 (v2 작업분) — **완료 2026-09-14** ## 1단계 — 직원 · 휴가 · 급여 ✅ | # | 작업 | 상태 | |---|---|---| | 1 | `DATA_PAY` 12건 추가 (§4-5) | ✅ | | 2 | 💰 급여명세 화면 추가 (역할별 분기, 조회 전용) (§3) | ✅ | | 3 | 휴가 **취소 처리를 삭제 → 상태 변경**으로 수정 (§6-3) | ✅ | | 4 | 연차 계산 내역·집계에 **'취소' 상태 반영** (§5-1, §5-2) | ✅ | | 5 | 휴가 신청 화면에 **달력일 기준 안내** (§6-2) | ✅ | | 6 | 디자인 — 모서리 8px 통일 · 고정폭 `.mono` (DESIGN §9) | ✅ | ## 2단계 — 비품 · 문서 동기화 ✅ | # | 작업 | 상태 | |---|---|---| | 7 | 비품 **취소 처리를 상태 변경**으로 수정 (§6-3) | ✅ | | 8 | 비품 화면에 고정폭 `.mono` 적용 | ✅ | | 9 | `data/가상_인사총무.json`을 앱에서 뽑아 **재생성** (§4-7) | ✅ | | 10 | `확인예시.md` · `데이터안내.md` · `DESIGN.md` 동기화 | ✅ | | 11 | **S1~S28 전수 검증** — Playwright 실제 브라우저 | ✅ 콘솔 에러 0건 | > ⬜ **미적용 1건**: 주요 버튼 색(파랑 → 무채색). 인상이 크게 바뀌는 변경이라 > 실물을 보고 따로 결정하기로 보류 (DESIGN.md §9-2). --- # 11. 관련 문서 | 문서 | 내용 | |---|---| | `데이터안내.md` | 직원·휴가·급여 표를 왜 나누고 사번으로 어떻게 연결하는지 | | `확인예시.md` | 직원 한 명(강도현)으로 따라가는 입력값·예상 결과 | | `레퍼런스.md` | flex · Frappe HR · Odoo 공식 문서에서 가져온 설계 아이디어 3가지 | | `data/가상_인사총무.json` | 앱 데이터와 동일한 사본 (한글 항목명) | --- # 12. AI 연결 시험 화면 (v4 신규 · 실험) > 🎓 이 화면의 답변은 **외부 AI(Google Gemini)가 만든 글**이며, > 이 앱의 가상 인사·급여 자료와는 무관합니다. ## 12-1. 목적 인사총무 기능이 아니라, **API 키 연동이 제대로 되는지 확인하는 실험용 화면**이다. "우리 앱에서 AI를 부를 수 있는가"를 눈으로 확인하는 것이 전부다. ## 12-2. 화면 좌측 메뉴 5번째 `🤖 AI 연결 시험` · 직원/담당자 두 시점 모두 동일. | 칸 | 내용 | |---|---| | 질문 보내기 | 질문 입력칸(기본 문장 채워둠) + 「보내기」 버튼 + 결과 표시 | | 지금 쓸 수 있나 | 키 준비 여부(✅/🔑/📦) · 모델 이름 · 왜 로컬 서버가 필요한지 설명 | ## 12-3. 동작 규칙 | 항목 | 규칙 | |---|---| | **API 키 위치** | **서버(`로컬서버.mjs`)에만 둔다. 브라우저로 절대 내려보내지 않는다.** HTML에 키를 넣으면 페이지 소스에 그대로 노출되기 때문이다 | | 키 읽는 곳 | `.env` 의 `GEMINI_API_KEY`(또는 `GOOGLE_API_KEY`). 이름 없이 값만 적힌 줄도 인식한다 (`gemini/gemini_호출.py` 와 같은 처리) | | 작동 조건 | **`node 로컬서버.mjs` 로 열었을 때만.** 더블클릭(file://)·인터넷 배포본에서는 "지금은 사용할 수 없습니다"로 안내하고 **오류를 내지 않는다** | | 모델 | `gemini-3.6-flash`. 이름이 맞지 않으면(404) 사용 가능한 모델을 찾아 한 번만 다시 시도한다 | | 서버 주소 | `GET /api/ai/status` — 키 **준비 여부만** 반환 (키 값은 반환하지 않음)
`POST /api/ai/ask` — `{question}` 을 받아 `{모델, 답변, 걸린시간}` 반환 | | 외부 패키지 | 쓰지 않는다. Node 기본 `fetch` 로 호출한다 | | 데이터 영향 | **없다.** 직원·휴가·비품·급여 숫자를 전혀 건드리지 않는다 | ## 12-4. 확인 기준 - S13. `node 로컬서버.mjs` 로 열고 「보내기」 → 몇 초 뒤 **✅ 연결 성공 · 모델 이름 · 걸린시간**과 답변이 보인다 - S14. 파일을 더블클릭해서 열고 이 화면에 들어가면 **📦 지금은 사용할 수 없습니다** 안내가 뜬다 (오류 아님) - S15. 페이지 소스(Ctrl+U)에 **API 키가 보이지 않는다**