1ㅊㅏ완료

This commit is contained in:
2026-01-05 02:00:13 +09:00
parent 1bbad6efa7
commit 185161db16
30 changed files with 4331 additions and 837 deletions

View File

@@ -0,0 +1,171 @@
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<number, { work: any[], plan: any[] }>()
const membersByProject = new Map<number, Map<number, { name: string, workHours: number, planHours: number }>>()
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<number, { name: string, workHours: number, planHours: number }>()
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
}
})