// /js/project-analysis-data.js /** * 근무 형태에 따른 실제 투입 시간을 계산합니다. * 잔업(OT)은 1.5배 가산됩니다. * @param {string} workDetails - 근무 형태 (예: '정상', '연차', '반차') * @param {number} overtimeHours - 잔업 시간 * @returns {number} - 실제 투입 시간 */ function calculateActualWorkHours(workDetails, overtimeHours) { let baseHours = 8; switch(workDetails) { case '연차': case '휴무': case '유급': baseHours = 0; break; case '반차': baseHours = 4; break; case '반반차': baseHours = 6; break; case '조퇴': baseHours = 2; break; default: baseHours = 8; // 정상근무 } return baseHours + (overtimeHours || 0) * 1.5; } /** * 원본 작업 보고서 데이터에 마스터 데이터를 매핑하고 가공합니다. * @param {Array} rawReports - 원본 작업 보고서 * @param {{workers: Array, projects: Array, tasks: Array}} masterData - 마스터 데이터 * @returns {Array} - 가공된 데이터 */ export function processRawData(rawReports, masterData) { if (!rawReports || !masterData) return []; const { workers, projects, tasks } = masterData; const workerMap = new Map(workers.map(w => [w.worker_id, w.worker_name])); const projectMap = new Map(projects.map(p => [p.project_id, p.project_name])); const taskMap = new Map(tasks.map(t => [t.task_id, t.category])); return rawReports .map(item => ({ ...item, worker_name: workerMap.get(item.worker_id) || '알 수 없음', project_name: projectMap.get(item.project_id) || `프로젝트 ID ${item.project_id}`, task_category: taskMap.get(item.task_id) || `작업 ID ${item.task_id}`, work_hours: calculateActualWorkHours(item.work_details, item.overtime_hours), })) // 실제 투입 시간이 있고, 완전 휴가가 아닌 유효한 데이터만 필터링 .filter(item => item.work_hours > 0 && !['연차', '휴무', '유급'].includes(item.work_details)); } /** * 주어진 데이터셋을 필터링합니다. * @param {Array} data - 가공된 전체 데이터 * @param {{project: string, worker: string, task: string}} filters - 필터 조건 * @returns {Array} - 필터링된 데이터 */ export function applyFilters(data, filters) { return data.filter(item => { const projectMatch = !filters.project || item.project_name === filters.project; const workerMatch = !filters.worker || item.worker_name === filters.worker; const taskMatch = !filters.task || item.task_category === filters.task; return projectMatch && workerMatch && taskMatch; }); } /** * 데이터를 특정 키(프로젝트, 작업자, 작업)로 집계합니다. * @param {Array} data - 집계할 데이터 * @param {'project_name' | 'worker_name' | 'task_category'} key - 집계 기준 키 * @returns {Array} - 집계된 데이터 */ function aggregateData(data, key) { const aggregated = data.reduce((acc, item) => { const group = item[key]; if (!acc[group]) { acc[group] = { name: group, hours: 0, // 참여자 또는 참여 프로젝트를 추적하기 위한 Set participants: new Set(), }; } acc[group].hours += item.work_hours; // 집계 키에 따라 다른 종류의 참여자를 추가 if (key === 'project_name') acc[group].participants.add(item.worker_name); else if (key === 'worker_name') acc[group].participants.add(item.project_name); else if (key === 'task_category') acc[group].participants.add(item.worker_name); return acc; }, {}); return Object.values(aggregated).sort((a, b) => b.hours - a.hours); } /** * 필터링된 데이터를 기반으로 모든 분석 데이터를 생성합니다. * @param {Array} filteredData - 필터링된 데이터 * @returns {object} - 요약, 프로젝트별, 작업자별, 작업별 집계 데이터 */ export function getAnalysis(filteredData) { const totalHours = filteredData.reduce((sum, item) => sum + (item.work_hours || 0), 0); const summary = { totalHours, totalProjects: new Set(filteredData.map(item => item.project_name)).size, totalWorkers: new Set(filteredData.map(item => item.worker_name)).size, totalTasks: new Set(filteredData.map(item => item.task_category)).size, }; const byProject = aggregateData(filteredData, 'project_name'); const byWorker = aggregateData(filteredData, 'worker_name'); const byTask = aggregateData(filteredData, 'task_category'); return { summary, byProject, byWorker, byTask }; }