import { defineEventHandler, getQuery, createError } from 'h3' import { query } from '../../../../utils/db' /** * 주차별 취합 상세 (프로젝트별 실적/계획 테이블용) * GET /api/report/summary/week/detail?year=2026&week=1 */ export default defineEventHandler(async (event) => { const queryParams = getQuery(event) const year = queryParams.year ? parseInt(queryParams.year as string) : null const week = queryParams.week ? parseInt(queryParams.week as string) : null if (!year || !week) { throw createError({ statusCode: 400, message: '연도와 주차를 지정해주세요.' }) } // 해당 주차 취합 보고서 목록 const summaries = await query(` SELECT s.summary_id, s.project_id, p.project_name, p.project_code, s.week_start_date, s.week_end_date, s.member_count, s.total_work_hours, s.ai_work_summary, s.ai_plan_summary, s.ai_summary_at, s.summary_status, s.aggregated_at FROM wr_aggregated_report_summary s JOIN wr_project_info p ON s.project_id = p.project_id WHERE s.report_year = $1 AND s.report_week = $2 ORDER BY p.project_name `, [year, week]) if (summaries.length === 0) { throw createError({ statusCode: 404, message: '해당 주차의 취합 보고서가 없습니다.' }) } // 프로젝트 ID 목록 const projectIds = summaries.map((s: any) => s.project_id) // 해당 주차/프로젝트의 모든 Task 조회 const tasks = await query(` SELECT t.project_id, t.task_type, t.task_description, t.task_hours, t.is_completed, r.author_id, e.employee_name as author_name FROM wr_weekly_report r JOIN wr_weekly_report_task t ON r.report_id = t.report_id JOIN wr_employee_info e ON r.author_id = e.employee_id WHERE r.report_year = $1 AND r.report_week = $2 AND t.project_id = ANY($3) AND r.report_status IN ('SUBMITTED', 'AGGREGATED') ORDER BY t.project_id, t.task_type, e.employee_name `, [year, week, projectIds]) // Task를 프로젝트별로 그룹핑 + 프로젝트별 인원별 시간 집계 (실적/계획 통합) const tasksByProject = new Map() const membersByProject = new Map>() for (const task of tasks) { // Task 그룹핑 if (!tasksByProject.has(task.project_id)) { tasksByProject.set(task.project_id, { work: [], plan: [] }) } const group = tasksByProject.get(task.project_id)! const taskItem = { description: task.task_description, hours: parseFloat(task.task_hours) || 0, isCompleted: task.is_completed, authorId: task.author_id, authorName: task.author_name } // 프로젝트별 인원별 시간 집계 if (!membersByProject.has(task.project_id)) { membersByProject.set(task.project_id, new Map()) } const members = membersByProject.get(task.project_id)! if (!members.has(task.author_id)) { members.set(task.author_id, { name: task.author_name, workHours: 0, planHours: 0 }) } const hours = parseFloat(task.task_hours) || 0 const member = members.get(task.author_id)! if (task.task_type === 'WORK') { group.work.push(taskItem) member.workHours += hours } else { group.plan.push(taskItem) member.planHours += hours } } // 전체 인원별 시간 집계 const memberHours = new Map() for (const task of tasks) { const authorId = task.author_id if (!memberHours.has(authorId)) { memberHours.set(authorId, { name: task.author_name, workHours: 0, planHours: 0 }) } const member = memberHours.get(authorId)! const hours = parseFloat(task.task_hours) || 0 if (task.task_type === 'WORK') { member.workHours += hours } else { member.planHours += hours } } // 첫번째 summary에서 날짜 정보 추출 const weekInfo = { reportYear: year, reportWeek: week, weekStartDate: summaries[0].week_start_date, weekEndDate: summaries[0].week_end_date, totalProjects: summaries.length, totalWorkHours: summaries.reduce((sum: number, s: any) => sum + (parseFloat(s.total_work_hours) || 0), 0) } // 프로젝트별 데이터 구성 const projects = summaries.map((s: any) => { const taskGroup = tasksByProject.get(s.project_id) || { work: [], plan: [] } const projectMembers = membersByProject.get(s.project_id) // 프로젝트별 인원 시간 배열 (실적+계획 통합) const memberHoursList = projectMembers ? Array.from(projectMembers.values()).sort((a, b) => (b.workHours + b.planHours) - (a.workHours + a.planHours)) : [] return { summaryId: s.summary_id, projectId: s.project_id, projectName: s.project_name, projectCode: s.project_code, memberCount: s.member_count, totalWorkHours: parseFloat(s.total_work_hours) || 0, aiWorkSummary: s.ai_work_summary, aiPlanSummary: s.ai_plan_summary, aiSummaryAt: s.ai_summary_at, workTasks: taskGroup.work, planTasks: taskGroup.plan, memberHours: memberHoursList // { name, workHours, planHours } } }) // 전체 인원별 시간 배열로 변환 const members = Array.from(memberHours.entries()).map(([id, m]) => ({ employeeId: id, employeeName: m.name, workHours: m.workHours, planHours: m.planHours, availableHours: Math.max(0, 40 - m.planHours) })).sort((a, b) => b.availableHours - a.availableHours) return { weekInfo, projects, members } })