diff options
Diffstat (limited to 'lib/evaluation/service.ts')
| -rw-r--r-- | lib/evaluation/service.ts | 124 |
1 files changed, 109 insertions, 15 deletions
diff --git a/lib/evaluation/service.ts b/lib/evaluation/service.ts index 76811753..8e394f88 100644 --- a/lib/evaluation/service.ts +++ b/lib/evaluation/service.ts @@ -9,6 +9,7 @@ import { periodicEvaluationsView, regEvalCriteria, regEvalCriteriaDetails, + reviewerEvaluationAttachments, reviewerEvaluationDetails, reviewerEvaluations, roles, @@ -32,6 +33,7 @@ import { revalidatePath } from "next/cache" import { DEPARTMENT_CODE_LABELS } from "@/types/evaluation" import { getServerSession } from "next-auth" import { authOptions } from "@/app/api/auth/[...nextauth]/route" +import { AttachmentDetail, EvaluationDetailResponse } from "@/types/evaluation-form" export async function getPeriodicEvaluations(input: GetEvaluationTargetsSchema) { try { @@ -989,17 +991,7 @@ export interface EvaluationDetailData { /** * 특정 정기평가의 상세 정보를 조회합니다 */ -export async function getEvaluationDetails(periodicEvaluationId: number): Promise<{ - evaluationInfo: { - id: number - vendorName: string - vendorCode: string - evaluationYear: number - division: string - status: string - } - reviewerDetails: EvaluationDetailData[] -}> { +export async function getEvaluationDetails(periodicEvaluationId: number): Promise<EvaluationDetailResponse> { try { // 1. 평가 기본 정보 조회 const evaluationInfo = await db @@ -1060,11 +1052,90 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis .where(eq(reviewerEvaluations.periodicEvaluationId, periodicEvaluationId)) .orderBy(evaluationTargetReviewers.departmentCode, regEvalCriteria.category, regEvalCriteria.classification) - // 3. 리뷰어별로 그룹화 + // 📎 3. 첨부파일 정보 조회 + const attachmentsData = await db + .select({ + // 첨부파일 정보 + attachmentId: reviewerEvaluationAttachments.id, + originalFileName: reviewerEvaluationAttachments.originalFileName, + storedFileName: reviewerEvaluationAttachments.storedFileName, + publicPath: reviewerEvaluationAttachments.publicPath, + fileSize: reviewerEvaluationAttachments.fileSize, + mimeType: reviewerEvaluationAttachments.mimeType, + fileExtension: reviewerEvaluationAttachments.fileExtension, + description: reviewerEvaluationAttachments.description, + uploadedBy: reviewerEvaluationAttachments.uploadedBy, + attachmentCreatedAt: reviewerEvaluationAttachments.createdAt, + + // 업로드한 사용자 정보 + uploadedByName: users.name, + + // 평가 세부사항 정보 + evaluationDetailId: reviewerEvaluationDetails.id, + reviewerEvaluationId: reviewerEvaluationDetails.reviewerEvaluationId, + + // 평가 기준 정보 (질문 식별용) + criteriaId: regEvalCriteriaDetails.criteriaId, + }) + .from(reviewerEvaluationAttachments) + .innerJoin( + reviewerEvaluationDetails, + eq(reviewerEvaluationAttachments.reviewerEvaluationDetailId, reviewerEvaluationDetails.id) + ) + .innerJoin( + reviewerEvaluations, + eq(reviewerEvaluationDetails.reviewerEvaluationId, reviewerEvaluations.id) + ) + .leftJoin( + regEvalCriteriaDetails, + eq(reviewerEvaluationDetails.regEvalCriteriaDetailsId, regEvalCriteriaDetails.id) + ) + .leftJoin( + users, + eq(reviewerEvaluationAttachments.uploadedBy, users.id) + ) + .where(eq(reviewerEvaluations.periodicEvaluationId, periodicEvaluationId)) + .orderBy(desc(reviewerEvaluationAttachments.createdAt)) + + // 📎 4. 첨부파일을 평가 세부사항별로 그룹화 + const attachmentsByDetailId = new Map<number, AttachmentDetail[]>() + const attachmentsByReviewerId = new Map<number, AttachmentDetail[]>() + + attachmentsData.forEach(attachment => { + const attachmentInfo: AttachmentDetail = { + id: attachment.attachmentId, + originalFileName: attachment.originalFileName, + storedFileName: attachment.storedFileName, + publicPath: attachment.publicPath, + fileSize: attachment.fileSize, + mimeType: attachment.mimeType || undefined, + fileExtension: attachment.fileExtension || undefined, + description: attachment.description || undefined, + uploadedBy: attachment.uploadedBy, + uploadedByName: attachment.uploadedByName || undefined, + createdAt: new Date(attachment.attachmentCreatedAt), + } + + // 평가 세부사항별 그룹화 + if (!attachmentsByDetailId.has(attachment.evaluationDetailId)) { + attachmentsByDetailId.set(attachment.evaluationDetailId, []) + } + attachmentsByDetailId.get(attachment.evaluationDetailId)!.push(attachmentInfo) + + // 리뷰어별 그룹화 + if (!attachmentsByReviewerId.has(attachment.reviewerEvaluationId)) { + attachmentsByReviewerId.set(attachment.reviewerEvaluationId, []) + } + attachmentsByReviewerId.get(attachment.reviewerEvaluationId)!.push(attachmentInfo) + }) + + // 5. 리뷰어별로 그룹화하고 첨부파일 정보 포함 const reviewerDetailsMap = new Map<number, EvaluationDetailData>() reviewerDetailsRaw.forEach(row => { if (!reviewerDetailsMap.has(row.reviewerEvaluationId)) { + const reviewerAttachments = attachmentsByReviewerId.get(row.reviewerEvaluationId) || [] + reviewerDetailsMap.set(row.reviewerEvaluationId, { reviewerEvaluationId: row.reviewerEvaluationId, reviewerName: row.reviewerName || "", @@ -1074,13 +1145,22 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis isCompleted: row.isCompleted || false, completedAt: row.completedAt, reviewerComment: row.reviewerComment, - evaluationItems: [] + evaluationItems: [], + + // 📎 리뷰어별 첨부파일 통계 + totalAttachments: reviewerAttachments.length, + totalAttachmentSize: reviewerAttachments.reduce((sum, att) => sum + att.fileSize, 0), + questionsWithAttachments: new Set(reviewerAttachments.map(att => + attachmentsData.find(a => a.attachmentId === att.id)?.criteriaId + ).filter(Boolean)).size, }) } // 평가 항목이 있는 경우에만 추가 if (row.criteriaId && row.detailId) { const reviewer = reviewerDetailsMap.get(row.reviewerEvaluationId)! + const itemAttachments = attachmentsByDetailId.get(row.detailId) || [] + reviewer.evaluationItems.push({ criteriaId: row.criteriaId, category: row.category || "", @@ -1093,14 +1173,28 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis selectedDetailId: row.selectedDetailId, selectedDetail: row.selectedDetail, score: row.score ? Number(row.score) : null, - comment: row.comment + comment: row.comment, + + // 📎 항목별 첨부파일 정보 + attachments: itemAttachments, + attachmentCount: itemAttachments.length, + attachmentTotalSize: itemAttachments.reduce((sum, att) => sum + att.fileSize, 0), }) } }) + // 📎 6. 전체 첨부파일 통계 계산 + const attachmentStats = { + totalFiles: attachmentsData.length, + totalSize: attachmentsData.reduce((sum, att) => sum + att.fileSize, 0), + reviewersWithAttachments: attachmentsByReviewerId.size, + questionsWithAttachments: new Set(attachmentsData.map(att => att.criteriaId).filter(Boolean)).size, + } + return { evaluationInfo: evaluationInfo[0], - reviewerDetails: Array.from(reviewerDetailsMap.values()) + reviewerDetails: Array.from(reviewerDetailsMap.values()), + attachmentStats, } } catch (error) { |
