기존 중구난방이던 문서를 체계적으로 재구성 ## 주요 변경사항 - 목차 추가 (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>
36 KiB
TBM 시스템 배포 가이드
📋 문서 개요
목적: TBM(Tool Box Meeting) 시스템 배포 및 사용 가이드 대상: 시스템 관리자, 개발자 버전: v1.2.0 최종 수정일: 2026-01-26
📑 목차
시스템 개요
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 필수 의존성
{
"knex": "^2.x",
"mysql2": "^3.x"
}
2.3 기존 시스템 확인
# 테이블 존재 확인
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 데이터베이스 백업 (필수)
# 배포 전 백업
mysqldump -u root -p hyungi > backup_before_tbm_$(date +%Y%m%d_%H%M%S).sql
3.2 마이그레이션 실행
3.2.1 마이그레이션 파일 확인
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 마이그레이션 실행
# 환경 변수 설정 (필요 시)
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 마이그레이션 확인
# 상태 확인
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 초기 데이터 확인
# 안전 체크리스트 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 서버 재시작
# PM2 사용 시
pm2 restart api-hyungi
# 또는 직접 재시작
pm2 stop api-hyungi
pm2 start ecosystem.config.js --env production
# 로그 확인
pm2 logs api-hyungi --lines 50
3.4 페이지 권한 설정
# 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 웹 서버 재시작
# 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)
- 세션 수정 및 삭제
사용자 시나리오:
- 팀장이 매일 아침 TBM 페이지 접속
- "새 TBM 시작" 클릭
- 오늘 날짜, 프로젝트, 작업 장소, 작업 내용 입력
- 안전 특이사항 기록 (선택)
- "저장 및 팀 구성하기" 클릭
4.2 팀 구성 관리
목적: 작업 팀원 선택 및 역할 분배
주요 기능:
- 다중 선택으로 팀원 지정
- 각 팀원의 역할/담당 업무 기록 (선택)
- 출석/결석 처리
- 결석 사유 기록
사용자 시나리오:
- TBM 세션 저장 후 팀 구성 모달 자동 표시
- 체크박스로 팀원 선택 (전체 선택/해제 버튼 제공)
- 선택된 팀원 확인 (N명 표시)
- "팀 구성 완료" 클릭
작업 보고서 연동:
- 작업 보고서 페이지에서 TBM 팀원이 자동으로 선택됨
- 안내 메시지: "오늘 TBM에서 구성된 팀원 N명이 자동으로 선택되었습니다."
4.3 안전 체크리스트
목적: 작업 전 안전 점검 항목 확인
카테고리 및 항목 (총 17개):
| 카테고리 | 항목 수 | 예시 |
|---|---|---|
| PPE (개인 보호 장비) | 5 | 안전모, 안전화, 안전벨트, 보호안경, 안전장갑 |
| EQUIPMENT (장비 점검) | 4 | 공구 점검, 전기 장비, 사다리/비계, 안전 표지판 |
| ENVIRONMENT (작업 환경) | 4 | 작업 구역 정리, 위험물 확인, 환기, 조명 |
| EMERGENCY (비상 대응) | 3 | 비상 연락망, 응급 처치함, 대피 경로 |
주요 기능:
- 카테고리별 체크리스트 표시
- 각 항목 체크 (is_checked: true/false)
- 특이사항/비고 입력 (선택)
- 체크한 사람 및 시간 기록
사용자 시나리오:
- TBM 세션 카드에서 "안전 체크" 버튼 클릭
- 카테고리별로 항목 확인
- 각 항목 체크 및 특이사항 입력
- "안전 체크 완료" 클릭
4.4 작업 인계 시스템
목적: 팀장 조퇴/반차 시 작업 인계
인계 사유:
half_day: 반차early_leave: 조퇴emergency: 긴급 상황other: 기타
주요 기능:
- 인수자(다음 팀장) 선택
- 인계할 팀원 선택 (기본: 전체 선택)
- 인계 날짜/시간 기록
- 인계 내용 작성
- 인수자 확인 프로세스
사용자 시나리오:
- TBM 세션 카드에서 "인계" 버튼 클릭
- 인계 사유 선택 (반차, 조퇴, 긴급, 기타)
- 인수자(다음 팀장) 선택
- 인계할 팀원 선택 (체크박스)
- 인계 내용 입력 (특이사항, 주의사항 등)
- "인계 요청" 클릭
- 인수자가 확인 후 "인수 확인" 처리
4.5 TBM 상세보기
목적: TBM 세션의 모든 정보를 한눈에 확인
표시 정보:
- 기본 정보: 날짜, 팀장, 프로젝트, 작업 장소, 작업 내용, 안전 특이사항, 시작/종료 시간
- 팀 구성: 팀원 목록 (이름, 역할, 출석 여부)
- 안전 체크: 카테고리별 체크리스트 상태 (체크됨/안됨, 특이사항)
성능 최적화:
// 병렬 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) - 동적 권한 체크: 로그인 시 자동으로 권한 확인
권한 체크 로직:
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 세션 생성 요청 본문:
{
"session_date": "2026-01-26",
"leader_id": 1,
"project_id": 5,
"work_location": "현장 A동",
"work_description": "배관 설치 작업",
"safety_notes": "고소 작업 주의",
"start_time": "08:00:00"
}
응답:
{
"success": true,
"message": "TBM 세션이 생성되었습니다",
"data": { "session_id": 123 }
}
GET /api/tbm/sessions/date/:date
설명: 특정 날짜의 TBM 세션 목록 조회
예시: GET /api/tbm/sessions/date/2026-01-26
응답:
{
"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 세션 완료 처리 요청 본문:
{
"end_time": "17:00:00"
}
5.2 팀 구성 관리
POST /api/tbm/sessions/:sessionId/team/batch
설명: 팀원 일괄 추가 요청 본문:
{
"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
설명: 안전 체크 일괄 저장 요청 본문:
{
"checks": [
{ "check_id": 1, "is_checked": true, "notes": "" },
{ "check_id": 2, "is_checked": true, "notes": "안전화 교체 필요" }
]
}
5.4 작업 인계
POST /api/tbm/handovers
설명: 작업 인계 생성 요청 본문:
{
"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
// 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
// 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
// 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 빠른 작업 카드
<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 테스트
# 환경 변수 설정 (토큰)
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 권한 확인 테스트
- 관리자 계정으로 로그인
/pages/admin/page-access.html접속- 그룹장 계정에 "TBM 관리" 페이지 권한 부여
- 그룹장 계정으로 재로그인
- 대시보드에서 "TBM 관리" 빠른 작업 카드 확인
7.2.2 TBM 세션 생성 테스트
- TBM 페이지 접속 (
/pages/work/tbm.html) - "새 TBM 시작" 클릭
- 폼 입력:
- 날짜: 오늘
- 팀장: 김그룹장
- 프로젝트: 아파트 건설
- 작업 장소: A동 5층
- 작업 내용: 배관 설치
- "저장 및 팀 구성하기" 클릭
- 팀 구성 모달에서 팀원 3명 선택
- "팀 구성 완료" 클릭
- TBM 세션 카드가 목록에 표시되는지 확인
7.2.3 안전 체크리스트 테스트
- TBM 세션 카드에서 "안전 체크" 버튼 클릭
- PPE 카테고리 항목 5개 모두 체크
- EQUIPMENT 카테고리에서 "전기 장비 점검" 체크 후 특이사항 입력: "전압 확인 필요"
- "안전 체크 완료" 클릭
- 세션 카드에서 안전 체크 완료 표시 확인
7.2.4 작업 인계 테스트
- TBM 세션 카드에서 "인계" 버튼 클릭
- 인계 사유: "조퇴" 선택
- 인수자: 이그룹장 선택
- 팀원 2명 선택 (기본: 전체 선택됨)
- 인계 내용: "3시 조퇴로 인계합니다. 배관 작업 마무리 부탁드립니다."
- "인계 요청" 클릭
- 인계 완료 메시지 확인
7.2.5 작업 보고서 연동 테스트
- TBM 세션 생성 및 팀 구성 완료 (팀원 5명)
- "작업 보고서 작성" 페이지 이동
- 보고 날짜: TBM 세션과 동일한 날짜 선택
- "다음" 버튼 클릭 (작업자 선택 단계)
- 확인 사항:
- TBM 팀원 5명이 자동으로 선택되어 있음
- 안내 메시지: "오늘 TBM에서 구성된 팀원 5명이 자동으로 선택되었습니다."
- "다음" 버튼이 활성화되어 있는지 확인
7.2.6 TBM 상세보기 테스트
- TBM 세션 카드 클릭
- 상세보기 모달 확인:
- 기본 정보: 날짜, 팀장, 프로젝트, 장소, 작업 내용
- 팀 구성: 팀원 목록 및 역할
- 안전 체크: 카테고리별 체크 상태 (✅/❌)
- 모달 닫기
7.3 성능 테스트
// 브라우저 개발자 도구 콘솔에서 실행
// 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 데이터 타입 불일치
해결:
- 참조 테이블의 PK 데이터 타입 확인:
DESCRIBE workers; -- worker_id는 INT(11) signed
DESCRIBE users; -- user_id는 INT(11) signed
- 마이그레이션 파일에서 외래 키 컬럼을 signed INT로 수정:
table.integer('leader_id').notNullable(); // unsigned 제거
- 마이그레이션 재실행:
npx knex migrate:rollback
npx knex migrate:latest
오류: 테이블 이미 존재
증상:
Error: Table 'tbm_sessions' already exists
해결:
# 마이그레이션 상태 확인
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
원인: 라우트 등록 누락 또는 서버 미재시작
해결:
- 라우트 등록 확인:
// api.hyungi.net/config/routes.js
const tbmRoutes = require('../routes/tbmRoutes');
app.use('/api/tbm', tbmRoutes);
- 서버 재시작:
pm2 restart api-hyungi
pm2 logs api-hyungi --lines 50
오류: 401 Unauthorized
증상:
{ "message": "인증 토큰이 필요합니다" }
원인: JWT 토큰 누락 또는 만료
해결:
- 로그인 후 새 토큰 발급
- 요청 헤더에 토큰 포함:
headers: {
'Authorization': `Bearer ${token}`
}
8.3 프론트엔드 오류
오류: TBM 빠른 작업 카드가 표시되지 않음
증상: 권한이 있는 사용자인데도 대시보드에 TBM 카드가 보이지 않음
원인:
- 페이지 권한 미설정
- JavaScript 오류로
checkTbmPageAccess()미실행 - API 응답 구조 변경
해결:
- 페이지 권한 확인:
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';
- 브라우저 개발자 도구 콘솔 확인:
// 콘솔에 "✅ TBM 페이지 접근 권한 있음" 메시지가 있어야 함
// 오류가 있다면 확인
- API 응답 확인:
curl -X GET "http://localhost:20005/api/users/1/page-access" \
-H "Authorization: Bearer $TOKEN"
- 캐시 삭제 및 강력 새로고침 (Ctrl+Shift+R / Cmd+Shift+R)
오류: TBM 팀원이 작업 보고서에 자동 선택되지 않음
증상: TBM 세션을 만들었는데 작업 보고서에서 팀원이 자동 선택되지 않음
원인:
- TBM 세션 날짜와 작업 보고서 날짜 불일치
- TBM 세션이 completed 상태 (draft 세션만 자동 선택됨)
- API 호출 오류
해결:
- 날짜 일치 확인:
// 콘솔에서 확인
const reportDate = document.getElementById('reportDate').value;
console.log('보고서 날짜:', reportDate);
- TBM 세션 상태 확인:
SELECT session_id, session_date, status
FROM tbm_sessions
WHERE session_date = '2026-01-26';
- 콘솔 로그 확인:
// "✅ TBM 팀 구성 로드 성공: N명" 메시지가 있어야 함
// "❌ TBM 팀 구성 조회 오류" 메시지가 있다면 API 오류
8.4 권한 문제
오류: TBM 페이지 접근 불가 (403 Forbidden)
증상: TBM 페이지 접속 시 권한 없음 메시지
원인: 사용자에게 TBM 페이지 접근 권한 미부여
해결:
- 관리자 계정으로 로그인
/pages/admin/page-access.html접속- 권한 부여할 사용자 선택
- "TBM 관리" 페이지 체크
- "저장" 클릭
- 해당 사용자 재로그인
또는 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이 아님
원인: 초기 데이터 삽입 실패
해결:
# 마이그레이션 롤백 후 재실행
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
관련 문서
데이터베이스 스키마
tbm_sessions
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
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
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
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
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