feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등
- 순찰/점검 기능 개선 (zone-detail 페이지 추가) - 출근/근태 시스템 개선 (연차 조회, 근무현황) - 작업분석 대분류 그룹화 및 마이그레이션 스크립트 - 모바일 네비게이션 UI 추가 - NAS 배포 도구 및 문서 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -182,8 +182,10 @@ class WorkAnalysis {
|
||||
|
||||
// 최근 작업 현황
|
||||
async getRecentWork(startDate, endDate, limit = 50) {
|
||||
// work_type_id가 work_types에 있으면 직접 사용,
|
||||
// 없으면 tasks 테이블을 통해 해당 task의 work_type_id로 공정(대분류) 조회
|
||||
// work_type_id 컬럼에는 task_id가 저장됨 (tasks 테이블 우선 조회)
|
||||
// task_id로 매칭되면 해당 task의 work_type_id로 공정(대분류) 조회
|
||||
// 매칭 안 되면 직접 work_types 테이블 조회 (레거시 데이터 호환)
|
||||
// error_type_id는 issue_report_items 테이블 참조 (issue-categories.html에서 관리)
|
||||
const query = `
|
||||
SELECT
|
||||
dwr.id,
|
||||
@@ -194,13 +196,20 @@ class WorkAnalysis {
|
||||
p.project_name,
|
||||
p.job_no,
|
||||
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,
|
||||
COALESCE(
|
||||
CASE WHEN t.task_id IS NOT NULL THEN t.work_type_id ELSE NULL END,
|
||||
wt.id
|
||||
) as work_type_id,
|
||||
COALESCE(
|
||||
CASE WHEN t.task_id IS NOT NULL THEN wt2.name ELSE NULL END,
|
||||
wt.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,
|
||||
et.name as error_type_name,
|
||||
iri.item_name as error_type_name,
|
||||
irc.category_name as error_category_name,
|
||||
dwr.work_hours,
|
||||
dwr.created_by,
|
||||
u.name as created_by_name,
|
||||
@@ -212,7 +221,8 @@ class WorkAnalysis {
|
||||
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 issue_report_items iri ON dwr.error_type_id = iri.item_id
|
||||
LEFT JOIN issue_report_categories irc ON iri.category_id = irc.category_id
|
||||
LEFT JOIN users u ON dwr.created_by = u.user_id
|
||||
WHERE dwr.report_date BETWEEN ? AND ?
|
||||
ORDER BY dwr.created_at DESC
|
||||
@@ -236,6 +246,7 @@ class WorkAnalysis {
|
||||
work_status_name: row.work_status_name || '정상',
|
||||
error_type_id: row.error_type_id,
|
||||
error_type_name: row.error_type_name || null,
|
||||
error_category_name: row.error_category_name || null,
|
||||
work_hours: parseFloat(row.work_hours) || 0,
|
||||
created_by: row.created_by,
|
||||
created_by_name: row.created_by_name || '미지정',
|
||||
@@ -286,20 +297,23 @@ class WorkAnalysis {
|
||||
}
|
||||
|
||||
// 에러 분석
|
||||
// error_type_id는 issue_report_items 테이블 참조 (issue-categories.html에서 관리)
|
||||
async getErrorAnalysis(startDate, endDate) {
|
||||
const query = `
|
||||
SELECT
|
||||
SELECT
|
||||
dwr.error_type_id,
|
||||
et.name as error_type_name,
|
||||
iri.item_name as error_type_name,
|
||||
irc.category_name as error_category_name,
|
||||
COUNT(*) as error_count,
|
||||
SUM(dwr.work_hours) as total_hours,
|
||||
COUNT(DISTINCT dwr.worker_id) as affected_workers,
|
||||
COUNT(DISTINCT dwr.project_id) as affected_projects
|
||||
FROM daily_work_reports dwr
|
||||
LEFT JOIN error_types et ON dwr.error_type_id = et.id
|
||||
LEFT JOIN issue_report_items iri ON dwr.error_type_id = iri.item_id
|
||||
LEFT JOIN issue_report_categories irc ON iri.category_id = irc.category_id
|
||||
WHERE dwr.report_date BETWEEN ? AND ?
|
||||
AND dwr.work_status_id = 2
|
||||
GROUP BY dwr.error_type_id, et.name
|
||||
GROUP BY dwr.error_type_id, iri.item_name, irc.category_name
|
||||
ORDER BY error_count DESC
|
||||
`;
|
||||
|
||||
@@ -308,6 +322,7 @@ class WorkAnalysis {
|
||||
return results.map(row => ({
|
||||
error_type_id: row.error_type_id,
|
||||
error_type_name: row.error_type_name || `에러유형 ${row.error_type_id}`,
|
||||
error_category_name: row.error_category_name || null,
|
||||
errorCount: parseInt(row.error_count) || 0,
|
||||
totalHours: parseFloat(row.total_hours) || 0,
|
||||
affectedworkers: parseInt(row.affected_workers) || 0,
|
||||
@@ -436,14 +451,23 @@ class WorkAnalysis {
|
||||
}
|
||||
// 프로젝트별-작업별 시간 분석용 데이터 조회 (공정/대분류 기준)
|
||||
async getProjectWorkTypeRawData(startDate, endDate) {
|
||||
// work_type_id가 실제로 task_id인 경우 해당 task의 work_type_id로 공정 조회
|
||||
// work_type_id 컬럼에는 task_id가 저장됨 (tasks 테이블 우선 조회)
|
||||
// task_id로 매칭되면 해당 task의 work_type_id로 공정 조회
|
||||
// 매칭 안 되면 직접 work_types 조회 (레거시 데이터 호환)
|
||||
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,
|
||||
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,
|
||||
COALESCE(
|
||||
CASE WHEN t.task_id IS NOT NULL THEN t.work_type_id ELSE NULL END,
|
||||
wt.id
|
||||
) as work_type_id,
|
||||
COALESCE(
|
||||
CASE WHEN t.task_id IS NOT NULL THEN wt2.name ELSE NULL END,
|
||||
wt.name,
|
||||
CONCAT('작업유형 ', dwr.work_type_id)
|
||||
) as work_type_name,
|
||||
|
||||
-- 총 시간
|
||||
SUM(dwr.work_hours) as total_hours,
|
||||
@@ -472,8 +496,14 @@ class WorkAnalysis {
|
||||
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,
|
||||
COALESCE(wt.id, t.work_type_id),
|
||||
COALESCE(wt.name, wt2.name)
|
||||
COALESCE(
|
||||
CASE WHEN t.task_id IS NOT NULL THEN t.work_type_id ELSE NULL END,
|
||||
wt.id
|
||||
),
|
||||
COALESCE(
|
||||
CASE WHEN t.task_id IS NOT NULL THEN wt2.name ELSE NULL END,
|
||||
wt.name
|
||||
)
|
||||
ORDER BY p.project_name, work_type_name
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user