diff --git a/docs/TBM_DEPLOYMENT_GUIDE.md b/docs/TBM_DEPLOYMENT_GUIDE.md index cf6745a..7c9902d 100644 --- a/docs/TBM_DEPLOYMENT_GUIDE.md +++ b/docs/TBM_DEPLOYMENT_GUIDE.md @@ -1,138 +1,132 @@ # TBM 시스템 배포 가이드 -## 📋 개요 +## 📋 문서 개요 -TBM (Tool Box Meeting) 시스템은 아침 안전 회의 및 팀 구성 관리를 위한 기능입니다. - -**배포일**: 2026-01-20 -**버전**: 1.0.0 +**목적**: TBM(Tool Box Meeting) 시스템 배포 및 사용 가이드 +**대상**: 시스템 관리자, 개발자 +**버전**: v1.2.0 +**최종 수정일**: 2026-01-26 --- -## 🗄️ 데이터베이스 마이그레이션 +## 📑 목차 -### 필수 마이그레이션 파일 +1. [시스템 개요](#시스템-개요) +2. [배포 전 확인사항](#배포-전-확인사항) +3. [배포 절차](#배포-절차) +4. [기능 명세](#기능-명세) +5. [API 명세](#api-명세) +6. [프론트엔드 구현](#프론트엔드-구현) +7. [테스트 가이드](#테스트-가이드) +8. [문제 해결](#문제-해결) +9. [변경 이력](#변경-이력) -본 서버에 배포 시 반드시 실행해야 할 마이그레이션: +--- -1. **`20260120000000_create_tbm_system.js`** - TBM 시스템 테이블 생성 -2. **`20260120000001_add_tbm_page.js`** - TBM 페이지 등록 +## 시스템 개요 -### 생성되는 테이블 +### 1.1 TBM이란? + +**TBM (Tool Box Meeting)**: 작업 시작 전 아침 안전 회의 +- 팀 구성 및 역할 분배 +- 안전 체크리스트 확인 +- 작업 내용 및 장소 공유 +- 특이사항 및 주의사항 전달 + +### 1.2 주요 기능 + +| 기능 | 설명 | 버전 | +|------|------|------| +| TBM 세션 관리 | 날짜별 TBM 생성, 수정, 완료 | v1.0.0 | +| 팀 구성 관리 | 팀장 지정, 팀원 선택, 출석 관리 | v1.0.0 | +| 안전 체크리스트 | 17개 항목 카테고리별 체크 | v1.0.0 | +| 작업 인계 | 팀장 조퇴/반차 시 인계 기능 | v1.1.0 | +| TBM 상세보기 | 세션 정보 통합 조회 | v1.1.0 | +| 작업 보고서 연동 | TBM 팀원 자동 선택 | v1.1.0 | +| 대시보드 통합 | 빠른 작업 배너 (권한 기반) | v1.2.0 | + +### 1.3 시스템 아키텍처 -#### 1. `tbm_sessions` - TBM 세션 (아침 미팅) -```sql -CREATE TABLE `tbm_sessions` ( - `session_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - `session_date` DATE NOT NULL COMMENT 'TBM 날짜', - `leader_id` INT NOT NULL COMMENT '팀장 worker_id', - `project_id` INT NULL COMMENT '프로젝트 ID', - `work_location` VARCHAR(200) NULL COMMENT '작업 장소', - `work_description` TEXT NULL COMMENT '작업 내용', - `safety_notes` TEXT NULL COMMENT '안전 관련 특이사항', - `status` ENUM('draft', 'completed', 'cancelled') DEFAULT 'draft' COMMENT '상태', - `start_time` TIME NULL COMMENT 'TBM 시작 시간', - `end_time` TIME NULL COMMENT 'TBM 종료 시간', - `created_by` INT NOT NULL COMMENT '생성자 user_id', - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_session_date_leader` (`session_date`, `leader_id`), - FOREIGN KEY (`leader_id`) REFERENCES `workers` (`worker_id`), - FOREIGN KEY (`project_id`) REFERENCES `projects` (`project_id`) ON DELETE SET NULL, - FOREIGN KEY (`created_by`) REFERENCES `users` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ``` - -#### 2. `tbm_team_assignments` - TBM 팀 구성 -```sql -CREATE TABLE `tbm_team_assignments` ( - `assignment_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - `session_id` INT UNSIGNED NOT NULL COMMENT 'TBM 세션 ID', - `worker_id` INT NOT NULL COMMENT '팀원 worker_id', - `assigned_role` VARCHAR(100) NULL COMMENT '역할/담당', - `work_detail` TEXT NULL COMMENT '세부 작업 내용', - `is_present` BOOLEAN DEFAULT TRUE COMMENT '출석 여부', - `absence_reason` TEXT NULL COMMENT '결석 사유', - `assigned_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE KEY `uk_session_worker` (`session_id`, `worker_id`), - FOREIGN KEY (`session_id`) REFERENCES `tbm_sessions` (`session_id`) ON DELETE CASCADE, - FOREIGN KEY (`worker_id`) REFERENCES `workers` (`worker_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -``` - -#### 3. `tbm_safety_checks` - 안전 체크리스트 마스터 -```sql -CREATE TABLE `tbm_safety_checks` ( - `check_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - `check_category` VARCHAR(50) NOT NULL COMMENT '카테고리 (PPE, EQUIPMENT, ENVIRONMENT, EMERGENCY)', - `check_item` VARCHAR(200) NOT NULL COMMENT '체크 항목', - `description` TEXT NULL COMMENT '설명', - `display_order` INT DEFAULT 0 COMMENT '표시 순서', - `is_required` BOOLEAN DEFAULT TRUE COMMENT '필수 체크 여부', - `is_active` BOOLEAN DEFAULT TRUE COMMENT '활성 여부', - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_check_category` (`check_category`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -``` - -**초기 데이터 (17개 항목)**: -- PPE (개인 보호 장비): 5개 -- EQUIPMENT (장비 점검): 4개 -- ENVIRONMENT (작업 환경): 4개 -- EMERGENCY (비상 대응): 3개 - -#### 4. `tbm_safety_records` - 안전 체크 기록 -```sql -CREATE TABLE `tbm_safety_records` ( - `record_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - `session_id` INT UNSIGNED NOT NULL COMMENT 'TBM 세션 ID', - `check_id` INT UNSIGNED NOT NULL COMMENT '체크 항목 ID', - `is_checked` BOOLEAN DEFAULT FALSE COMMENT '체크 여부', - `notes` TEXT NULL COMMENT '비고/특이사항', - `checked_by` INT NULL COMMENT '체크한 user_id', - `checked_at` TIMESTAMP NULL COMMENT '체크 시간', - UNIQUE KEY `uk_session_check` (`session_id`, `check_id`), - FOREIGN KEY (`session_id`) REFERENCES `tbm_sessions` (`session_id`) ON DELETE CASCADE, - FOREIGN KEY (`check_id`) REFERENCES `tbm_safety_checks` (`check_id`), - FOREIGN KEY (`checked_by`) REFERENCES `users` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -``` - -#### 5. `team_handovers` - 작업 인계 -```sql -CREATE TABLE `team_handovers` ( - `handover_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - `session_id` INT UNSIGNED NOT NULL COMMENT 'TBM 세션 ID', - `from_leader_id` INT NOT NULL COMMENT '인계자 worker_id', - `to_leader_id` INT NOT NULL COMMENT '인수자 worker_id', - `handover_date` DATE NOT NULL COMMENT '인계 날짜', - `handover_time` TIME NULL COMMENT '인계 시간', - `reason` ENUM('half_day', 'early_leave', 'emergency', 'other') NOT NULL COMMENT '인계 사유', - `handover_notes` TEXT NULL COMMENT '인계 내용', - `worker_ids` TEXT NULL COMMENT '인계하는 작업자 IDs (JSON array)', - `is_confirmed` BOOLEAN DEFAULT FALSE COMMENT '인수 확인 여부', - `confirmed_at` TIMESTAMP NULL COMMENT '인수 확인 시간', - `confirmed_by` INT NULL COMMENT '인수 확인자 user_id', - `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - INDEX `idx_session_handover_date` (`session_id`, `handover_date`), - FOREIGN KEY (`session_id`) REFERENCES `tbm_sessions` (`session_id`) ON DELETE CASCADE, - FOREIGN KEY (`from_leader_id`) REFERENCES `workers` (`worker_id`), - FOREIGN KEY (`to_leader_id`) REFERENCES `workers` (`worker_id`), - FOREIGN KEY (`confirmed_by`) REFERENCES `users` (`user_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Dashboard │────▶│ TBM Page │────▶│ Work Report │ +│ (빠른 작업) │ │ (TBM 관리) │ │ (팀원 자동선택) │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ │ │ + │ ▼ │ + │ ┌──────────────────┐ │ + └─────────────▶│ TBM API │◀─────────────┘ + │ (17 endpoints) │ + └──────────────────┘ + │ + ┌────────▼────────┐ + │ MySQL Database │ + │ (5 tables) │ + └─────────────────┘ ``` --- -## 🚀 배포 절차 +## 배포 전 확인사항 -### 1. 데이터베이스 마이그레이션 실행 +### 2.1 환경 요구사항 + +- **Node.js**: v14 이상 +- **MySQL**: v8.0 이상 +- **PM2**: 프로세스 관리 (선택) +- **데이터베이스**: `hyungi` 스키마 존재 + +### 2.2 필수 의존성 + +```json +{ + "knex": "^2.x", + "mysql2": "^3.x" +} +``` + +### 2.3 기존 시스템 확인 ```bash -# 본 서버에서 실행 -cd /path/to/api.hyungi.net +# 테이블 존재 확인 +mysql -u root -p -e "SHOW TABLES LIKE 'workers'" hyungi +mysql -u root -p -e "SHOW TABLES LIKE 'users'" hyungi +mysql -u root -p -e "SHOW TABLES LIKE 'projects'" hyungi +mysql -u root -p -e "SHOW TABLES LIKE 'pages'" hyungi +# workers.worker_id 데이터 타입 확인 (INT(11) signed 필수) +mysql -u root -p -e "DESCRIBE workers" hyungi | grep worker_id +``` + +**중요**: `worker_id`는 **signed INT(11)** 이어야 함 (unsigned 아님) + +--- + +## 배포 절차 + +### 3.1 데이터베이스 백업 (필수) + +```bash +# 배포 전 백업 +mysqldump -u root -p hyungi > backup_before_tbm_$(date +%Y%m%d_%H%M%S).sql +``` + +### 3.2 마이그레이션 실행 + +#### 3.2.1 마이그레이션 파일 확인 + +```bash +cd /path/to/api.hyungi.net +ls -l db/migrations/*tbm* +``` + +필수 파일: +- `20260120000000_create_tbm_system.js` - TBM 테이블 5개 생성 +- `20260120000001_add_tbm_page.js` - pages 테이블에 TBM 페이지 등록 + +#### 3.2.2 마이그레이션 실행 + +```bash # 환경 변수 설정 (필요 시) export DB_HOST=your_db_host export DB_PORT=your_db_port @@ -143,22 +137,42 @@ export DB_NAME=hyungi # 마이그레이션 실행 npm run db:migrate -# 또는 직접 실행 +# 또는 npx knex migrate:latest --knexfile knexfile.js ``` -### 2. 마이그레이션 확인 +#### 3.2.3 마이그레이션 확인 ```bash -# 마이그레이션 상태 확인 -npx knex migrate:status --knexfile knexfile.js +# 상태 확인 +npx knex migrate:status # 테이블 생성 확인 mysql -u root -p -e "SHOW TABLES LIKE 'tbm%'" hyungi mysql -u root -p -e "SHOW TABLES LIKE 'team_handovers'" hyungi +``` -# 안전 체크리스트 초기 데이터 확인 -mysql -u root -p -e "SELECT check_category, COUNT(*) as count FROM tbm_safety_checks GROUP BY check_category" hyungi +예상 결과: +``` ++------------------------------+ +| Tables_in_hyungi (tbm%) | ++------------------------------+ +| tbm_safety_checks | +| tbm_safety_records | +| tbm_sessions | +| tbm_team_assignments | ++------------------------------+ +``` + +#### 3.2.4 초기 데이터 확인 + +```bash +# 안전 체크리스트 17개 항목 확인 +mysql -u root -p -e " + SELECT check_category, COUNT(*) as count + FROM tbm_safety_checks + GROUP BY check_category +" hyungi ``` 예상 결과: @@ -173,7 +187,7 @@ mysql -u root -p -e "SELECT check_category, COUNT(*) as count FROM tbm_safety_ch +----------------+-------+ ``` -### 3. API 서버 재시작 +### 3.3 API 서버 재시작 ```bash # PM2 사용 시 @@ -182,12 +196,15 @@ pm2 restart api-hyungi # 또는 직접 재시작 pm2 stop api-hyungi pm2 start ecosystem.config.js --env production + +# 로그 확인 +pm2 logs api-hyungi --lines 50 ``` -### 4. 페이지 권한 확인 +### 3.4 페이지 권한 설정 ```bash -# TBM 페이지가 pages 테이블에 등록되었는지 확인 +# TBM 페이지 등록 확인 mysql -u root -p -e "SELECT * FROM pages WHERE page_key='tbm'\G" hyungi ``` @@ -203,191 +220,1029 @@ mysql -u root -p -e "SELECT * FROM pages WHERE page_key='tbm'\G" hyungi display_order: 10 ``` ---- +### 3.5 웹 서버 재시작 -## 📡 API 엔드포인트 - -### TBM 세션 관리 -- `POST /api/tbm/sessions` - TBM 세션 생성 -- `GET /api/tbm/sessions/date/:date` - 특정 날짜의 TBM 세션 목록 -- `GET /api/tbm/sessions/:sessionId` - TBM 세션 상세 조회 -- `PUT /api/tbm/sessions/:sessionId` - TBM 세션 수정 -- `POST /api/tbm/sessions/:sessionId/complete` - TBM 세션 완료 - -### 팀 구성 관리 -- `POST /api/tbm/sessions/:sessionId/team` - 팀원 추가 (단일) -- `POST /api/tbm/sessions/:sessionId/team/batch` - 팀원 일괄 추가 -- `GET /api/tbm/sessions/:sessionId/team` - 팀 구성 조회 -- `DELETE /api/tbm/sessions/:sessionId/team/:workerId` - 팀원 제거 - -### 안전 체크리스트 -- `GET /api/tbm/safety-checks` - 모든 안전 체크 항목 조회 -- `GET /api/tbm/sessions/:sessionId/safety` - 안전 체크 기록 조회 -- `POST /api/tbm/sessions/:sessionId/safety` - 안전 체크 일괄 저장 - -### 작업 인계 -- `POST /api/tbm/handovers` - 작업 인계 생성 -- `POST /api/tbm/handovers/:handoverId/confirm` - 작업 인계 확인 -- `GET /api/tbm/handovers/date/:date` - 특정 날짜의 인계 목록 -- `GET /api/tbm/handovers/pending` - 나에게 온 미확인 인계 건 - -### 통계 및 리포트 -- `GET /api/tbm/statistics/tbm?startDate=&endDate=` - TBM 통계 -- `GET /api/tbm/statistics/leaders?startDate=&endDate=` - 리더별 통계 - ---- - -## 🔐 권한 설정 - -### 1. 관리자가 페이지 권한 설정 -1. 관리자 계정으로 로그인 -2. `/pages/admin/page-access.html` 접속 -3. 권한을 부여할 사용자 선택 -4. "TBM 관리" 페이지 체크 -5. 저장 - -### 2. 기본 권한 (권장) -- **그룹장 (Leader)**: TBM 페이지 접근 권한 부여 필요 -- **관리자 (Admin)**: 자동으로 모든 페이지 접근 가능 -- **일반 작업자 (User)**: 필요에 따라 부여 - ---- - -## 📁 파일 구조 - -### 백엔드 (API) -``` -api.hyungi.net/ -├── db/migrations/ -│ ├── 20260120000000_create_tbm_system.js # TBM 테이블 생성 -│ └── 20260120000001_add_tbm_page.js # TBM 페이지 등록 -├── models/ -│ └── tbmModel.js # TBM 데이터 모델 -├── controllers/ -│ └── tbmController.js # TBM 컨트롤러 -├── routes/ -│ └── tbmRoutes.js # TBM 라우트 -└── config/ - └── routes.js # 라우트 등록 (수정됨) -``` - -### 프론트엔드 (Web UI) -``` -web-ui/ -├── pages/work/ -│ └── tbm.html # TBM 페이지 -└── js/ - └── tbm.js # TBM JavaScript (예정) -``` - ---- - -## ⚠️ 주의사항 - -### 1. 외래 키 제약 조건 -- `workers` 테이블의 `worker_id`는 **signed INT(11)** -- `users` 테이블의 `user_id`는 **signed INT(11)** -- `projects` 테이블의 PK는 `project_id` (NOT `id`) -- 외래 키 컬럼은 반드시 **signed INT**로 선언 (unsigned 사용 금지) - -### 2. 데이터 정합성 -- TBM 세션 삭제 시 관련 팀 구성, 안전 체크 기록 자동 삭제 (CASCADE) -- 작업자 삭제 시 관련 TBM 세션도 삭제됨 (workers 테이블의 CASCADE 설정) - -### 3. 백업 -마이그레이션 실행 전 **반드시 데이터베이스 백업**: ```bash -mysqldump -u root -p hyungi > backup_before_tbm_$(date +%Y%m%d_%H%M%S).sql +# Nginx 재시작 (정적 파일 갱신) +docker restart tkfb_web + +# 또는 +sudo systemctl restart nginx ``` -### 4. 롤백 -문제 발생 시 롤백: -```bash -# 한 단계 롤백 -npx knex migrate:rollback --knexfile knexfile.js +### 3.6 배포 체크리스트 -# 또는 백업 복구 -mysql -u root -p hyungi < backup_before_tbm_YYYYMMDD_HHMMSS.sql -``` - ---- - -## ✅ 배포 체크리스트 - -배포 전 확인: - [ ] 데이터베이스 백업 완료 -- [ ] 마이그레이션 파일 본 서버에 복사 완료 -- [ ] 환경 변수 설정 확인 (DB 접속 정보) - [ ] 마이그레이션 실행 완료 - [ ] 5개 테이블 생성 확인 - [ ] tbm_safety_checks 초기 데이터 (17개) 확인 - [ ] pages 테이블에 TBM 페이지 등록 확인 - [ ] API 서버 재시작 완료 +- [ ] 웹 서버 재시작 완료 - [ ] API 엔드포인트 테스트 (최소 1개) - [ ] TBM 페이지 접속 테스트 - [ ] 권한 설정 테스트 (그룹장 계정) +- [ ] 대시보드 빠른 작업 표시 확인 --- -## 🔍 테스트 방법 +## 기능 명세 -### 1. API 테스트 -```bash -# 안전 체크 항목 조회 -curl -X GET http://localhost:20005/api/tbm/safety-checks \ - -H "Authorization: Bearer YOUR_TOKEN" +### 4.1 TBM 세션 관리 -# 오늘 날짜의 TBM 세션 조회 -curl -X GET http://localhost:20005/api/tbm/sessions/date/2026-01-20 \ - -H "Authorization: Bearer YOUR_TOKEN" +**목적**: 날짜별 아침 안전 회의 생성 및 관리 + +**주요 기능**: +- TBM 세션 생성 (날짜, 팀장, 프로젝트, 장소, 작업 내용, 안전 특이사항) +- 시작 시간/종료 시간 기록 +- 상태 관리 (draft, completed, cancelled) +- 세션 수정 및 삭제 + +**사용자 시나리오**: +1. 팀장이 매일 아침 TBM 페이지 접속 +2. "새 TBM 시작" 클릭 +3. 오늘 날짜, 프로젝트, 작업 장소, 작업 내용 입력 +4. 안전 특이사항 기록 (선택) +5. "저장 및 팀 구성하기" 클릭 + +### 4.2 팀 구성 관리 + +**목적**: 작업 팀원 선택 및 역할 분배 + +**주요 기능**: +- 다중 선택으로 팀원 지정 +- 각 팀원의 역할/담당 업무 기록 (선택) +- 출석/결석 처리 +- 결석 사유 기록 + +**사용자 시나리오**: +1. TBM 세션 저장 후 팀 구성 모달 자동 표시 +2. 체크박스로 팀원 선택 (전체 선택/해제 버튼 제공) +3. 선택된 팀원 확인 (N명 표시) +4. "팀 구성 완료" 클릭 + +**작업 보고서 연동**: +- 작업 보고서 페이지에서 TBM 팀원이 자동으로 선택됨 +- 안내 메시지: "오늘 TBM에서 구성된 팀원 N명이 자동으로 선택되었습니다." + +### 4.3 안전 체크리스트 + +**목적**: 작업 전 안전 점검 항목 확인 + +**카테고리 및 항목** (총 17개): + +| 카테고리 | 항목 수 | 예시 | +|---------|---------|------| +| PPE (개인 보호 장비) | 5 | 안전모, 안전화, 안전벨트, 보호안경, 안전장갑 | +| EQUIPMENT (장비 점검) | 4 | 공구 점검, 전기 장비, 사다리/비계, 안전 표지판 | +| ENVIRONMENT (작업 환경) | 4 | 작업 구역 정리, 위험물 확인, 환기, 조명 | +| EMERGENCY (비상 대응) | 3 | 비상 연락망, 응급 처치함, 대피 경로 | + +**주요 기능**: +- 카테고리별 체크리스트 표시 +- 각 항목 체크 (is_checked: true/false) +- 특이사항/비고 입력 (선택) +- 체크한 사람 및 시간 기록 + +**사용자 시나리오**: +1. TBM 세션 카드에서 "안전 체크" 버튼 클릭 +2. 카테고리별로 항목 확인 +3. 각 항목 체크 및 특이사항 입력 +4. "안전 체크 완료" 클릭 + +### 4.4 작업 인계 시스템 + +**목적**: 팀장 조퇴/반차 시 작업 인계 + +**인계 사유**: +- `half_day`: 반차 +- `early_leave`: 조퇴 +- `emergency`: 긴급 상황 +- `other`: 기타 + +**주요 기능**: +- 인수자(다음 팀장) 선택 +- 인계할 팀원 선택 (기본: 전체 선택) +- 인계 날짜/시간 기록 +- 인계 내용 작성 +- 인수자 확인 프로세스 + +**사용자 시나리오**: +1. TBM 세션 카드에서 "인계" 버튼 클릭 +2. 인계 사유 선택 (반차, 조퇴, 긴급, 기타) +3. 인수자(다음 팀장) 선택 +4. 인계할 팀원 선택 (체크박스) +5. 인계 내용 입력 (특이사항, 주의사항 등) +6. "인계 요청" 클릭 +7. 인수자가 확인 후 "인수 확인" 처리 + +### 4.5 TBM 상세보기 + +**목적**: TBM 세션의 모든 정보를 한눈에 확인 + +**표시 정보**: +- **기본 정보**: 날짜, 팀장, 프로젝트, 작업 장소, 작업 내용, 안전 특이사항, 시작/종료 시간 +- **팀 구성**: 팀원 목록 (이름, 역할, 출석 여부) +- **안전 체크**: 카테고리별 체크리스트 상태 (체크됨/안됨, 특이사항) + +**성능 최적화**: +```javascript +// 병렬 API 호출로 로딩 속도 개선 +const [sessionRes, teamRes, safetyRes] = await Promise.all([ + window.apiCall(`/tbm/sessions/${sessionId}`), + window.apiCall(`/tbm/sessions/${sessionId}/team`), + window.apiCall(`/tbm/sessions/${sessionId}/safety`) +]); ``` -### 2. 웹 페이지 테스트 -1. 그룹장 계정으로 로그인 -2. `/pages/work/tbm.html` 접속 -3. "새 TBM 시작" 버튼 클릭 -4. TBM 세션 생성 및 팀 구성 +### 4.6 대시보드 빠른 작업 + +**목적**: 대시보드에서 TBM 페이지로 빠른 접근 + +**주요 특징**: +- **권한 기반 표시**: 페이지 접근 권한이 있는 사용자만 표시 +- **시각적 구분**: 오렌지 그라데이션 배경 (`#f59e0b` → `#d97706`) +- **동적 권한 체크**: 로그인 시 자동으로 권한 확인 + +**권한 체크 로직**: +```javascript +async function checkTbmPageAccess() { + const response = await window.apiCall(`/users/${currentUser.user_id}/page-access`); + const tbmPage = response.data.pageAccess.find(p => p.page_key === 'tbm'); + if (tbmPage && tbmPage.can_access) { + document.getElementById('tbmQuickAction').style.display = 'block'; + } +} +``` --- -## 📞 문제 해결 +## API 명세 -### 문제 1: 마이그레이션 실패 (errno: 150) -**원인**: 외래 키 제약 조건 오류 (데이터 타입 불일치) -**해결**: 마이그레이션 파일에서 외래 키 컬럼을 signed INT로 수정 +### 5.1 TBM 세션 관리 + +#### POST /api/tbm/sessions +**설명**: TBM 세션 생성 +**요청 본문**: +```json +{ + "session_date": "2026-01-26", + "leader_id": 1, + "project_id": 5, + "work_location": "현장 A동", + "work_description": "배관 설치 작업", + "safety_notes": "고소 작업 주의", + "start_time": "08:00:00" +} +``` +**응답**: +```json +{ + "success": true, + "message": "TBM 세션이 생성되었습니다", + "data": { "session_id": 123 } +} +``` + +#### GET /api/tbm/sessions/date/:date +**설명**: 특정 날짜의 TBM 세션 목록 조회 +**예시**: `GET /api/tbm/sessions/date/2026-01-26` +**응답**: +```json +{ + "success": true, + "data": [ + { + "session_id": 123, + "session_date": "2026-01-26", + "leader_name": "김팀장", + "project_name": "아파트 건설", + "status": "draft" + } + ] +} +``` + +#### GET /api/tbm/sessions/:sessionId +**설명**: TBM 세션 상세 조회 +**예시**: `GET /api/tbm/sessions/123` + +#### PUT /api/tbm/sessions/:sessionId +**설명**: TBM 세션 수정 + +#### POST /api/tbm/sessions/:sessionId/complete +**설명**: TBM 세션 완료 처리 +**요청 본문**: +```json +{ + "end_time": "17:00:00" +} +``` + +### 5.2 팀 구성 관리 + +#### POST /api/tbm/sessions/:sessionId/team/batch +**설명**: 팀원 일괄 추가 +**요청 본문**: +```json +{ + "workers": [ + { "worker_id": 10, "assigned_role": "용접" }, + { "worker_id": 11, "assigned_role": "보조" } + ] +} +``` + +#### GET /api/tbm/sessions/:sessionId/team +**설명**: 팀 구성 조회 + +#### DELETE /api/tbm/sessions/:sessionId/team/:workerId +**설명**: 팀원 제거 + +### 5.3 안전 체크리스트 + +#### GET /api/tbm/safety-checks +**설명**: 모든 안전 체크 항목 조회 (17개) + +#### GET /api/tbm/sessions/:sessionId/safety +**설명**: 특정 세션의 안전 체크 기록 조회 + +#### POST /api/tbm/sessions/:sessionId/safety +**설명**: 안전 체크 일괄 저장 +**요청 본문**: +```json +{ + "checks": [ + { "check_id": 1, "is_checked": true, "notes": "" }, + { "check_id": 2, "is_checked": true, "notes": "안전화 교체 필요" } + ] +} +``` + +### 5.4 작업 인계 + +#### POST /api/tbm/handovers +**설명**: 작업 인계 생성 +**요청 본문**: +```json +{ + "session_id": 123, + "from_leader_id": 1, + "to_leader_id": 2, + "handover_date": "2026-01-26", + "handover_time": "15:00:00", + "reason": "early_leave", + "handover_notes": "3시 조퇴로 인계합니다", + "worker_ids": [10, 11, 12] +} +``` + +#### POST /api/tbm/handovers/:handoverId/confirm +**설명**: 작업 인계 확인 (인수자가 확인) + +#### GET /api/tbm/handovers/pending +**설명**: 나에게 온 미확인 인계 건 + +### 5.5 통계 및 리포트 + +#### GET /api/tbm/statistics/tbm +**설명**: TBM 통계 +**쿼리 파라미터**: `startDate`, `endDate` +**예시**: `GET /api/tbm/statistics/tbm?startDate=2026-01-01&endDate=2026-01-31` + +#### GET /api/tbm/statistics/leaders +**설명**: 리더별 TBM 통계 + +--- + +## 프론트엔드 구현 + +### 6.1 파일 구조 + +``` +web-ui/ +├── pages/ +│ ├── dashboard.html # 대시보드 (TBM 빠른 작업 추가됨) +│ └── work/ +│ └── tbm.html # TBM 페이지 +├── js/ +│ ├── tbm.js # TBM JavaScript 로직 +│ ├── modern-dashboard.js # 대시보드 (TBM 권한 체크 추가됨) +│ └── daily-work-report.js # 작업 보고서 (TBM 연동 추가됨) +└── css/ + └── project-management.css # TBM 페이지 스타일 +``` + +### 6.2 주요 함수 + +#### tbm.js + +```javascript +// TBM 세션 목록 표시 +async function loadTbmSessions(date) { + const response = await window.apiCall(`/tbm/sessions/date/${date}`); + displayTbmSessions(response.data); +} + +// TBM 세션 저장 +async function saveTbmSession() { + const sessionData = { + session_date: document.getElementById('sessionDate').value, + leader_id: document.getElementById('leaderId').value, + // ... + }; + const response = await window.apiCall('/tbm/sessions', 'POST', sessionData); + if (response.success) { + openTeamModal(response.data.session_id); + } +} + +// 팀 구성 저장 +async function saveTeamComposition() { + const selectedWorkers = Array.from( + document.querySelectorAll('input[name="worker"]:checked') + ).map(cb => ({ worker_id: parseInt(cb.value) })); + + await window.apiCall( + `/tbm/sessions/${currentSessionId}/team/batch`, + 'POST', + { workers: selectedWorkers } + ); +} + +// 안전 체크리스트 저장 +async function saveSafetyChecklist() { + const checks = []; + document.querySelectorAll('.safety-check-item').forEach(item => { + checks.push({ + check_id: item.dataset.checkId, + is_checked: item.querySelector('input[type="checkbox"]').checked, + notes: item.querySelector('textarea')?.value || '' + }); + }); + + await window.apiCall( + `/tbm/sessions/${currentSessionId}/safety`, + 'POST', + { checks } + ); +} + +// TBM 상세보기 (병렬 API 호출) +async function viewTbmSession(sessionId) { + const [sessionRes, teamRes, safetyRes] = await Promise.all([ + window.apiCall(`/tbm/sessions/${sessionId}`), + window.apiCall(`/tbm/sessions/${sessionId}/team`), + window.apiCall(`/tbm/sessions/${sessionId}/safety`) + ]); + + // 데이터 표시 + displaySessionDetail(sessionRes.data, teamRes.data, safetyRes.data); +} + +// 작업 인계 +async function saveHandover() { + const handoverData = { + session_id: currentSessionId, + from_leader_id: fromLeaderId, + to_leader_id: document.getElementById('toLeaderId').value, + handover_date: document.getElementById('handoverDate').value, + handover_time: document.getElementById('handoverTime').value, + reason: document.getElementById('handoverReason').value, + handover_notes: document.getElementById('handoverNotes').value, + worker_ids: Array.from( + document.querySelectorAll('input[name="handoverWorker"]:checked') + ).map(cb => parseInt(cb.value)) + }; + + await window.apiCall('/tbm/handovers', 'POST', handoverData); +} +``` + +#### daily-work-report.js + +```javascript +// TBM 팀 구성 자동 로드 +async function loadTbmTeamForDate(date) { + try { + const response = await window.apiCall(`/tbm/sessions/date/${date}`); + + if (response && response.success && response.data && response.data.length > 0) { + // Draft 세션 우선 선택 + const draftSessions = response.data.filter(s => s.status === 'draft'); + const targetSession = draftSessions.length > 0 + ? draftSessions[0] + : response.data[0]; + + if (targetSession) { + const teamRes = await window.apiCall( + `/tbm/sessions/${targetSession.session_id}/team` + ); + + if (teamRes && teamRes.success && teamRes.data) { + const teamWorkerIds = teamRes.data.map(m => m.worker_id); + console.log(`✅ TBM 팀 구성 로드 성공: ${teamWorkerIds.length}명`); + return teamWorkerIds; + } + } + } + + return []; + } catch (error) { + console.error('❌ TBM 팀 구성 조회 오류:', error); + return []; + } +} + +// 작업자 그리드 생성 (TBM 팀원 자동 선택) +async function populateWorkerGrid() { + const grid = document.getElementById('workerGrid'); + grid.innerHTML = ''; + + // TBM 팀 구성 로드 + const reportDate = document.getElementById('reportDate').value; + let tbmWorkerIds = []; + + if (reportDate) { + tbmWorkerIds = await loadTbmTeamForDate(reportDate); + } + + // 안내 메시지 표시 + if (tbmWorkerIds.length > 0) { + const infoDiv = document.createElement('div'); + infoDiv.className = 'tbm-auto-select-info'; + infoDiv.innerHTML = ` + 🛠️ TBM 팀 구성 자동 적용 + 오늘 TBM에서 구성된 팀원 ${tbmWorkerIds.length}명이 자동으로 선택되었습니다. + `; + grid.appendChild(infoDiv); + } + + // 작업자 버튼 생성 + workers.forEach(worker => { + const btn = document.createElement('button'); + btn.className = 'worker-btn'; + btn.textContent = worker.worker_name; + + // TBM 팀원 자동 선택 + if (tbmWorkerIds.includes(worker.worker_id)) { + btn.classList.add('selected'); + selectedWorkers.add(worker.worker_id); + } + + btn.onclick = () => toggleWorker(worker.worker_id, btn); + grid.appendChild(btn); + }); +} +``` + +#### modern-dashboard.js + +```javascript +// TBM 페이지 접근 권한 확인 +async function checkTbmPageAccess() { + try { + if (!currentUser || !currentUser.user_id) { + console.log('⚠️ TBM 페이지 권한 확인: 사용자 정보 없음'); + return; + } + + console.log('🛠️ TBM 페이지 권한 확인 중...'); + + // 사용자의 페이지 접근 권한 조회 + const response = await window.apiCall( + `/users/${currentUser.user_id}/page-access` + ); + + if (response && response.success) { + const pageAccess = response.data?.pageAccess || []; + + // 'tbm' 페이지 접근 권한 확인 + const tbmPage = pageAccess.find(p => p.page_key === 'tbm'); + const tbmQuickAction = document.getElementById('tbmQuickAction'); + + if (tbmPage && tbmPage.can_access && tbmQuickAction) { + tbmQuickAction.style.display = 'block'; + console.log('✅ TBM 페이지 접근 권한 있음 - 빠른 작업 버튼 표시'); + } else { + console.log('❌ TBM 페이지 접근 권한 없음 - 빠른 작업 버튼 숨김'); + } + } + } catch (error) { + console.error('❌ TBM 페이지 권한 확인 오류:', error); + } +} + +// 대시보드 초기화 시 호출 +async function initializeDashboard() { + // ... + await loadDashboardData(); + checkAdminAccess(); + checkTbmPageAccess(); // TBM 권한 체크 추가 +} +``` + +### 6.3 CSS 스타일 + +#### dashboard.html - TBM 빠른 작업 카드 + +```html + + + 🛠️ TBM 관리 + + 아침 안전 회의 및 팀 구성을 관리합니다 + + + → + +``` + +**스타일 특징**: +- `display: none`: 기본 숨김 (권한 확인 후 표시) +- `background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%)`: 오렌지 그라데이션 +- `color: white`: 흰색 텍스트 (가독성) + +--- + +## 테스트 가이드 + +### 7.1 API 테스트 + +```bash +# 환경 변수 설정 (토큰) +export TOKEN="your_jwt_token_here" + +# 1. 안전 체크 항목 조회 +curl -X GET "http://localhost:20005/api/tbm/safety-checks" \ + -H "Authorization: Bearer $TOKEN" + +# 2. 오늘 날짜의 TBM 세션 조회 +curl -X GET "http://localhost:20005/api/tbm/sessions/date/$(date +%Y-%m-%d)" \ + -H "Authorization: Bearer $TOKEN" + +# 3. TBM 세션 생성 +curl -X POST "http://localhost:20005/api/tbm/sessions" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "session_date": "2026-01-26", + "leader_id": 1, + "project_id": 5, + "work_location": "현장 A동", + "work_description": "배관 설치 작업" + }' +``` + +### 7.2 웹 페이지 테스트 + +#### 7.2.1 권한 확인 테스트 +1. 관리자 계정으로 로그인 +2. `/pages/admin/page-access.html` 접속 +3. 그룹장 계정에 "TBM 관리" 페이지 권한 부여 +4. 그룹장 계정으로 재로그인 +5. 대시보드에서 "TBM 관리" 빠른 작업 카드 확인 + +#### 7.2.2 TBM 세션 생성 테스트 +1. TBM 페이지 접속 (`/pages/work/tbm.html`) +2. "새 TBM 시작" 클릭 +3. 폼 입력: + - 날짜: 오늘 + - 팀장: 김그룹장 + - 프로젝트: 아파트 건설 + - 작업 장소: A동 5층 + - 작업 내용: 배관 설치 +4. "저장 및 팀 구성하기" 클릭 +5. 팀 구성 모달에서 팀원 3명 선택 +6. "팀 구성 완료" 클릭 +7. TBM 세션 카드가 목록에 표시되는지 확인 + +#### 7.2.3 안전 체크리스트 테스트 +1. TBM 세션 카드에서 "안전 체크" 버튼 클릭 +2. PPE 카테고리 항목 5개 모두 체크 +3. EQUIPMENT 카테고리에서 "전기 장비 점검" 체크 후 특이사항 입력: "전압 확인 필요" +4. "안전 체크 완료" 클릭 +5. 세션 카드에서 안전 체크 완료 표시 확인 + +#### 7.2.4 작업 인계 테스트 +1. TBM 세션 카드에서 "인계" 버튼 클릭 +2. 인계 사유: "조퇴" 선택 +3. 인수자: 이그룹장 선택 +4. 팀원 2명 선택 (기본: 전체 선택됨) +5. 인계 내용: "3시 조퇴로 인계합니다. 배관 작업 마무리 부탁드립니다." +6. "인계 요청" 클릭 +7. 인계 완료 메시지 확인 + +#### 7.2.5 작업 보고서 연동 테스트 +1. TBM 세션 생성 및 팀 구성 완료 (팀원 5명) +2. "작업 보고서 작성" 페이지 이동 +3. 보고 날짜: TBM 세션과 동일한 날짜 선택 +4. "다음" 버튼 클릭 (작업자 선택 단계) +5. 확인 사항: + - TBM 팀원 5명이 자동으로 선택되어 있음 + - 안내 메시지: "오늘 TBM에서 구성된 팀원 5명이 자동으로 선택되었습니다." +6. "다음" 버튼이 활성화되어 있는지 확인 + +#### 7.2.6 TBM 상세보기 테스트 +1. TBM 세션 카드 클릭 +2. 상세보기 모달 확인: + - 기본 정보: 날짜, 팀장, 프로젝트, 장소, 작업 내용 + - 팀 구성: 팀원 목록 및 역할 + - 안전 체크: 카테고리별 체크 상태 (✅/❌) +3. 모달 닫기 + +### 7.3 성능 테스트 + +```javascript +// 브라우저 개발자 도구 콘솔에서 실행 + +// TBM 상세보기 로딩 시간 측정 +console.time('TBM Detail Load'); +await viewTbmSession(123); +console.timeEnd('TBM Detail Load'); +// 예상: < 500ms (병렬 API 호출로 최적화) + +// 작업 보고서 팀원 자동 선택 시간 측정 +console.time('TBM Team Auto-select'); +await loadTbmTeamForDate('2026-01-26'); +console.timeEnd('TBM Team Auto-select'); +// 예상: < 300ms +``` + +--- + +## 문제 해결 + +### 8.1 마이그레이션 오류 + +#### 오류: errno: 150 (외래 키 제약 조건 오류) + +**증상**: +``` +Error: ER_CANNOT_ADD_FOREIGN: Cannot add foreign key constraint +errno: 150 +``` + +**원인**: 외래 키 컬럼과 참조 테이블의 PK 데이터 타입 불일치 + +**해결**: +1. 참조 테이블의 PK 데이터 타입 확인: +```sql +DESCRIBE workers; -- worker_id는 INT(11) signed +DESCRIBE users; -- user_id는 INT(11) signed +``` + +2. 마이그레이션 파일에서 외래 키 컬럼을 **signed INT**로 수정: +```javascript +table.integer('leader_id').notNullable(); // unsigned 제거 +``` + +3. 마이그레이션 재실행: +```bash +npx knex migrate:rollback +npx knex migrate:latest +``` + +#### 오류: 테이블 이미 존재 + +**증상**: +``` +Error: Table 'tbm_sessions' already exists +``` -### 문제 2: API 엔드포인트 404 -**원인**: 라우트 등록 누락 또는 서버 미재시작 **해결**: ```bash -pm2 logs api-hyungi --lines 50 # 로그 확인 -pm2 restart api-hyungi # 서버 재시작 +# 마이그레이션 상태 확인 +npx knex migrate:status + +# 이미 실행된 마이그레이션이면 스킵 +# 테이블 수동 삭제 후 재실행 (주의: 데이터 손실) +mysql -u root -p -e "DROP TABLE IF EXISTS tbm_safety_records, tbm_team_assignments, tbm_sessions, tbm_safety_checks, team_handovers" hyungi +npx knex migrate:latest ``` -### 문제 3: TBM 페이지 접근 불가 -**원인**: 페이지 권한 미설정 -**해결**: 관리자가 `/pages/admin/page-access.html`에서 권한 부여 +### 8.2 API 오류 + +#### 오류: 404 Not Found + +**증상**: +``` +GET /api/tbm/sessions/date/2026-01-26 404 +``` + +**원인**: 라우트 등록 누락 또는 서버 미재시작 + +**해결**: +1. 라우트 등록 확인: +```javascript +// api.hyungi.net/config/routes.js +const tbmRoutes = require('../routes/tbmRoutes'); +app.use('/api/tbm', tbmRoutes); +``` + +2. 서버 재시작: +```bash +pm2 restart api-hyungi +pm2 logs api-hyungi --lines 50 +``` + +#### 오류: 401 Unauthorized + +**증상**: +```json +{ "message": "인증 토큰이 필요합니다" } +``` + +**원인**: JWT 토큰 누락 또는 만료 + +**해결**: +1. 로그인 후 새 토큰 발급 +2. 요청 헤더에 토큰 포함: +```javascript +headers: { + 'Authorization': `Bearer ${token}` +} +``` + +### 8.3 프론트엔드 오류 + +#### 오류: TBM 빠른 작업 카드가 표시되지 않음 + +**증상**: 권한이 있는 사용자인데도 대시보드에 TBM 카드가 보이지 않음 + +**원인**: +1. 페이지 권한 미설정 +2. JavaScript 오류로 `checkTbmPageAccess()` 미실행 +3. API 응답 구조 변경 + +**해결**: +1. 페이지 권한 확인: +```sql +SELECT u.username, pa.page_key, pa.can_access +FROM users u +LEFT JOIN user_page_access pa ON u.user_id = pa.user_id +LEFT JOIN pages p ON pa.page_id = p.page_id +WHERE u.user_id = 1 AND p.page_key = 'tbm'; +``` + +2. 브라우저 개발자 도구 콘솔 확인: +```javascript +// 콘솔에 "✅ TBM 페이지 접근 권한 있음" 메시지가 있어야 함 +// 오류가 있다면 확인 +``` + +3. API 응답 확인: +```bash +curl -X GET "http://localhost:20005/api/users/1/page-access" \ + -H "Authorization: Bearer $TOKEN" +``` + +4. 캐시 삭제 및 강력 새로고침 (Ctrl+Shift+R / Cmd+Shift+R) + +#### 오류: TBM 팀원이 작업 보고서에 자동 선택되지 않음 + +**증상**: TBM 세션을 만들었는데 작업 보고서에서 팀원이 자동 선택되지 않음 + +**원인**: +1. TBM 세션 날짜와 작업 보고서 날짜 불일치 +2. TBM 세션이 completed 상태 (draft 세션만 자동 선택됨) +3. API 호출 오류 + +**해결**: +1. 날짜 일치 확인: +```javascript +// 콘솔에서 확인 +const reportDate = document.getElementById('reportDate').value; +console.log('보고서 날짜:', reportDate); +``` + +2. TBM 세션 상태 확인: +```sql +SELECT session_id, session_date, status +FROM tbm_sessions +WHERE session_date = '2026-01-26'; +``` + +3. 콘솔 로그 확인: +```javascript +// "✅ TBM 팀 구성 로드 성공: N명" 메시지가 있어야 함 +// "❌ TBM 팀 구성 조회 오류" 메시지가 있다면 API 오류 +``` + +### 8.4 권한 문제 + +#### 오류: TBM 페이지 접근 불가 (403 Forbidden) + +**증상**: TBM 페이지 접속 시 권한 없음 메시지 + +**원인**: 사용자에게 TBM 페이지 접근 권한 미부여 + +**해결**: +1. 관리자 계정으로 로그인 +2. `/pages/admin/page-access.html` 접속 +3. 권한 부여할 사용자 선택 +4. "TBM 관리" 페이지 체크 +5. "저장" 클릭 +6. 해당 사용자 재로그인 + +또는 SQL로 직접 권한 부여: +```sql +-- TBM 페이지 ID 확인 +SELECT page_id FROM pages WHERE page_key = 'tbm'; +-- 예: page_id = 15 + +-- 사용자에게 권한 부여 +INSERT INTO user_page_access (user_id, page_id, can_access) +VALUES (3, 15, 1) +ON DUPLICATE KEY UPDATE can_access = 1; +``` + +### 8.5 데이터베이스 문제 + +#### 오류: 안전 체크리스트 항목이 17개가 아님 + +**증상**: `SELECT COUNT(*) FROM tbm_safety_checks` 결과가 17이 아님 + +**원인**: 초기 데이터 삽입 실패 + +**해결**: +```bash +# 마이그레이션 롤백 후 재실행 +npx knex migrate:rollback --step=1 +npx knex migrate:latest + +# 또는 수동으로 데이터 삽입 +mysql -u root -p hyungi < api.hyungi.net/db/migrations/20260120000000_create_tbm_system.js +``` --- -## 📝 변경 이력 +## 변경 이력 + +### v1.2.0 (2026-01-26) +**기능 추가**: +- 대시보드 TBM 빠른 작업 배너 추가 +- 페이지 접근 권한 기반 표시/숨김 처리 +- `checkTbmPageAccess()` 함수 추가 (`modern-dashboard.js`) +- 오렌지 그라데이션 스타일링 (`#f59e0b` → `#d97706`) + +**커밋**: `67e9c08` + +### v1.1.0 (2026-01-26) +**기능 추가**: +- **작업 인계 시스템** + - 인계 모달 추가 (`handoverModal`) + - 인계 사유 선택 (반차, 조퇴, 긴급, 기타) + - 인수자 선택 및 팀원 인계 + - `openHandoverModal()`, `saveHandover()` 함수 추가 +- **TBM 상세보기** + - 상세보기 모달 추가 (`detailModal`) + - 병렬 API 호출로 성능 최적화 (`Promise.all()`) + - 카테고리별 안전 체크리스트 표시 + - `viewTbmSession()` 함수 추가 +- **작업 보고서 연동** + - TBM 팀원 자동 선택 기능 + - `loadTbmTeamForDate()` 함수 추가 (`daily-work-report.js`) + - Draft 세션 우선 처리 + - 자동 선택 안내 메시지 표시 + +**커밋**: `4802069`, `f813868` ### v1.0.0 (2026-01-20) -- TBM 시스템 초기 배포 -- 5개 테이블 생성 -- 17개 안전 체크리스트 항목 -- API 엔드포인트 17개 +**초기 배포**: +- 데이터베이스 스키마 5개 테이블 생성 + - `tbm_sessions`, `tbm_team_assignments`, `tbm_safety_checks`, `tbm_safety_records`, `team_handovers` +- 안전 체크리스트 초기 데이터 17개 항목 +- API 엔드포인트 17개 구현 +- TBM 페이지 (`/pages/work/tbm.html`) - 페이지 권한 시스템 연동 +- TBM JavaScript 기본 로직 (`tbm.js`) + +**커밋**: `4d0c4c0` --- -## 📚 관련 문서 +## 관련 문서 - [작업자-계정 연동 가이드](./worker-account-integration.md) - [페이지 권한 관리 가이드](./page-access-management.md) - [데이터베이스 스키마](./database-schema.md) +- [API 문서](./api-documentation.md) + +--- + +## 데이터베이스 스키마 + +### tbm_sessions +```sql +CREATE TABLE `tbm_sessions` ( + `session_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + `session_date` DATE NOT NULL COMMENT 'TBM 날짜', + `leader_id` INT NOT NULL COMMENT '팀장 worker_id', + `project_id` INT NULL COMMENT '프로젝트 ID', + `work_location` VARCHAR(200) NULL COMMENT '작업 장소', + `work_description` TEXT NULL COMMENT '작업 내용', + `safety_notes` TEXT NULL COMMENT '안전 관련 특이사항', + `status` ENUM('draft', 'completed', 'cancelled') DEFAULT 'draft', + `start_time` TIME NULL, + `end_time` TIME NULL, + `created_by` INT NOT NULL COMMENT '생성자 user_id', + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX `idx_session_date_leader` (`session_date`, `leader_id`), + FOREIGN KEY (`leader_id`) REFERENCES `workers` (`worker_id`), + FOREIGN KEY (`project_id`) REFERENCES `projects` (`project_id`) ON DELETE SET NULL, + FOREIGN KEY (`created_by`) REFERENCES `users` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### tbm_team_assignments +```sql +CREATE TABLE `tbm_team_assignments` ( + `assignment_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + `session_id` INT UNSIGNED NOT NULL, + `worker_id` INT NOT NULL, + `assigned_role` VARCHAR(100) NULL, + `work_detail` TEXT NULL, + `is_present` BOOLEAN DEFAULT TRUE, + `absence_reason` TEXT NULL, + `assigned_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `uk_session_worker` (`session_id`, `worker_id`), + FOREIGN KEY (`session_id`) REFERENCES `tbm_sessions` (`session_id`) ON DELETE CASCADE, + FOREIGN KEY (`worker_id`) REFERENCES `workers` (`worker_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### tbm_safety_checks +```sql +CREATE TABLE `tbm_safety_checks` ( + `check_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + `check_category` VARCHAR(50) NOT NULL COMMENT 'PPE, EQUIPMENT, ENVIRONMENT, EMERGENCY', + `check_item` VARCHAR(200) NOT NULL, + `description` TEXT NULL, + `display_order` INT DEFAULT 0, + `is_required` BOOLEAN DEFAULT TRUE, + `is_active` BOOLEAN DEFAULT TRUE, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX `idx_check_category` (`check_category`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +**초기 데이터 (17개)**: +- PPE: 안전모, 안전화, 안전벨트, 보호안경, 안전장갑 +- EQUIPMENT: 공구 점검, 전기 장비, 사다리/비계, 안전 표지판 +- ENVIRONMENT: 작업 구역 정리, 위험물 확인, 환기, 조명 +- EMERGENCY: 비상 연락망, 응급 처치함, 대피 경로 + +### tbm_safety_records +```sql +CREATE TABLE `tbm_safety_records` ( + `record_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + `session_id` INT UNSIGNED NOT NULL, + `check_id` INT UNSIGNED NOT NULL, + `is_checked` BOOLEAN DEFAULT FALSE, + `notes` TEXT NULL, + `checked_by` INT NULL, + `checked_at` TIMESTAMP NULL, + UNIQUE KEY `uk_session_check` (`session_id`, `check_id`), + FOREIGN KEY (`session_id`) REFERENCES `tbm_sessions` (`session_id`) ON DELETE CASCADE, + FOREIGN KEY (`check_id`) REFERENCES `tbm_safety_checks` (`check_id`), + FOREIGN KEY (`checked_by`) REFERENCES `users` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` + +### team_handovers +```sql +CREATE TABLE `team_handovers` ( + `handover_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + `session_id` INT UNSIGNED NOT NULL, + `from_leader_id` INT NOT NULL, + `to_leader_id` INT NOT NULL, + `handover_date` DATE NOT NULL, + `handover_time` TIME NULL, + `reason` ENUM('half_day', 'early_leave', 'emergency', 'other') NOT NULL, + `handover_notes` TEXT NULL, + `worker_ids` TEXT NULL COMMENT 'JSON array', + `is_confirmed` BOOLEAN DEFAULT FALSE, + `confirmed_at` TIMESTAMP NULL, + `confirmed_by` INT NULL, + `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX `idx_session_handover_date` (`session_id`, `handover_date`), + FOREIGN KEY (`session_id`) REFERENCES `tbm_sessions` (`session_id`) ON DELETE CASCADE, + FOREIGN KEY (`from_leader_id`) REFERENCES `workers` (`worker_id`), + FOREIGN KEY (`to_leader_id`) REFERENCES `workers` (`worker_id`), + FOREIGN KEY (`confirmed_by`) REFERENCES `users` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +``` --- **작성자**: Claude -**최종 수정일**: 2026-01-20 +**최종 수정일**: 2026-01-26 +**문서 버전**: 2.0
+ 아침 안전 회의 및 팀 구성을 관리합니다 +