feat: 다수 기능 개선 - 순찰, 출근, 작업분석, 모바일 UI 등

- 순찰/점검 기능 개선 (zone-detail 페이지 추가)
- 출근/근태 시스템 개선 (연차 조회, 근무현황)
- 작업분석 대분류 그룹화 및 마이그레이션 스크립트
- 모바일 네비게이션 UI 추가
- NAS 배포 도구 및 문서 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-09 14:41:01 +09:00
parent 1548253f56
commit 2b1c7bfb88
633 changed files with 361224 additions and 1090 deletions

View File

@@ -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
`;