fix: 작업 분석에서 공정(대분류)으로 올바르게 분류

문제: 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 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-05 10:29:08 +09:00
parent 0ea253befd
commit 1548253f56
2 changed files with 35 additions and 40 deletions

View File

@@ -182,8 +182,10 @@ class WorkAnalysis {
// 최근 작업 현황 // 최근 작업 현황
async getRecentWork(startDate, endDate, limit = 50) { async getRecentWork(startDate, endDate, limit = 50) {
// work_type_id가 work_types에 있으면 직접 사용,
// 없으면 tasks 테이블을 통해 해당 task의 work_type_id로 공정(대분류) 조회
const query = ` const query = `
SELECT SELECT
dwr.id, dwr.id,
dwr.report_date, dwr.report_date,
dwr.worker_id, dwr.worker_id,
@@ -191,8 +193,10 @@ class WorkAnalysis {
dwr.project_id, dwr.project_id,
p.project_name, p.project_name,
p.job_no, p.job_no,
dwr.work_type_id, dwr.work_type_id as original_work_type_id,
wt.name as work_type_name, 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, dwr.work_status_id,
wst.name as work_status_name, wst.name as work_status_name,
dwr.error_type_id, dwr.error_type_id,
@@ -205,6 +209,8 @@ class WorkAnalysis {
LEFT JOIN workers w ON dwr.worker_id = w.worker_id LEFT JOIN workers w ON dwr.worker_id = w.worker_id
LEFT JOIN projects p ON dwr.project_id = p.project_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 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 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 error_types et ON dwr.error_type_id = et.id
LEFT JOIN users u ON dwr.created_by = u.user_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}`, project_name: row.project_name || `프로젝트 ${row.project_id}`,
job_no: row.job_no || 'N/A', job_no: row.job_no || 'N/A',
work_type_id: row.work_type_id, 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_id: row.work_status_id,
work_status_name: row.work_status_name || '정상', work_status_name: row.work_status_name || '정상',
error_type_id: row.error_type_id, error_type_id: row.error_type_id,
@@ -427,15 +434,16 @@ class WorkAnalysis {
throw new Error(`대시보드 데이터 조회 실패: ${error.message}`); throw new Error(`대시보드 데이터 조회 실패: ${error.message}`);
} }
} }
// 프로젝트별-작업별 시간 분석용 데이터 조회 // 프로젝트별-작업별 시간 분석용 데이터 조회 (공정/대분류 기준)
async getProjectWorkTypeRawData(startDate, endDate) { async getProjectWorkTypeRawData(startDate, endDate) {
// work_type_id가 실제로 task_id인 경우 해당 task의 work_type_id로 공정 조회
const query = ` const query = `
SELECT SELECT
COALESCE(p.project_id, dwr.project_id) as project_id, COALESCE(p.project_id, dwr.project_id) as project_id,
COALESCE(p.project_name, CONCAT('프로젝트 ', dwr.project_id)) as project_name, COALESCE(p.project_name, CONCAT('프로젝트 ', dwr.project_id)) as project_name,
COALESCE(p.job_no, 'N/A') as job_no, COALESCE(p.job_no, 'N/A') as job_no,
dwr.work_type_id, COALESCE(wt.id, t.work_type_id) as work_type_id,
COALESCE(wt.name, CONCAT('작업유형 ', dwr.work_type_id)) as work_type_name, COALESCE(wt.name, wt2.name, CONCAT('작업유형 ', dwr.work_type_id)) as work_type_name,
-- 총 시간 -- 총 시간
SUM(dwr.work_hours) as total_hours, SUM(dwr.work_hours) as total_hours,
@@ -460,9 +468,13 @@ class WorkAnalysis {
FROM daily_work_reports dwr FROM daily_work_reports dwr
LEFT JOIN projects p ON dwr.project_id = p.project_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 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 ? WHERE dwr.report_date BETWEEN ? AND ?
GROUP BY dwr.project_id, p.project_name, p.job_no, dwr.work_type_id, wt.name GROUP BY dwr.project_id, p.project_name, p.job_no,
ORDER BY p.project_name, wt.name COALESCE(wt.id, t.work_type_id),
COALESCE(wt.name, wt2.name)
ORDER BY p.project_name, work_type_name
`; `;
try { try {

View File

@@ -983,22 +983,6 @@
return WorkAnalysis.utils.isVacationProject(projectName); 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() { function getKSTDate() {
return WorkAnalysis.utils.getKSTDate(); return WorkAnalysis.utils.getKSTDate();
} }
@@ -1519,20 +1503,20 @@
vacationData.regularHours += hours; vacationData.regularHours += hours;
} }
} else { } else {
// 일반 프로젝트 처리 - 대분류로 그룹화 // 일반 프로젝트 처리 - API에서 반환된 공정(대분류) 사용
const majorCategory = getMajorCategory(work.work_type_id);
const projectName = work.project_name || `프로젝트 ${work.project_id}`; 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)) { if (!workTypeMap.has(combinedKey)) {
workTypeMap.set(combinedKey, { workTypeMap.set(combinedKey, {
project_id: work.project_id, project_id: work.project_id,
project_name: projectName, project_name: projectName,
job_no: work.job_no, job_no: work.job_no,
work_type_id: majorCategory.id, work_type_id: work.work_type_id,
work_type_name: majorCategory.name, work_type_name: workTypeName,
regularHours: 0, regularHours: 0,
errorHours: 0, errorHours: 0,
errorDetails: new Map(), // 오류 유형별 세분화 errorDetails: new Map(), // 오류 유형별 세분화
@@ -1838,14 +1822,14 @@
const project = projectMap.get(projectKey); const project = projectMap.get(projectKey);
// 대분류로 그룹화 // API에서 반환된 공정(대분류) 사용
const majorCategory = getMajorCategory(work.work_type_id); const workTypeName = work.work_type_name || '기타';
const workTypeKey = `${majorCategory.id}_${majorCategory.name}`; const workTypeKey = `${work.work_type_id || 0}_${workTypeName}`;
if (!project.work_types.has(workTypeKey)) { if (!project.work_types.has(workTypeKey)) {
project.work_types.set(workTypeKey, { project.work_types.set(workTypeKey, {
work_type_id: majorCategory.id, work_type_id: work.work_type_id,
work_type_name: majorCategory.name, work_type_name: workTypeName,
total_hours: 0, total_hours: 0,
regular_hours: 0, regular_hours: 0,
error_hours: 0, error_hours: 0,
@@ -2505,14 +2489,13 @@
workerGroupedData[workerName][projectName] = {}; workerGroupedData[workerName][projectName] = {};
} }
// 대분류로 작업내용 그룹화 // 작업내용 - API에서 공정(대분류) 이름을 직접 반환
let finalWorkTypeName; let finalWorkTypeName;
if (isVacationProject(projectName)) { if (isVacationProject(projectName)) {
finalWorkTypeName = '-'; // 연차/휴무는 작업내용을 '-'로 통합 finalWorkTypeName = '-'; // 연차/휴무는 작업내용을 '-'로 통합
} else { } else {
// 대분류 매핑 사용 // API에서 반환된 work_type_name 사용 (공정/대분류)
const majorCategory = getMajorCategory(work.work_type_id); finalWorkTypeName = work.work_type_name || '기타';
finalWorkTypeName = majorCategory.name;
} }
if (!workerGroupedData[workerName][projectName][finalWorkTypeName]) { if (!workerGroupedData[workerName][projectName][finalWorkTypeName]) {