/** * 대리입력 + 일별 현황 모델 */ const { getDb } = require('../dbPool'); const ProxyInputModel = { /** * 중복 배정 체크 (같은 날짜 + 같은 작업자) */ checkDuplicateAssignments: async (conn, sessionDate, userIds) => { if (!userIds.length) return []; const placeholders = userIds.map(() => '?').join(','); const [rows] = await conn.query(` SELECT ta.user_id, w.worker_name, ta.session_id FROM tbm_team_assignments ta JOIN tbm_sessions s ON ta.session_id = s.session_id JOIN workers w ON ta.user_id = w.worker_id WHERE s.session_date = ? AND ta.user_id IN (${placeholders}) AND s.status != 'cancelled' `, [sessionDate, ...userIds]); return rows; }, /** * 작업자 존재 여부 체크 */ validateWorkers: async (conn, userIds) => { if (!userIds.length) return []; const placeholders = userIds.map(() => '?').join(','); const [rows] = await conn.query(` SELECT worker_id FROM workers WHERE worker_id IN (${placeholders}) AND status = 'active' `, [...userIds]); return rows.map(r => r.worker_id); }, /** * TBM 세션 생성 (대리입력) */ createProxySession: async (conn, data) => { const [result] = await conn.query(` INSERT INTO tbm_sessions (session_date, leader_user_id, status, is_proxy_input, proxy_input_by, created_by, safety_notes, work_location) VALUES (?, ?, 'completed', 1, ?, ?, ?, ?) `, [data.session_date, data.leader_id, data.proxy_input_by, data.created_by, data.safety_notes || '', data.work_location || '']); return result; }, /** * 팀 배정 생성 */ createTeamAssignment: async (conn, data) => { const [result] = await conn.query(` INSERT INTO tbm_team_assignments (session_id, user_id, project_id, work_type_id, task_id, workplace_id, work_hours, is_present) VALUES (?, ?, ?, ?, ?, ?, ?, 1) `, [data.session_id, data.user_id, data.project_id, data.work_type_id, data.task_id || null, data.workplace_id || null, data.work_hours]); return result; }, /** * 작업보고서 생성 (accumulative) */ createWorkReport: async (conn, data) => { const [result] = await conn.query(` INSERT INTO daily_work_reports (report_date, user_id, project_id, work_type_id, task_id, work_status_id, work_hours, start_time, end_time, note, tbm_session_id, tbm_assignment_id, created_by, created_by_name, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) `, [data.report_date, data.user_id, data.project_id, data.work_type_id, data.task_id || null, data.work_status_id || 1, data.work_hours, data.start_time || null, data.end_time || null, data.note || '', data.tbm_session_id, data.tbm_assignment_id, data.created_by, data.created_by_name || '']); return result; }, /** * 일별 현황 조회 */ getDailyStatus: async (date) => { const db = await getDb(); // 1. 활성 작업자 const [workers] = await db.query(` SELECT w.worker_id AS user_id, w.worker_name, w.job_type, COALESCE(d.department_name, '미배정') AS department_name FROM workers w LEFT JOIN departments d ON w.department_id = d.department_id WHERE w.status = 'active' ORDER BY w.worker_name `); // 2. TBM 배정 현황 const [tbmAssignments] = await db.query(` SELECT ta.user_id, ta.session_id, s.leader_user_id, lu.worker_name AS leader_name, s.is_proxy_input FROM tbm_team_assignments ta JOIN tbm_sessions s ON ta.session_id = s.session_id LEFT JOIN workers lu ON s.leader_user_id = lu.worker_id WHERE s.session_date = ? AND s.status != 'cancelled' `, [date]); // 3. 작업보고서 현황 const [reports] = await db.query(` SELECT dwr.user_id, SUM(dwr.work_hours) AS total_hours, COUNT(*) AS entry_count FROM daily_work_reports dwr WHERE dwr.report_date = ? GROUP BY dwr.user_id `, [date]); // 메모리에서 조합 const tbmMap = {}; tbmAssignments.forEach(ta => { if (!tbmMap[ta.user_id]) tbmMap[ta.user_id] = []; tbmMap[ta.user_id].push(ta); }); const reportMap = {}; reports.forEach(r => { reportMap[r.user_id] = r; }); let tbmCompleted = 0, reportCompleted = 0, bothCompleted = 0, bothMissing = 0; const workerList = workers.map(w => { const hasTbm = !!tbmMap[w.user_id]; const hasReport = !!reportMap[w.user_id]; const tbmSessions = (tbmMap[w.user_id] || []).map(ta => ({ session_id: ta.session_id, leader_name: ta.leader_name, is_proxy_input: !!ta.is_proxy_input })); const totalReportHours = reportMap[w.user_id]?.total_hours || 0; let status = 'both_missing'; if (hasTbm && hasReport) { status = 'complete'; bothCompleted++; } else if (hasTbm && !hasReport) { status = 'tbm_only'; } else if (!hasTbm && hasReport) { status = 'report_only'; } else { bothMissing++; } if (hasTbm) tbmCompleted++; if (hasReport) reportCompleted++; return { user_id: w.user_id, worker_name: w.worker_name, job_type: w.job_type, department_name: w.department_name, has_tbm: hasTbm, has_report: hasReport, tbm_sessions: tbmSessions, total_report_hours: totalReportHours, status }; }); return { date, summary: { total_active_workers: workers.length, tbm_completed: tbmCompleted, tbm_missing: workers.length - tbmCompleted, report_completed: reportCompleted, report_missing: workers.length - reportCompleted, both_completed: bothCompleted, both_missing: bothMissing }, workers: workerList }; }, /** * 작업자별 상세 조회 */ getDailyStatusDetail: async (date, userId) => { const db = await getDb(); // 작업자 정보 const [workerRows] = await db.query(` SELECT w.worker_id AS user_id, w.worker_name, w.job_type, COALESCE(d.department_name, '미배정') AS department_name FROM workers w LEFT JOIN departments d ON w.department_id = d.department_id WHERE w.worker_id = ? `, [userId]); // TBM 세션 const [tbmSessions] = await db.query(` SELECT ta.session_id, s.status, s.is_proxy_input, lu.worker_name AS leader_name, pu.name AS proxy_input_by_name, p.project_name, wt.work_type_name, ta.work_hours FROM tbm_team_assignments ta JOIN tbm_sessions s ON ta.session_id = s.session_id LEFT JOIN workers lu ON s.leader_user_id = lu.worker_id LEFT JOIN sso_users pu ON s.proxy_input_by = pu.user_id LEFT JOIN projects p ON ta.project_id = p.project_id LEFT JOIN work_types wt ON ta.work_type_id = wt.work_type_id WHERE s.session_date = ? AND ta.user_id = ? AND s.status != 'cancelled' `, [date, userId]); // 작업보고서 const [workReports] = await db.query(` SELECT dwr.report_id, dwr.work_hours, dwr.created_at, dwr.created_by, cu.name AS created_by_name, p.project_name, wt.work_type_name, t.task_name, ws.status_name AS work_status, s.is_proxy_input FROM daily_work_reports dwr LEFT JOIN sso_users cu ON dwr.created_by = cu.user_id LEFT JOIN projects p ON dwr.project_id = p.project_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.work_type_id LEFT JOIN tasks t ON dwr.task_id = t.task_id LEFT JOIN work_statuses ws ON dwr.work_status_id = ws.work_status_id LEFT JOIN tbm_sessions s ON dwr.tbm_session_id = s.session_id WHERE dwr.report_date = ? AND dwr.user_id = ? ORDER BY dwr.created_at `, [date, userId]); return { worker: workerRows[0] || null, tbm_sessions: tbmSessions, work_reports: workReports }; } }; module.exports = ProxyInputModel;