From 1548253f56d2c31e1b554060ec44557ffb4299ce Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Thu, 5 Feb 2026 10:29:08 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9E=91=EC=97=85=20=EB=B6=84=EC=84=9D?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EA=B3=B5=EC=A0=95(=EB=8C=80=EB=B6=84?= =?UTF-8?q?=EB=A5=98)=EC=9C=BC=EB=A1=9C=20=EC=98=AC=EB=B0=94=EB=A5=B4?= =?UTF-8?q?=EA=B2=8C=20=EB=B6=84=EB=A5=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 문제: work_type_id에 task_id가 저장된 경우 공정 분류가 안됨 - work_type_id=10 → 실제로는 task "노즐 용접" (공정: Vessel) 해결: - API에서 task_id인 경우 해당 task의 work_type_id로 공정 조회 - getRecentWork, getProjectWorkTypeRawData 쿼리 수정 - 프론트엔드는 API 결과의 work_type_name 직접 사용 공정(대분류): Base(구조물), Vessel(용기), Piping Assembly(배관), 작업대기, 휴무, 시설설비 Co-Authored-By: Claude Opus 4.5 --- api.hyungi.net/models/WorkAnalysis.js | 30 ++++++++++++------ web-ui/pages/work/analysis.html | 45 +++++++++------------------ 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/api.hyungi.net/models/WorkAnalysis.js b/api.hyungi.net/models/WorkAnalysis.js index 855d14a..f39e5b1 100644 --- a/api.hyungi.net/models/WorkAnalysis.js +++ b/api.hyungi.net/models/WorkAnalysis.js @@ -182,8 +182,10 @@ class WorkAnalysis { // 최근 작업 현황 async getRecentWork(startDate, endDate, limit = 50) { + // work_type_id가 work_types에 있으면 직접 사용, + // 없으면 tasks 테이블을 통해 해당 task의 work_type_id로 공정(대분류) 조회 const query = ` - SELECT + SELECT dwr.id, dwr.report_date, dwr.worker_id, @@ -191,8 +193,10 @@ class WorkAnalysis { dwr.project_id, p.project_name, p.job_no, - dwr.work_type_id, - wt.name as work_type_name, + dwr.work_type_id as original_work_type_id, + COALESCE(wt.id, t.work_type_id) as work_type_id, + COALESCE(wt.name, wt2.name) as work_type_name, + t.task_name as task_name, dwr.work_status_id, wst.name as work_status_name, dwr.error_type_id, @@ -205,6 +209,8 @@ class WorkAnalysis { LEFT JOIN workers w ON dwr.worker_id = w.worker_id LEFT JOIN projects p ON dwr.project_id = p.project_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.id + LEFT JOIN tasks t ON dwr.work_type_id = t.task_id + LEFT JOIN work_types wt2 ON t.work_type_id = wt2.id LEFT JOIN work_status_types wst ON dwr.work_status_id = wst.id LEFT JOIN error_types et ON dwr.error_type_id = et.id LEFT JOIN users u ON dwr.created_by = u.user_id @@ -224,7 +230,8 @@ class WorkAnalysis { project_name: row.project_name || `프로젝트 ${row.project_id}`, job_no: row.job_no || 'N/A', work_type_id: row.work_type_id, - work_type_name: row.work_type_name || `작업유형 ${row.work_type_id}`, + work_type_name: row.work_type_name || `작업유형 ${row.original_work_type_id}`, + task_name: row.task_name || null, work_status_id: row.work_status_id, work_status_name: row.work_status_name || '정상', error_type_id: row.error_type_id, @@ -427,15 +434,16 @@ class WorkAnalysis { throw new Error(`대시보드 데이터 조회 실패: ${error.message}`); } } - // 프로젝트별-작업별 시간 분석용 데이터 조회 + // 프로젝트별-작업별 시간 분석용 데이터 조회 (공정/대분류 기준) async getProjectWorkTypeRawData(startDate, endDate) { + // work_type_id가 실제로 task_id인 경우 해당 task의 work_type_id로 공정 조회 const query = ` SELECT COALESCE(p.project_id, dwr.project_id) as project_id, COALESCE(p.project_name, CONCAT('프로젝트 ', dwr.project_id)) as project_name, COALESCE(p.job_no, 'N/A') as job_no, - dwr.work_type_id, - COALESCE(wt.name, CONCAT('작업유형 ', dwr.work_type_id)) as work_type_name, + COALESCE(wt.id, t.work_type_id) as work_type_id, + COALESCE(wt.name, wt2.name, CONCAT('작업유형 ', dwr.work_type_id)) as work_type_name, -- 총 시간 SUM(dwr.work_hours) as total_hours, @@ -460,9 +468,13 @@ class WorkAnalysis { FROM daily_work_reports dwr LEFT JOIN projects p ON dwr.project_id = p.project_id LEFT JOIN work_types wt ON dwr.work_type_id = wt.id + LEFT JOIN tasks t ON dwr.work_type_id = t.task_id + LEFT JOIN work_types wt2 ON t.work_type_id = wt2.id WHERE dwr.report_date BETWEEN ? AND ? - GROUP BY dwr.project_id, p.project_name, p.job_no, dwr.work_type_id, wt.name - ORDER BY p.project_name, wt.name + GROUP BY dwr.project_id, p.project_name, p.job_no, + COALESCE(wt.id, t.work_type_id), + COALESCE(wt.name, wt2.name) + ORDER BY p.project_name, work_type_name `; try { diff --git a/web-ui/pages/work/analysis.html b/web-ui/pages/work/analysis.html index fb10bd3..e4f32ad 100644 --- a/web-ui/pages/work/analysis.html +++ b/web-ui/pages/work/analysis.html @@ -983,22 +983,6 @@ return WorkAnalysis.utils.isVacationProject(projectName); } - // 대분류 매핑 함수 (work_type_id → 대분류) - function getMajorCategory(workTypeId) { - // 대분류 매핑: 1=Base제작, 2=용기제작, 3=파이핑 - const majorCategories = { - 1: { id: 1, name: 'Base제작' }, - 2: { id: 2, name: '용기제작' }, - 3: { id: 3, name: '파이핑' }, - 4: { id: 4, name: '작업대기' }, - 11: { id: 11, name: '휴무' }, - 12: { id: 12, name: '시설설비' } - }; - - // 매핑된 대분류가 있으면 반환, 없으면 기타 - return majorCategories[workTypeId] || { id: 0, name: '기타' }; - } - function getKSTDate() { return WorkAnalysis.utils.getKSTDate(); } @@ -1519,20 +1503,20 @@ vacationData.regularHours += hours; } } else { - // 일반 프로젝트 처리 - 대분류로 그룹화 - const majorCategory = getMajorCategory(work.work_type_id); + // 일반 프로젝트 처리 - API에서 반환된 공정(대분류) 사용 const projectName = work.project_name || `프로젝트 ${work.project_id}`; + const workTypeName = work.work_type_name || '기타'; - // 대분류 기준으로 집계 (프로젝트별로 구분) - const combinedKey = `${work.project_id || 'unknown'}_${majorCategory.id}`; + // 공정(대분류) 기준으로 집계 (프로젝트별로 구분) + const combinedKey = `${work.project_id || 'unknown'}_${work.work_type_id || 0}`; if (!workTypeMap.has(combinedKey)) { workTypeMap.set(combinedKey, { project_id: work.project_id, project_name: projectName, job_no: work.job_no, - work_type_id: majorCategory.id, - work_type_name: majorCategory.name, + work_type_id: work.work_type_id, + work_type_name: workTypeName, regularHours: 0, errorHours: 0, errorDetails: new Map(), // 오류 유형별 세분화 @@ -1838,14 +1822,14 @@ const project = projectMap.get(projectKey); - // 대분류로 그룹화 - const majorCategory = getMajorCategory(work.work_type_id); - const workTypeKey = `${majorCategory.id}_${majorCategory.name}`; + // API에서 반환된 공정(대분류) 사용 + const workTypeName = work.work_type_name || '기타'; + const workTypeKey = `${work.work_type_id || 0}_${workTypeName}`; if (!project.work_types.has(workTypeKey)) { project.work_types.set(workTypeKey, { - work_type_id: majorCategory.id, - work_type_name: majorCategory.name, + work_type_id: work.work_type_id, + work_type_name: workTypeName, total_hours: 0, regular_hours: 0, error_hours: 0, @@ -2505,14 +2489,13 @@ workerGroupedData[workerName][projectName] = {}; } - // 대분류로 작업내용 그룹화 + // 작업내용 - API에서 공정(대분류) 이름을 직접 반환 let finalWorkTypeName; if (isVacationProject(projectName)) { finalWorkTypeName = '-'; // 연차/휴무는 작업내용을 '-'로 통합 } else { - // 대분류 매핑 사용 - const majorCategory = getMajorCategory(work.work_type_id); - finalWorkTypeName = majorCategory.name; + // API에서 반환된 work_type_name 사용 (공정/대분류) + finalWorkTypeName = work.work_type_name || '기타'; } if (!workerGroupedData[workerName][projectName][finalWorkTypeName]) {