diff --git a/DashBoard-organization.html b/DashBoard-organization.html index 2ee57f5..05c03ba 100644 --- a/DashBoard-organization.html +++ b/DashBoard-organization.html @@ -8,8 +8,8 @@ - - + + @@ -60,6 +60,6 @@ - + diff --git a/backend/app/main.py b/backend/app/main.py index dec5847..95c81c6 100755 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1308,11 +1308,59 @@ def build_center_chair_viewer_html(layout: dict[str, object]) -> str: ctx.lineWidth = (selected ? 2.6 : active ? 2.0 : 1.6) / camera.scale;""", 1, ) + html = html.replace( + """ sorted.forEach((chair, index) => { + chair.key = String(index + 1); + chair.seatNo = index + 1; + });""", + """ sorted.forEach((chair, index) => { + chair.seatNo = index + 1; + });""", + 1, + ) html = html.replace( "function persistPlaced() {\n localStorage.setItem(STORAGE_KEY, JSON.stringify([...placed]));\n }", "function persistPlaced() {\n return;\n }", 1, ) + html = html.replace( + """ window.addEventListener("pointerup", (event) => { + if (dragging && dragStart) { + const move = Math.hypot(event.clientX - dragStart.x, event.clientY - dragStart.y); + if (move < 4) { + const rect = canvas.getBoundingClientRect(); + const picked = pickChair(event.clientX - rect.left, event.clientY - rect.top); + if (picked) { + if (placed.has(picked.key)) placed.delete(picked.key); + else placed.add(picked.key); + persistPlaced(); + if (activePersonId) { + const currentChair = getChairByPerson(activePersonId); + if (chairAssignments[picked.key] === activePersonId) { + delete chairAssignments[picked.key]; + } else { + if (currentChair && currentChair !== picked.key) delete chairAssignments[currentChair]; + chairAssignments[picked.key] = activePersonId; + } + persistAssignments(); + renderPeopleList(); + } + } + } + } + dragging = false; + dragStart = null; + canvas.classList.remove("dragging"); + requestDraw(); + });""", + """ window.addEventListener("pointerup", () => { + dragging = false; + dragStart = null; + canvas.classList.remove("dragging"); + requestDraw(); + });""", + 1, + ) html = html.replace( """ window.addEventListener("pointerup", (event) => { if (dragging && dragStart) { diff --git a/docs/DEVELOPMENT_HISTORY.md b/docs/DEVELOPMENT_HISTORY.md index 6221b2a..d916c9f 100644 --- a/docs/DEVELOPMENT_HISTORY.md +++ b/docs/DEVELOPMENT_HISTORY.md @@ -169,9 +169,59 @@ - Windows LAN IP 또는 WSL IP가 바뀌면 `portproxy`의 `connectaddress`는 다시 맞춰야 한다 - 운영 안정성을 위해 향후 자동화 스크립트화가 필요함 +## 10. 인증 기본 구조 추가 + +### 작업 내용 + +- 프런트 로그인 화면을 실제 `/api/auth/login` API와 연결 +- 로그인 세션 확인용 `/api/auth/me` 추가 +- 로그아웃용 `/api/auth/logout` 추가 +- 로그인 감사로그와 세션 저장 테이블 추가 + +### 해결 방식 + +- 업무 데이터는 기존 `members` 중심으로 유지 +- 인증 데이터는 `auth.users`, `auth.sessions`, `auth.login_audit_logs` 로 분리 +- 구성원 import 시 사번 기준으로 계정을 동기화하고 기본 관리자 계정을 seed + +### 현재 한계 + +- 권한 모델은 아직 `role` 단일 컬럼 수준이다 +- API별 세부 권한 검증은 아직 미완성이다 +- `/api/mock-login` 은 아직 남아 있어 운영 기준으로는 정리가 필요하다 + +## 11. 이력형 DB 전환 방향 확정 + +### 배경 + +- 월간 스냅샷 파일보다, 사용자가 원하는 날짜 기준으로 조직도와 자리배치도를 바로 조회하는 요구가 더 중요해졌다 +- 조직도 기본 정보나 자리배치 정보처럼 원래 날짜가 없는 데이터도 과거/현재 버전 차이를 추적해야 한다 + +### 결정 + +- 월간 스냅샷 기능은 범위에서 제외 +- 대신 DB 자체를 `valid_from`, `valid_to` 기반 버전 구조로 전환 +- 사용자 조회는 파일 스냅샷이 아니라 `as_of` 기준 조회 방식으로 설계 + +### 우선 적용 대상 + +- `members` -> `member_versions` +- `seat_positions` -> `seat_assignment_versions` + +### 기대 효과 + +- 특정 날짜의 조직 상태 재구성 가능 +- 특정 날짜의 자리배치도 재구성 가능 +- 기간 비교나 변경 추적 UI로 확장 가능 + +### 설계 문서 + +- [HISTORY_ASOF_DB_PLAN.md](/home/hyunho/projects/mh-dashboard-organization/docs/HISTORY_ASOF_DB_PLAN.md) + ## Next Focus +- `#2` 영속성 운영 검증과 문서 기준 정리 +- 권한 제어와 mock login 정리 +- `#9` as-of date 기반 history 구조 설계 및 점진적 도입 +- 자리배치도 조직 트리, 나머지 사무실 도면 등 실사용 기능 고도화 - 프로젝트별 분석의 남은 소수점/분류 오차 정리 -- 자리배치도 색상/조직 트리 등 추가 UX 기능 고도화 -- 실제 인증 체계 전환 -- 나머지 사무실 도면 추가 diff --git a/docs/DEV_PROD_DB_PROTOCOL.md b/docs/DEV_PROD_DB_PROTOCOL.md index a3a5ac2..0397805 100644 --- a/docs/DEV_PROD_DB_PROTOCOL.md +++ b/docs/DEV_PROD_DB_PROTOCOL.md @@ -95,6 +95,7 @@ 1. `8080`과 `8081` 모두 기동 상태 확인 2. 이번 작업이 `코드 변경`인지 `데이터 변경`인지 먼저 구분 3. 공개용 기준 데이터가 필요한 화면이면 `8081` DB를 먼저 `8080` 기준으로 맞춤 +4. 작업 전후 검증은 [REGRESSION_CHECKLIST.md](/home/hyunho/projects/mh-dashboard-organization/docs/REGRESSION_CHECKLIST.md) 기준으로 수행 ### 2. 기능 개발 중 @@ -146,7 +147,8 @@ 2. 공개용 기준 데이터가 필요한지 판단 3. 필요하면 `8081` DB를 `8080` 기준으로 먼저 동기화 4. 그 뒤 기능 개발과 검증 수행 -5. 검증 완료 후 공개용에 코드 승격 +5. 검증은 [REGRESSION_CHECKLIST.md](/home/hyunho/projects/mh-dashboard-organization/docs/REGRESSION_CHECKLIST.md) 기준으로 수행 +6. 검증 완료 후 공개용에 코드 승격 ## 다음 액션 diff --git a/docs/NEXT_SESSION_CHECKPOINT.md b/docs/NEXT_SESSION_CHECKPOINT.md index ae1cf08..217aa06 100644 --- a/docs/NEXT_SESSION_CHECKPOINT.md +++ b/docs/NEXT_SESSION_CHECKPOINT.md @@ -6,6 +6,7 @@ - latest checked commit: `1d15cf9` - main history doc: [DEVELOPMENT_HISTORY.md](/home/hyunho/projects/mh-dashboard-organization/docs/DEVELOPMENT_HISTORY.md) - dev/prod protocol: [DEV_PROD_DB_PROTOCOL.md](/home/hyunho/projects/mh-dashboard-organization/docs/DEV_PROD_DB_PROTOCOL.md) +- regression checklist: [REGRESSION_CHECKLIST.md](/home/hyunho/projects/mh-dashboard-organization/docs/REGRESSION_CHECKLIST.md) ## What Was Finished @@ -80,6 +81,7 @@ - 데이터 정본은 `8080` DB - `8081` DB는 독립 정본이 아니라 `8080` 기준 복제본처럼 관리해야 함 - 조직도, 멤버, 자리배치 검증 전에는 `DEV_PROD_DB_PROTOCOL.md`를 먼저 확인 +- 기능 수정 후 완료 판단은 `REGRESSION_CHECKLIST.md`를 기준으로 해야 함 ### Seat Map Save @@ -165,6 +167,7 @@ - `docs/DEVELOPMENT_HISTORY.md` - `docs/NEXT_SESSION_CHECKPOINT.md` - `docs/DEV_PROD_DB_PROTOCOL.md` +- `docs/REGRESSION_CHECKLIST.md` - `docs/HISTORY_ASOF_DB_PLAN.md` - Gitea 이슈 `#2`, `#5`, `#9` diff --git a/docs/REGRESSION_CHECKLIST.md b/docs/REGRESSION_CHECKLIST.md new file mode 100644 index 0000000..71c4292 --- /dev/null +++ b/docs/REGRESSION_CHECKLIST.md @@ -0,0 +1,160 @@ +# 회귀 검증 체크리스트 + +## 목적 + +- 새 기능을 추가하거나 기존 기능을 수정할 때, 이전에 되던 핵심 기능이 깨졌는지 빠르게 확인한다. +- `8081` 작업용에서 검증한 결과를 신뢰할 수 있도록 `환경`, `데이터`, `핵심 시나리오`를 고정한다. +- 완료 판단을 감이 아니라 반복 가능한 체크 절차로 바꾼다. + +## 적용 원칙 + +- 코드 수정은 먼저 `8081`에서 수행한다. +- 데이터 기준은 항상 `8080` 공개용 DB를 따른다. +- 검증 전에는 작업 범위에 맞는 DB 동기화를 먼저 수행한다. +- 기능 수정 후에는 관련 화면만 보지 말고, 이 문서의 핵심 시나리오를 함께 확인한다. + +관련 문서: + +- [DEV_PROD_DB_PROTOCOL.md](/home/hyunho/projects/mh-dashboard-organization/docs/DEV_PROD_DB_PROTOCOL.md) +- [INFRA_VALIDATION_CHECKLIST.md](/home/hyunho/projects/mh-dashboard-organization/docs/INFRA_VALIDATION_CHECKLIST.md) + +## 작업 시작 전 + +### 1. 서버 상태 확인 + +- `8081` 작업용 접속 확인 +- `8080` 공개용 접속 확인 +- `docker compose ps`에서 `backend`, `frontend`, `proxy`, `db`가 정상인지 확인 + +### 2. 데이터 동기화 범위 결정 + +- 조직도, 관리자모드, 자리배치도 작업 전: + - `./scripts/sync_prod_db_to_dev.sh minimal` +- 프로젝트별 분석, 팀/개인별 분석 작업 전: + - `./scripts/sync_prod_db_to_dev.sh analysis` +- 공개용 기준 전체 데이터 재검증이 필요한 경우만: + - `./scripts/sync_prod_db_to_dev.sh full` + +### 3. 기준 고정 + +- 어느 서버에서 재현했는지 기록 +- 어떤 데이터 동기화 범위로 검증했는지 기록 +- 브라우저 캐시 영향을 피하려면 강력 새로고침 후 확인 + +## 공통 회귀 시나리오 + +기능 수정 후 아래 항목을 최소한 확인한다. + +### A. 허브 및 공통 진입 + +- 메인 허브가 정상 렌더링된다. +- 상단 탭 이동이 정상 동작한다. +- 로그인 상태가 비정상적으로 풀리지 않는다. + +### B. 조직현황 + +- 조직도 트리가 정상 표시된다. +- 관리자모드 진입이 가능하다. +- 대상인원 클릭 시 기본정보 모달이 열린다. +- `+` 신규 구성원 추가 모달이 열린다. +- 기본정보 저장이 정상 동작한다. + +### C. 자리배치도 + +- `기술개발센터`, `한맥빌딩 6층`, `한맥빌딩 7층` 도면이 모두 열린다. +- 미배치 인원 목록이 정상 표시된다. +- 미배치 인원을 chair에 드래그앤드롭할 수 있다. +- 드롭 후: + - 미배치 목록에서 사라진다. + - chair에 배치 상태가 표시된다. + - 이름/직급 표기가 보인다. +- 배치된 좌석 클릭 후 해제 또는 수정 흐름이 정상 동작한다. + +### D. 조직도와 자리배치 연동 + +- 조직도에서 인원 클릭 시 상세 정보가 열린다. +- 재석위치 미리보기가 표시된다. +- 좌석이 배정된 인원은 해당 자리로 줌인된다. + +### E. 프로젝트별 분석 + +- 월 선택이 정상 동작한다. +- 프로젝트 목록과 합계가 비어 있지 않다. +- `1월`, `2월` 데이터가 현재 기준값과 일치한다. + +현재 기준 검증값: + +- `2026-01` + - 시간 `37,342.39` + - 인건비 `1,391,966,625` +- `2026-02` + - 시간 `29,060.59` + - 인건비 `1,078,337,651` + +### F. 팀/개인별 분석 + +- `전체`, `GPD`, `TDC` 버튼이 순서대로 보인다. +- `전체`에서 모든 팀이 노출된다. +- `GPD`, `TDC` 선택 시 각 소속 범위만 버튼 기준으로 보인다. +- 검색은 버튼 상태와 무관하게 전체 데이터를 검색한다. + +## 작업 유형별 필수 추가 확인 + +### 조직도 / 관리자모드 수정 시 + +- 대상인원 수정 모달 레이아웃이 깨지지 않는지 확인 +- 신규 구성원 추가 모달도 같은 레이아웃으로 보이는지 확인 +- 저장 후 목록 반영이 정상인지 확인 + +### 자리배치도 수정 시 + +- viewer iframe 로드 여부 확인 +- 드래그앤드롭 이후 배치 상태가 즉시 반영되는지 확인 +- 조직도 상세 재석위치 preview까지 같이 확인 + +### 분석 로직 수정 시 + +- 작업 전에 반드시 `analysis` 또는 `full` 동기화 수행 +- 월별 합계 검증값 재확인 +- 원본 기준과 차이가 있으면 반올림, 제외 인원, 가공시간 규칙부터 점검 + +## 완료 처리 기준 + +수정 사항을 완료로 판단하려면 아래를 모두 만족해야 한다. + +- 수정한 기능이 의도대로 동작한다. +- 관련 공통 회귀 시나리오가 깨지지 않는다. +- 필요한 경우 `8081`에서 검증 결과를 숫자 또는 화면 기준으로 기록한다. +- 이후에만 `8080` 공개용 반영 여부를 판단한다. + +## 장애 원인 분류 기준 + +문제가 생기면 먼저 아래 셋 중 어디인지 분리한다. + +- 코드 차이 + - `8080`, `8081`의 정적 파일 또는 백엔드 로직이 다름 +- DB 차이 + - `members`, `seat_maps`, `integration_*` 등 기준 데이터가 다름 +- 캐시 또는 런타임 상태 + - 정적 파일 캐시, 컨테이너 재시작 미반영, 브라우저 세션 상태 문제 + +이 분류를 먼저 해야 원인을 잘못 짚지 않는다. + +## 권장 기록 방식 + +작업 종료 시 아래 형식으로 남긴다. + +```text +작업 범위: +- 예: 조직현황 관리자모드 기본정보 모달 레이아웃 변경 + +검증 환경: +- 서버: 8081 +- DB 동기화: minimal / analysis / full 중 무엇을 사용했는지 + +검증 결과: +- 조직도: 정상 +- 관리자모드 모달: 정상 +- 자리배치도 연동: 정상 또는 미검증 +- 프로젝트별 분석: 정상 또는 미검증 +``` diff --git a/frontend/public/app.js b/frontend/public/app.js index 347a968..5d34fe3 100644 --- a/frontend/public/app.js +++ b/frontend/public/app.js @@ -861,6 +861,7 @@ function renderDxfSeatMapBoard() { const viewerUrl = resolveAppUrl(`/api/seat-maps/${seatMapState.seatMap.id}/viewer`); seatMapBoard.innerHTML = `
+