Files
TK-FB-Project/api.hyungi.net/models/tbmModel.js
Hyungi Ahn 7acb835c39 feat: 작업 관리 시스템 및 TBM 공정/작업 통합
## Backend Changes
- Create tasks table with work_type_id FK to work_types
- Add taskModel, taskController, taskRoutes for task CRUD
- Update tbmModel to support work_type_id and task_id
- Add migrations for tasks table and TBM integration

## Frontend Changes
- Create task management admin page (tasks.html, task-management.js)
- Update TBM modal to include work type (공정) and task (작업) selection
- Add cascading dropdown: work type → task selection
- Display work type and task info in TBM session cards
- Update sidebar navigation in all admin pages

## Database Schema
- tasks: task_id, work_type_id, task_name, description, is_active
- tbm_sessions: add work_type_id, task_id columns with FKs
- Foreign keys maintain referential integrity with work_types and tasks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 15:06:43 +09:00

467 lines
12 KiB
JavaScript

// models/tbmModel.js - TBM 시스템 모델
const db = require('../db/connection');
const TbmModel = {
// ==================== TBM 세션 관련 ====================
/**
* TBM 세션 생성
*/
createSession: (sessionData, callback) => {
const sql = `
INSERT INTO tbm_sessions
(session_date, leader_id, project_id, work_type_id, task_id, work_location,
work_description, safety_notes, start_time, created_by)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`;
const values = [
sessionData.session_date,
sessionData.leader_id,
sessionData.project_id,
sessionData.work_type_id,
sessionData.task_id,
sessionData.work_location,
sessionData.work_description,
sessionData.safety_notes,
sessionData.start_time,
sessionData.created_by
];
db.query(sql, values, callback);
},
/**
* 특정 날짜의 TBM 세션 조회
*/
getSessionsByDate: (date, callback) => {
const sql = `
SELECT
s.*,
w.worker_name as leader_name,
w.job_type as leader_job_type,
p.project_name,
p.job_no,
wt.name as work_type_name,
wt.category as work_type_category,
t.task_name,
u.username as created_by_username,
COUNT(DISTINCT ta.worker_id) as team_member_count
FROM tbm_sessions s
LEFT JOIN workers w ON s.leader_id = w.worker_id
LEFT JOIN projects p ON s.project_id = p.project_id
LEFT JOIN work_types wt ON s.work_type_id = wt.id
LEFT JOIN tasks t ON s.task_id = t.task_id
LEFT JOIN users u ON s.created_by = u.user_id
LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id
WHERE s.session_date = ?
GROUP BY s.session_id
ORDER BY s.start_time DESC
`;
db.query(sql, [date], callback);
},
/**
* TBM 세션 상세 조회
*/
getSessionById: (sessionId, callback) => {
const sql = `
SELECT
s.*,
w.worker_name as leader_name,
w.job_type as leader_job_type,
w.phone_number as leader_phone,
p.project_name,
p.job_no,
p.site,
wt.name as work_type_name,
wt.category as work_type_category,
t.task_name,
t.description as task_description,
u.username as created_by_username,
u.name as created_by_name
FROM tbm_sessions s
LEFT JOIN workers w ON s.leader_id = w.worker_id
LEFT JOIN projects p ON s.project_id = p.project_id
LEFT JOIN work_types wt ON s.work_type_id = wt.id
LEFT JOIN tasks t ON s.task_id = t.task_id
LEFT JOIN users u ON s.created_by = u.user_id
WHERE s.session_id = ?
`;
db.query(sql, [sessionId], callback);
},
/**
* TBM 세션 수정
*/
updateSession: (sessionId, sessionData, callback) => {
const sql = `
UPDATE tbm_sessions
SET
project_id = ?,
work_location = ?,
work_description = ?,
safety_notes = ?,
status = ?,
updated_at = NOW()
WHERE session_id = ?
`;
const values = [
sessionData.project_id,
sessionData.work_location,
sessionData.work_description,
sessionData.safety_notes,
sessionData.status,
sessionId
];
db.query(sql, values, callback);
},
/**
* TBM 세션 완료 처리
*/
completeSession: (sessionId, endTime, callback) => {
const sql = `
UPDATE tbm_sessions
SET
status = 'completed',
end_time = ?,
updated_at = NOW()
WHERE session_id = ?
`;
db.query(sql, [endTime, sessionId], callback);
},
// ==================== 팀 구성 관련 ====================
/**
* 팀원 추가
*/
addTeamMember: (assignmentData, callback) => {
const sql = `
INSERT INTO tbm_team_assignments
(session_id, worker_id, assigned_role, work_detail, is_present, absence_reason)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
assigned_role = VALUES(assigned_role),
work_detail = VALUES(work_detail),
is_present = VALUES(is_present),
absence_reason = VALUES(absence_reason)
`;
const values = [
assignmentData.session_id,
assignmentData.worker_id,
assignmentData.assigned_role,
assignmentData.work_detail,
assignmentData.is_present !== undefined ? assignmentData.is_present : true,
assignmentData.absence_reason
];
db.query(sql, values, callback);
},
/**
* 팀 구성 일괄 추가
*/
addTeamMembers: (sessionId, members, callback) => {
if (!members || members.length === 0) {
return callback(null, { affectedRows: 0 });
}
const values = members.map(m => [
sessionId,
m.worker_id,
m.assigned_role || null,
m.work_detail || null,
m.is_present !== undefined ? m.is_present : true,
m.absence_reason || null
]);
const sql = `
INSERT INTO tbm_team_assignments
(session_id, worker_id, assigned_role, work_detail, is_present, absence_reason)
VALUES ?
`;
db.query(sql, [values], callback);
},
/**
* TBM 세션의 팀 구성 조회
*/
getTeamMembers: (sessionId, callback) => {
const sql = `
SELECT
ta.*,
w.worker_name,
w.job_type,
w.phone_number,
w.department
FROM tbm_team_assignments ta
INNER JOIN workers w ON ta.worker_id = w.worker_id
WHERE ta.session_id = ?
ORDER BY ta.assigned_at DESC
`;
db.query(sql, [sessionId], callback);
},
/**
* 팀원 제거
*/
removeTeamMember: (sessionId, workerId, callback) => {
const sql = `
DELETE FROM tbm_team_assignments
WHERE session_id = ? AND worker_id = ?
`;
db.query(sql, [sessionId, workerId], callback);
},
// ==================== 안전 체크리스트 관련 ====================
/**
* 모든 안전 체크 항목 조회
*/
getAllSafetyChecks: (callback) => {
const sql = `
SELECT *
FROM tbm_safety_checks
WHERE is_active = 1
ORDER BY check_category, display_order
`;
db.query(sql, callback);
},
/**
* 카테고리별 안전 체크 항목 조회
*/
getSafetyChecksByCategory: (category, callback) => {
const sql = `
SELECT *
FROM tbm_safety_checks
WHERE check_category = ? AND is_active = 1
ORDER BY display_order
`;
db.query(sql, [category], callback);
},
/**
* TBM 세션의 안전 체크 기록 조회
*/
getSafetyRecords: (sessionId, callback) => {
const sql = `
SELECT
sr.*,
sc.check_category,
sc.check_item,
sc.description,
sc.is_required,
u.username as checked_by_username,
u.name as checked_by_name
FROM tbm_safety_records sr
INNER JOIN tbm_safety_checks sc ON sr.check_id = sc.check_id
LEFT JOIN users u ON sr.checked_by = u.user_id
WHERE sr.session_id = ?
ORDER BY sc.check_category, sc.display_order
`;
db.query(sql, [sessionId], callback);
},
/**
* 안전 체크 기록 저장/업데이트
*/
saveSafetyRecord: (recordData, callback) => {
const sql = `
INSERT INTO tbm_safety_records
(session_id, check_id, is_checked, notes, checked_by, checked_at)
VALUES (?, ?, ?, ?, ?, NOW())
ON DUPLICATE KEY UPDATE
is_checked = VALUES(is_checked),
notes = VALUES(notes),
checked_by = VALUES(checked_by),
checked_at = NOW()
`;
const values = [
recordData.session_id,
recordData.check_id,
recordData.is_checked,
recordData.notes,
recordData.checked_by
];
db.query(sql, values, callback);
},
/**
* 안전 체크 일괄 저장
*/
saveSafetyRecords: (sessionId, records, checkedBy, callback) => {
if (!records || records.length === 0) {
return callback(null, { affectedRows: 0 });
}
const values = records.map(r => [
sessionId,
r.check_id,
r.is_checked,
r.notes || null,
checkedBy
]);
const sql = `
INSERT INTO tbm_safety_records
(session_id, check_id, is_checked, notes, checked_by, checked_at)
VALUES ?
ON DUPLICATE KEY UPDATE
is_checked = VALUES(is_checked),
notes = VALUES(notes),
checked_by = VALUES(checked_by),
checked_at = NOW()
`;
db.query(sql, [values], callback);
},
// ==================== 작업 인계 관련 ====================
/**
* 작업 인계 생성
*/
createHandover: (handoverData, callback) => {
const sql = `
INSERT INTO team_handovers
(session_id, from_leader_id, to_leader_id, handover_date, handover_time,
reason, handover_notes, worker_ids)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`;
const values = [
handoverData.session_id,
handoverData.from_leader_id,
handoverData.to_leader_id,
handoverData.handover_date,
handoverData.handover_time,
handoverData.reason,
handoverData.handover_notes,
JSON.stringify(handoverData.worker_ids || [])
];
db.query(sql, values, callback);
},
/**
* 작업 인계 확인
*/
confirmHandover: (handoverId, confirmedBy, callback) => {
const sql = `
UPDATE team_handovers
SET
is_confirmed = 1,
confirmed_at = NOW(),
confirmed_by = ?
WHERE handover_id = ?
`;
db.query(sql, [confirmedBy, handoverId], callback);
},
/**
* 특정 날짜의 작업 인계 목록 조회
*/
getHandoversByDate: (date, callback) => {
const sql = `
SELECT
h.*,
w1.worker_name as from_leader_name,
w2.worker_name as to_leader_name,
u.username as confirmed_by_username,
u.name as confirmed_by_name
FROM team_handovers h
INNER JOIN workers w1 ON h.from_leader_id = w1.worker_id
INNER JOIN workers w2 ON h.to_leader_id = w2.worker_id
LEFT JOIN users u ON h.confirmed_by = u.user_id
WHERE h.handover_date = ?
ORDER BY h.handover_time DESC
`;
db.query(sql, [date], callback);
},
/**
* 인수자가 받은 미확인 인계 건 조회
*/
getPendingHandovers: (toLeaderId, callback) => {
const sql = `
SELECT
h.*,
w1.worker_name as from_leader_name,
w1.phone_number as from_leader_phone,
s.work_location,
s.work_description
FROM team_handovers h
INNER JOIN workers w1 ON h.from_leader_id = w1.worker_id
LEFT JOIN tbm_sessions s ON h.session_id = s.session_id
WHERE h.to_leader_id = ? AND h.is_confirmed = 0
ORDER BY h.handover_date DESC, h.handover_time DESC
`;
db.query(sql, [toLeaderId], callback);
},
// ==================== 통계 및 리포트 ====================
/**
* 특정 기간의 TBM 통계
*/
getTbmStatistics: (startDate, endDate, callback) => {
const sql = `
SELECT
DATE(session_date) as date,
COUNT(DISTINCT session_id) as session_count,
COUNT(DISTINCT leader_id) as leader_count,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count
FROM tbm_sessions
WHERE session_date BETWEEN ? AND ?
GROUP BY DATE(session_date)
ORDER BY date DESC
`;
db.query(sql, [startDate, endDate], callback);
},
/**
* 리더별 TBM 진행 현황
*/
getLeaderStatistics: (startDate, endDate, callback) => {
const sql = `
SELECT
s.leader_id,
w.worker_name as leader_name,
COUNT(DISTINCT s.session_id) as total_sessions,
SUM(CASE WHEN s.status = 'completed' THEN 1 ELSE 0 END) as completed_sessions,
COUNT(DISTINCT ta.worker_id) as total_team_members
FROM tbm_sessions s
INNER JOIN workers w ON s.leader_id = w.worker_id
LEFT JOIN tbm_team_assignments ta ON s.session_id = ta.session_id
WHERE s.session_date BETWEEN ? AND ?
GROUP BY s.leader_id
ORDER BY total_sessions DESC
`;
db.query(sql, [startDate, endDate], callback);
}
};
module.exports = TbmModel;