기존 중구난방이던 문서를 체계적으로 재구성 ## 주요 변경사항 - 목차 추가 (9개 섹션) - 시스템 아키텍처 다이어그램 추가 - 배포 전 확인사항 섹션 신설 - 단계별 배포 절차 상세화 - 기능 명세 및 사용자 시나리오 추가 - API 명세 예시 코드 포함 - 프론트엔드 구현 세부 함수 코드 추가 - 테스트 가이드 확대 (API, 웹, 성능) - 문제 해결 섹션 강화 (5개 카테고리) - 데이터베이스 스키마 명시 ## 문서 구조 1. 시스템 개요 2. 배포 전 확인사항 3. 배포 절차 4. 기능 명세 5. API 명세 6. 프론트엔드 구현 7. 테스트 가이드 8. 문제 해결 9. 변경 이력 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1249 lines
36 KiB
Markdown
1249 lines
36 KiB
Markdown
# TBM 시스템 배포 가이드
|
|
|
|
## 📋 문서 개요
|
|
|
|
**목적**: TBM(Tool Box Meeting) 시스템 배포 및 사용 가이드
|
|
**대상**: 시스템 관리자, 개발자
|
|
**버전**: v1.2.0
|
|
**최종 수정일**: 2026-01-26
|
|
|
|
---
|
|
|
|
## 📑 목차
|
|
|
|
1. [시스템 개요](#시스템-개요)
|
|
2. [배포 전 확인사항](#배포-전-확인사항)
|
|
3. [배포 절차](#배포-절차)
|
|
4. [기능 명세](#기능-명세)
|
|
5. [API 명세](#api-명세)
|
|
6. [프론트엔드 구현](#프론트엔드-구현)
|
|
7. [테스트 가이드](#테스트-가이드)
|
|
8. [문제 해결](#문제-해결)
|
|
9. [변경 이력](#변경-이력)
|
|
|
|
---
|
|
|
|
## 시스템 개요
|
|
|
|
### 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 시스템 아키텍처
|
|
|
|
```
|
|
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
│ Dashboard │────▶│ TBM Page │────▶│ Work Report │
|
|
│ (빠른 작업) │ │ (TBM 관리) │ │ (팀원 자동선택) │
|
|
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
│ │ │
|
|
│ ▼ │
|
|
│ ┌──────────────────┐ │
|
|
└─────────────▶│ TBM API │◀─────────────┘
|
|
│ (17 endpoints) │
|
|
└──────────────────┘
|
|
│
|
|
┌────────▼────────┐
|
|
│ MySQL Database │
|
|
│ (5 tables) │
|
|
└─────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 배포 전 확인사항
|
|
|
|
### 2.1 환경 요구사항
|
|
|
|
- **Node.js**: v14 이상
|
|
- **MySQL**: v8.0 이상
|
|
- **PM2**: 프로세스 관리 (선택)
|
|
- **데이터베이스**: `hyungi` 스키마 존재
|
|
|
|
### 2.2 필수 의존성
|
|
|
|
```json
|
|
{
|
|
"knex": "^2.x",
|
|
"mysql2": "^3.x"
|
|
}
|
|
```
|
|
|
|
### 2.3 기존 시스템 확인
|
|
|
|
```bash
|
|
# 테이블 존재 확인
|
|
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
|
|
export DB_USER=your_db_user
|
|
export DB_PASSWORD=your_db_password
|
|
export DB_NAME=hyungi
|
|
|
|
# 마이그레이션 실행
|
|
npm run db:migrate
|
|
|
|
# 또는
|
|
npx knex migrate:latest --knexfile knexfile.js
|
|
```
|
|
|
|
#### 3.2.3 마이그레이션 확인
|
|
|
|
```bash
|
|
# 상태 확인
|
|
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
|
|
```
|
|
|
|
예상 결과:
|
|
```
|
|
+------------------------------+
|
|
| 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
|
|
```
|
|
|
|
예상 결과:
|
|
```
|
|
+----------------+-------+
|
|
| check_category | count |
|
|
+----------------+-------+
|
|
| PPE | 5 |
|
|
| EQUIPMENT | 4 |
|
|
| ENVIRONMENT | 4 |
|
|
| EMERGENCY | 3 |
|
|
+----------------+-------+
|
|
```
|
|
|
|
### 3.3 API 서버 재시작
|
|
|
|
```bash
|
|
# PM2 사용 시
|
|
pm2 restart api-hyungi
|
|
|
|
# 또는 직접 재시작
|
|
pm2 stop api-hyungi
|
|
pm2 start ecosystem.config.js --env production
|
|
|
|
# 로그 확인
|
|
pm2 logs api-hyungi --lines 50
|
|
```
|
|
|
|
### 3.4 페이지 권한 설정
|
|
|
|
```bash
|
|
# TBM 페이지 등록 확인
|
|
mysql -u root -p -e "SELECT * FROM pages WHERE page_key='tbm'\G" hyungi
|
|
```
|
|
|
|
예상 결과:
|
|
```
|
|
page_id: [auto_increment]
|
|
page_key: tbm
|
|
page_name: TBM 관리
|
|
page_path: /pages/work/tbm.html
|
|
category: work
|
|
description: Tool Box Meeting - 아침 안전 회의 및 팀 구성 관리
|
|
is_admin_only: 0
|
|
display_order: 10
|
|
```
|
|
|
|
### 3.5 웹 서버 재시작
|
|
|
|
```bash
|
|
# Nginx 재시작 (정적 파일 갱신)
|
|
docker restart tkfb_web
|
|
|
|
# 또는
|
|
sudo systemctl restart nginx
|
|
```
|
|
|
|
### 3.6 배포 체크리스트
|
|
|
|
- [ ] 데이터베이스 백업 완료
|
|
- [ ] 마이그레이션 실행 완료
|
|
- [ ] 5개 테이블 생성 확인
|
|
- [ ] tbm_safety_checks 초기 데이터 (17개) 확인
|
|
- [ ] pages 테이블에 TBM 페이지 등록 확인
|
|
- [ ] API 서버 재시작 완료
|
|
- [ ] 웹 서버 재시작 완료
|
|
- [ ] API 엔드포인트 테스트 (최소 1개)
|
|
- [ ] TBM 페이지 접속 테스트
|
|
- [ ] 권한 설정 테스트 (그룹장 계정)
|
|
- [ ] 대시보드 빠른 작업 표시 확인
|
|
|
|
---
|
|
|
|
## 기능 명세
|
|
|
|
### 4.1 TBM 세션 관리
|
|
|
|
**목적**: 날짜별 아침 안전 회의 생성 및 관리
|
|
|
|
**주요 기능**:
|
|
- 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`)
|
|
]);
|
|
```
|
|
|
|
### 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 명세
|
|
|
|
### 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 = `
|
|
<strong>🛠️ TBM 팀 구성 자동 적용</strong><br>
|
|
오늘 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
|
|
<a href="/pages/work/tbm.html"
|
|
class="quick-action-card"
|
|
id="tbmQuickAction"
|
|
style="display: none;
|
|
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
color: white;">
|
|
<div class="action-content">
|
|
<h3 style="color: white;">🛠️ TBM 관리</h3>
|
|
<p style="color: rgba(255, 255, 255, 0.9);">
|
|
아침 안전 회의 및 팀 구성을 관리합니다
|
|
</p>
|
|
</div>
|
|
<div class="action-arrow" style="color: white;">→</div>
|
|
</a>
|
|
```
|
|
|
|
**스타일 특징**:
|
|
- `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
|
|
```
|
|
|
|
**해결**:
|
|
```bash
|
|
# 마이그레이션 상태 확인
|
|
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
|
|
```
|
|
|
|
### 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)
|
|
**초기 배포**:
|
|
- 데이터베이스 스키마 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-26
|
|
**문서 버전**: 2.0
|