feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등
- 순찰/점검 기능 개선 (zone-detail 페이지 추가) - 출근/근태 시스템 개선 (연차 조회, 근무현황) - 작업분석 대분류 그룹화 및 마이그레이션 스크립트 - 모바일 네비게이션 UI 추가 - NAS 배포 도구 및 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
994
deploy/tkfb-package/api.hyungi.net/models/tbmModel.js
Normal file
994
deploy/tkfb-package/api.hyungi.net/models/tbmModel.js
Normal file
@@ -0,0 +1,994 @@
|
||||
// models/tbmModel.js - TBM 시스템 모델
|
||||
const { getDb } = require('../dbPool');
|
||||
|
||||
const TbmModel = {
|
||||
// ==================== TBM 세션 관련 ====================
|
||||
|
||||
/**
|
||||
* TBM 세션 생성
|
||||
*/
|
||||
createSession: async (sessionData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
INSERT INTO tbm_sessions
|
||||
(session_date, leader_id, project_id, work_type_id, task_id, work_location, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const values = [
|
||||
sessionData.session_date,
|
||||
sessionData.leader_id,
|
||||
sessionData.project_id || null,
|
||||
sessionData.work_type_id || null,
|
||||
sessionData.task_id || null,
|
||||
sessionData.work_location || null,
|
||||
sessionData.created_by
|
||||
];
|
||||
|
||||
const [result] = await db.query(sql, values);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 날짜의 TBM 세션 조회
|
||||
*/
|
||||
getSessionsByDate: async (date, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
s.*,
|
||||
w.worker_name as leader_name,
|
||||
w.job_type as leader_job_type,
|
||||
u.username as created_by_username,
|
||||
u.name as created_by_name,
|
||||
COUNT(DISTINCT ta.worker_id) as team_member_count,
|
||||
-- 첫 번째 팀원의 작업 정보 가져오기
|
||||
first_ta.project_id,
|
||||
first_ta.work_type_id,
|
||||
first_ta.task_id,
|
||||
first_ta.workplace_id,
|
||||
first_p.project_name,
|
||||
first_wt.name as work_type_name,
|
||||
first_t.task_name,
|
||||
first_wp.workplace_name as work_location
|
||||
FROM tbm_sessions s
|
||||
LEFT JOIN workers w ON s.leader_id = w.worker_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
|
||||
-- 첫 번째 팀원 정보 (가장 먼저 등록된 작업)
|
||||
LEFT JOIN (
|
||||
SELECT * FROM tbm_team_assignments
|
||||
WHERE (session_id, assignment_id) IN (
|
||||
SELECT session_id, MIN(assignment_id)
|
||||
FROM tbm_team_assignments
|
||||
GROUP BY session_id
|
||||
)
|
||||
) first_ta ON s.session_id = first_ta.session_id
|
||||
LEFT JOIN projects first_p ON first_ta.project_id = first_p.project_id
|
||||
LEFT JOIN work_types first_wt ON first_ta.work_type_id = first_wt.id
|
||||
LEFT JOIN tasks first_t ON first_ta.task_id = first_t.task_id
|
||||
LEFT JOIN workplaces first_wp ON first_ta.workplace_id = first_wp.workplace_id
|
||||
WHERE s.session_date = ?
|
||||
GROUP BY s.session_id
|
||||
ORDER BY s.session_id DESC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* TBM 세션 상세 조회
|
||||
*/
|
||||
getSessionById: async (sessionId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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 = ?
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [sessionId]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* TBM 세션 수정
|
||||
*/
|
||||
updateSession: async (sessionId, sessionData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
UPDATE tbm_sessions
|
||||
SET
|
||||
project_id = ?,
|
||||
work_location = ?,
|
||||
status = ?,
|
||||
updated_at = NOW()
|
||||
WHERE session_id = ?
|
||||
`;
|
||||
|
||||
const values = [
|
||||
sessionData.project_id,
|
||||
sessionData.work_location,
|
||||
sessionData.status,
|
||||
sessionId
|
||||
];
|
||||
|
||||
const [result] = await db.query(sql, values);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* TBM 세션 완료 처리
|
||||
*/
|
||||
completeSession: async (sessionId, endTime, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
UPDATE tbm_sessions
|
||||
SET
|
||||
status = 'completed',
|
||||
end_time = ?,
|
||||
updated_at = NOW()
|
||||
WHERE session_id = ?
|
||||
`;
|
||||
|
||||
const [result] = await db.query(sql, [endTime, sessionId]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 팀 구성 관련 ====================
|
||||
|
||||
/**
|
||||
* 팀원 추가 (작업자별 상세 정보 포함)
|
||||
*/
|
||||
addTeamMember: async (assignmentData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
INSERT INTO tbm_team_assignments
|
||||
(session_id, worker_id, assigned_role, work_detail, is_present, absence_reason,
|
||||
project_id, work_type_id, task_id, workplace_category_id, workplace_id)
|
||||
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),
|
||||
project_id = VALUES(project_id),
|
||||
work_type_id = VALUES(work_type_id),
|
||||
task_id = VALUES(task_id),
|
||||
workplace_category_id = VALUES(workplace_category_id),
|
||||
workplace_id = VALUES(workplace_id)
|
||||
`;
|
||||
|
||||
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,
|
||||
assignmentData.project_id || null,
|
||||
assignmentData.work_type_id || null,
|
||||
assignmentData.task_id || null,
|
||||
assignmentData.workplace_category_id || null,
|
||||
assignmentData.workplace_id || null
|
||||
];
|
||||
|
||||
const [result] = await db.query(sql, values);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 팀 구성 일괄 추가 (작업자별 상세 정보 포함)
|
||||
*/
|
||||
addTeamMembers: async (sessionId, members, callback) => {
|
||||
try {
|
||||
if (!members || members.length === 0) {
|
||||
return callback(null, { affectedRows: 0 });
|
||||
}
|
||||
|
||||
const db = await getDb();
|
||||
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,
|
||||
m.project_id || null,
|
||||
m.work_type_id || null,
|
||||
m.task_id || null,
|
||||
m.workplace_category_id || null,
|
||||
m.workplace_id || null
|
||||
]);
|
||||
|
||||
const sql = `
|
||||
INSERT INTO tbm_team_assignments
|
||||
(session_id, worker_id, assigned_role, work_detail, is_present, absence_reason,
|
||||
project_id, work_type_id, task_id, workplace_category_id, workplace_id)
|
||||
VALUES ?
|
||||
`;
|
||||
|
||||
const [result] = await db.query(sql, [values]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* TBM 세션의 팀 구성 조회 (작업자별 상세 정보 포함)
|
||||
*/
|
||||
getTeamMembers: async (sessionId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
ta.*,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
w.phone_number,
|
||||
w.department,
|
||||
p.project_name,
|
||||
wt.name as work_type_name,
|
||||
t.task_name,
|
||||
wc.category_name AS workplace_category_name,
|
||||
wp.workplace_name
|
||||
FROM tbm_team_assignments ta
|
||||
INNER JOIN workers w ON ta.worker_id = w.worker_id
|
||||
LEFT JOIN projects p ON ta.project_id = p.project_id
|
||||
LEFT JOIN work_types wt ON ta.work_type_id = wt.id
|
||||
LEFT JOIN tasks t ON ta.task_id = t.task_id
|
||||
LEFT JOIN workplace_categories wc ON ta.workplace_category_id = wc.category_id
|
||||
LEFT JOIN workplaces wp ON ta.workplace_id = wp.workplace_id
|
||||
WHERE ta.session_id = ?
|
||||
ORDER BY ta.assigned_at DESC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [sessionId]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 팀원 제거
|
||||
*/
|
||||
removeTeamMember: async (sessionId, workerId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
DELETE FROM tbm_team_assignments
|
||||
WHERE session_id = ? AND worker_id = ?
|
||||
`;
|
||||
|
||||
const [result] = await db.query(sql, [sessionId, workerId]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 세션의 모든 팀원 삭제
|
||||
*/
|
||||
clearAllTeamMembers: async (sessionId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
DELETE FROM tbm_team_assignments
|
||||
WHERE session_id = ?
|
||||
`;
|
||||
|
||||
const [result] = await db.query(sql, [sessionId]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 안전 체크리스트 관련 ====================
|
||||
|
||||
/**
|
||||
* 모든 안전 체크 항목 조회
|
||||
*/
|
||||
getAllSafetyChecks: async (callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT *
|
||||
FROM tbm_safety_checks
|
||||
WHERE is_active = 1
|
||||
ORDER BY check_category, display_order
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 카테고리별 안전 체크 항목 조회
|
||||
*/
|
||||
getSafetyChecksByCategory: async (category, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT *
|
||||
FROM tbm_safety_checks
|
||||
WHERE check_category = ? AND is_active = 1
|
||||
ORDER BY display_order
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [category]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* TBM 세션의 안전 체크 기록 조회
|
||||
*/
|
||||
getSafetyRecords: async (sessionId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [sessionId]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 안전 체크 기록 저장/업데이트
|
||||
*/
|
||||
saveSafetyRecord: async (recordData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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
|
||||
];
|
||||
|
||||
const [result] = await db.query(sql, values);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 안전 체크 일괄 저장
|
||||
*/
|
||||
saveSafetyRecords: async (sessionId, records, checkedBy, callback) => {
|
||||
try {
|
||||
if (!records || records.length === 0) {
|
||||
return callback(null, { affectedRows: 0 });
|
||||
}
|
||||
|
||||
const db = await getDb();
|
||||
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()
|
||||
`;
|
||||
|
||||
const [result] = await db.query(sql, [values]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 작업 인계 관련 ====================
|
||||
|
||||
/**
|
||||
* 작업 인계 생성
|
||||
*/
|
||||
createHandover: async (handoverData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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 || [])
|
||||
];
|
||||
|
||||
const [result] = await db.query(sql, values);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업 인계 확인
|
||||
*/
|
||||
confirmHandover: async (handoverId, confirmedBy, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
UPDATE team_handovers
|
||||
SET
|
||||
is_confirmed = 1,
|
||||
confirmed_at = NOW(),
|
||||
confirmed_by = ?
|
||||
WHERE handover_id = ?
|
||||
`;
|
||||
|
||||
const [result] = await db.query(sql, [confirmedBy, handoverId]);
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 특정 날짜의 작업 인계 목록 조회
|
||||
*/
|
||||
getHandoversByDate: async (date, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [date]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 인수자가 받은 미확인 인계 건 조회
|
||||
*/
|
||||
getPendingHandovers: async (toLeaderId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
SELECT
|
||||
h.*,
|
||||
w1.worker_name as from_leader_name,
|
||||
w1.phone_number as from_leader_phone,
|
||||
s.work_location
|
||||
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
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [toLeaderId]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 통계 및 리포트 ====================
|
||||
|
||||
/**
|
||||
* 특정 기간의 TBM 통계
|
||||
*/
|
||||
getTbmStatistics: async (startDate, endDate, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [startDate, endDate]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 리더별 TBM 진행 현황
|
||||
*/
|
||||
getLeaderStatistics: async (startDate, endDate, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
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
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, [startDate, endDate]);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업보고서가 작성되지 않은 TBM 세션의 팀 배정 조회
|
||||
* @param {number|null} userId - 조회할 사용자 ID (null이면 모든 TBM 조회 - 관리자용)
|
||||
*/
|
||||
getIncompleteWorkReports: async (userId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
// WHERE 조건 동적 생성
|
||||
let whereClause = `
|
||||
WHERE dwr.id IS NULL
|
||||
AND s.status = 'draft'
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
// userId가 있으면 created_by 조건 추가 (일반 사용자)
|
||||
if (userId !== null && userId !== undefined) {
|
||||
whereClause = `
|
||||
WHERE s.created_by = ?
|
||||
AND dwr.id IS NULL
|
||||
AND s.status = 'draft'
|
||||
`;
|
||||
params.push(userId);
|
||||
}
|
||||
|
||||
const sql = `
|
||||
SELECT
|
||||
ta.assignment_id,
|
||||
ta.session_id,
|
||||
ta.worker_id,
|
||||
ta.project_id,
|
||||
ta.work_type_id,
|
||||
ta.task_id,
|
||||
ta.workplace_category_id,
|
||||
ta.workplace_id,
|
||||
s.session_date,
|
||||
s.status as session_status,
|
||||
s.created_by,
|
||||
w.worker_name,
|
||||
w.job_type,
|
||||
p.project_name,
|
||||
wt.name as work_type_name,
|
||||
t.task_name,
|
||||
wp.workplace_name,
|
||||
wc.category_name,
|
||||
creator.name as created_by_name
|
||||
FROM tbm_team_assignments ta
|
||||
INNER JOIN tbm_sessions s ON ta.session_id = s.session_id
|
||||
INNER JOIN workers w ON ta.worker_id = w.worker_id
|
||||
LEFT JOIN users creator ON s.created_by = creator.user_id
|
||||
LEFT JOIN projects p ON ta.project_id = p.project_id
|
||||
LEFT JOIN work_types wt ON ta.work_type_id = wt.id
|
||||
LEFT JOIN tasks t ON ta.task_id = t.task_id
|
||||
LEFT JOIN workplaces wp ON ta.workplace_id = wp.workplace_id
|
||||
LEFT JOIN workplace_categories wc ON ta.workplace_category_id = wc.category_id
|
||||
LEFT JOIN daily_work_reports dwr ON ta.assignment_id = dwr.tbm_assignment_id
|
||||
${whereClause}
|
||||
ORDER BY s.session_date DESC, ta.assignment_id ASC
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, params);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
// ========== 안전 체크리스트 확장 메서드 ==========
|
||||
|
||||
/**
|
||||
* 유형별 안전 체크 항목 조회
|
||||
* @param {string} checkType - 체크 유형 (basic, weather, task)
|
||||
* @param {Object} options - 추가 옵션 (weatherCondition, taskId)
|
||||
*/
|
||||
getSafetyChecksByType: async (checkType, options = {}, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
let sql = `
|
||||
SELECT sc.*,
|
||||
wc.condition_name as weather_condition_name,
|
||||
wc.icon as weather_icon,
|
||||
t.task_name
|
||||
FROM tbm_safety_checks sc
|
||||
LEFT JOIN weather_conditions wc ON sc.weather_condition = wc.condition_code
|
||||
LEFT JOIN tasks t ON sc.task_id = t.task_id
|
||||
WHERE sc.is_active = 1 AND sc.check_type = ?
|
||||
`;
|
||||
const params = [checkType];
|
||||
|
||||
if (checkType === 'weather' && options.weatherCondition) {
|
||||
sql += ' AND sc.weather_condition = ?';
|
||||
params.push(options.weatherCondition);
|
||||
}
|
||||
|
||||
if (checkType === 'task' && options.taskId) {
|
||||
sql += ' AND sc.task_id = ?';
|
||||
params.push(options.taskId);
|
||||
}
|
||||
|
||||
sql += ' ORDER BY sc.check_category, sc.display_order';
|
||||
|
||||
const [rows] = await db.query(sql, params);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 날씨 조건별 안전 체크 항목 조회 (복수 조건)
|
||||
* @param {string[]} conditions - 날씨 조건 배열 ['rain', 'wind']
|
||||
*/
|
||||
getSafetyChecksByWeather: async (conditions, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
if (!conditions || conditions.length === 0) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
const placeholders = conditions.map(() => '?').join(',');
|
||||
const sql = `
|
||||
SELECT sc.*,
|
||||
wc.condition_name as weather_condition_name,
|
||||
wc.icon as weather_icon
|
||||
FROM tbm_safety_checks sc
|
||||
LEFT JOIN weather_conditions wc ON sc.weather_condition = wc.condition_code
|
||||
WHERE sc.is_active = 1
|
||||
AND sc.check_type = 'weather'
|
||||
AND sc.weather_condition IN (${placeholders})
|
||||
ORDER BY sc.weather_condition, sc.display_order
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, conditions);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 작업별 안전 체크 항목 조회 (복수 작업)
|
||||
* @param {number[]} taskIds - 작업 ID 배열
|
||||
*/
|
||||
getSafetyChecksByTasks: async (taskIds, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
if (!taskIds || taskIds.length === 0) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
const placeholders = taskIds.map(() => '?').join(',');
|
||||
const sql = `
|
||||
SELECT sc.*,
|
||||
t.task_name,
|
||||
wt.name as work_type_name
|
||||
FROM tbm_safety_checks sc
|
||||
LEFT JOIN tasks t ON sc.task_id = t.task_id
|
||||
LEFT JOIN work_types wt ON t.work_type_id = wt.id
|
||||
WHERE sc.is_active = 1
|
||||
AND sc.check_type = 'task'
|
||||
AND sc.task_id IN (${placeholders})
|
||||
ORDER BY sc.task_id, sc.display_order
|
||||
`;
|
||||
|
||||
const [rows] = await db.query(sql, taskIds);
|
||||
callback(null, rows);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* TBM 세션에 맞는 필터링된 안전 체크 항목 조회
|
||||
* 기본 + 날씨 + 작업별 체크항목 통합 조회
|
||||
* @param {number} sessionId - TBM 세션 ID
|
||||
* @param {string[]} weatherConditions - 날씨 조건 배열 (optional)
|
||||
*/
|
||||
getFilteredSafetyChecks: async (sessionId, weatherConditions = [], callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
|
||||
// 1. 세션 정보에서 작업 ID 목록 조회
|
||||
const [assignments] = await db.query(`
|
||||
SELECT DISTINCT task_id
|
||||
FROM tbm_team_assignments
|
||||
WHERE session_id = ? AND task_id IS NOT NULL
|
||||
`, [sessionId]);
|
||||
|
||||
const taskIds = assignments.map(a => a.task_id);
|
||||
|
||||
// 2. 기본 체크항목 조회
|
||||
const [basicChecks] = await db.query(`
|
||||
SELECT sc.*, 'basic' as section_type
|
||||
FROM tbm_safety_checks sc
|
||||
WHERE sc.is_active = 1 AND sc.check_type = 'basic'
|
||||
ORDER BY sc.check_category, sc.display_order
|
||||
`);
|
||||
|
||||
// 3. 날씨별 체크항목 조회
|
||||
let weatherChecks = [];
|
||||
if (weatherConditions && weatherConditions.length > 0) {
|
||||
const wcPlaceholders = weatherConditions.map(() => '?').join(',');
|
||||
const [rows] = await db.query(`
|
||||
SELECT sc.*, wc.condition_name as weather_condition_name, wc.icon as weather_icon,
|
||||
'weather' as section_type
|
||||
FROM tbm_safety_checks sc
|
||||
LEFT JOIN weather_conditions wc ON sc.weather_condition = wc.condition_code
|
||||
WHERE sc.is_active = 1
|
||||
AND sc.check_type = 'weather'
|
||||
AND sc.weather_condition IN (${wcPlaceholders})
|
||||
ORDER BY sc.weather_condition, sc.display_order
|
||||
`, weatherConditions);
|
||||
weatherChecks = rows;
|
||||
}
|
||||
|
||||
// 4. 작업별 체크항목 조회
|
||||
let taskChecks = [];
|
||||
if (taskIds.length > 0) {
|
||||
const taskPlaceholders = taskIds.map(() => '?').join(',');
|
||||
const [rows] = await db.query(`
|
||||
SELECT sc.*, t.task_name, wt.name as work_type_name,
|
||||
'task' as section_type
|
||||
FROM tbm_safety_checks sc
|
||||
LEFT JOIN tasks t ON sc.task_id = t.task_id
|
||||
LEFT JOIN work_types wt ON t.work_type_id = wt.id
|
||||
WHERE sc.is_active = 1
|
||||
AND sc.check_type = 'task'
|
||||
AND sc.task_id IN (${taskPlaceholders})
|
||||
ORDER BY sc.task_id, sc.display_order
|
||||
`, taskIds);
|
||||
taskChecks = rows;
|
||||
}
|
||||
|
||||
// 5. 기존 체크 기록 조회
|
||||
const [existingRecords] = await db.query(`
|
||||
SELECT check_id, is_checked, notes
|
||||
FROM tbm_safety_records
|
||||
WHERE session_id = ?
|
||||
`, [sessionId]);
|
||||
|
||||
const recordMap = {};
|
||||
existingRecords.forEach(r => {
|
||||
recordMap[r.check_id] = { is_checked: r.is_checked, notes: r.notes };
|
||||
});
|
||||
|
||||
// 6. 기록과 병합
|
||||
const mergeWithRecords = (checks) => {
|
||||
return checks.map(check => ({
|
||||
...check,
|
||||
is_checked: recordMap[check.check_id]?.is_checked || false,
|
||||
notes: recordMap[check.check_id]?.notes || null
|
||||
}));
|
||||
};
|
||||
|
||||
const result = {
|
||||
basic: mergeWithRecords(basicChecks),
|
||||
weather: mergeWithRecords(weatherChecks),
|
||||
task: mergeWithRecords(taskChecks),
|
||||
totalCount: basicChecks.length + weatherChecks.length + taskChecks.length,
|
||||
weatherConditions: weatherConditions
|
||||
};
|
||||
|
||||
callback(null, result);
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 안전 체크 항목 생성 (관리자용)
|
||||
*/
|
||||
createSafetyCheck: async (checkData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
INSERT INTO tbm_safety_checks
|
||||
(check_category, check_type, weather_condition, task_id, check_item, description, is_required, display_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const values = [
|
||||
checkData.check_category,
|
||||
checkData.check_type || 'basic',
|
||||
checkData.weather_condition || null,
|
||||
checkData.task_id || null,
|
||||
checkData.check_item,
|
||||
checkData.description || null,
|
||||
checkData.is_required !== false,
|
||||
checkData.display_order || 0
|
||||
];
|
||||
|
||||
const [result] = await db.query(sql, values);
|
||||
callback(null, { insertId: result.insertId });
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 안전 체크 항목 수정 (관리자용)
|
||||
*/
|
||||
updateSafetyCheck: async (checkId, checkData, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
const sql = `
|
||||
UPDATE tbm_safety_checks
|
||||
SET check_category = ?,
|
||||
check_type = ?,
|
||||
weather_condition = ?,
|
||||
task_id = ?,
|
||||
check_item = ?,
|
||||
description = ?,
|
||||
is_required = ?,
|
||||
display_order = ?,
|
||||
is_active = ?,
|
||||
updated_at = NOW()
|
||||
WHERE check_id = ?
|
||||
`;
|
||||
|
||||
const values = [
|
||||
checkData.check_category,
|
||||
checkData.check_type || 'basic',
|
||||
checkData.weather_condition || null,
|
||||
checkData.task_id || null,
|
||||
checkData.check_item,
|
||||
checkData.description || null,
|
||||
checkData.is_required !== false,
|
||||
checkData.display_order || 0,
|
||||
checkData.is_active !== false,
|
||||
checkId
|
||||
];
|
||||
|
||||
const [result] = await db.query(sql, values);
|
||||
callback(null, { affectedRows: result.affectedRows });
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 안전 체크 항목 삭제 (비활성화)
|
||||
*/
|
||||
deleteSafetyCheck: async (checkId, callback) => {
|
||||
try {
|
||||
const db = await getDb();
|
||||
// 실제 삭제 대신 비활성화
|
||||
const sql = `UPDATE tbm_safety_checks SET is_active = 0 WHERE check_id = ?`;
|
||||
|
||||
const [result] = await db.query(sql, [checkId]);
|
||||
callback(null, { affectedRows: result.affectedRows });
|
||||
} catch (err) {
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = TbmModel;
|
||||
Reference in New Issue
Block a user