1ㅊㅏ완료
This commit is contained in:
171
backend/api/report/summary/week/detail.get.ts
Normal file
171
backend/api/report/summary/week/detail.get.ts
Normal 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
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user