diff options
Diffstat (limited to 'lib/evaluation/service.ts')
| -rw-r--r-- | lib/evaluation/service.ts | 128 |
1 files changed, 77 insertions, 51 deletions
diff --git a/lib/evaluation/service.ts b/lib/evaluation/service.ts index 122d0777..9a6075bb 100644 --- a/lib/evaluation/service.ts +++ b/lib/evaluation/service.ts @@ -37,7 +37,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" +import { AttachmentDetail, EvaluationDetailResponse, EvaluationDetailData } from "@/types/evaluation-form" import { headers } from 'next/headers'; export async function getPeriodicEvaluations(input: GetEvaluationsSchema) { @@ -1045,46 +1045,49 @@ export async function unfinalizeEvaluations(evaluationIds: number[]) { } -// 평가 상세 정보 타입 -export interface EvaluationDetailData { - // 리뷰어 정보 - reviewerEvaluationId: number - reviewerName: string - reviewerEmail: string - departmentCode: string - departmentName: string - isCompleted: boolean - completedAt: Date | null - reviewerComment: string | null - - // 평가 항목별 상세 - evaluationItems: { - // 평가 기준 정보 - criteriaId: number - category: string - category2: string - item: string - classification: string - range: string | null - remarks: string | null - scoreType: string - - // 선택된 옵션 정보 (fixed 타입인 경우) - selectedDetailId: number | null - selectedDetail: string | null - - // 점수 및 의견 - score: number | null - comment: string | null - }[] -} +// 평가 상세 정보 타입은 types/evaluation-form.ts에서 import /** * 특정 정기평가의 상세 정보를 조회합니다 */ -export async function getEvaluationDetails(periodicEvaluationId: number): Promise<EvaluationDetailResponse> { +export async function getEvaluationDetails(periodicEvaluationId: number | string): Promise<EvaluationDetailResponse> { try { + // 집계 뷰(id가 "년도_벤더ID" 형태 문자열)에서 호출될 수 있으므로 문자열을 실제 정기평가 id로 해석 + if (typeof periodicEvaluationId === "string") { + const [yearStr, vendorIdStr] = periodicEvaluationId.split("_") + const evaluationYear = Number(yearStr) + const vendorId = Number(vendorIdStr) + + if (Number.isNaN(evaluationYear) || Number.isNaN(vendorId)) { + throw new Error("잘못된 평가 식별자입니다") + } + + const fallback = await db + .select({ + id: periodicEvaluations.id, + finalizedAt: periodicEvaluations.finalizedAt, + updatedAt: periodicEvaluations.updatedAt, + }) + .from(periodicEvaluations) + .innerJoin(evaluationTargets, eq(periodicEvaluations.evaluationTargetId, evaluationTargets.id)) + .where( + and( + eq(evaluationTargets.vendorId, vendorId), + eq(evaluationTargets.evaluationYear, evaluationYear) + ) + ) + // 확정된 평가를 우선, 없으면 최신 수정 순 + .orderBy(desc(periodicEvaluations.finalizedAt), desc(periodicEvaluations.updatedAt)) + .limit(1) + + if (fallback.length === 0) { + throw new Error("해당 업체/연도의 평가를 찾을 수 없습니다") + } + + periodicEvaluationId = fallback[0].id + } + // 1. 평가 기본 정보 조회 const evaluationInfo = await db .select({ @@ -1194,12 +1197,17 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis const attachmentsByReviewerId = new Map<number, AttachmentDetail[]>() attachmentsData.forEach(attachment => { + // 필수 필드가 없으면 건너뛰기 + if (!attachment.attachmentId || !attachment.evaluationDetailId || !attachment.reviewerEvaluationId) { + return + } + const attachmentInfo: AttachmentDetail = { id: attachment.attachmentId, - originalFileName: attachment.originalFileName, - storedFileName: attachment.storedFileName, - publicPath: attachment.publicPath, - fileSize: attachment.fileSize, + originalFileName: attachment.originalFileName || "", + storedFileName: attachment.storedFileName || "", + publicPath: attachment.publicPath || "", + fileSize: attachment.fileSize || 0, mimeType: attachment.mimeType || undefined, fileExtension: attachment.fileExtension || undefined, description: attachment.description || undefined, @@ -1225,6 +1233,11 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis const reviewerDetailsMap = new Map<number, EvaluationDetailData>() reviewerDetailsRaw.forEach(row => { + // reviewerEvaluationId가 null이면 건너뛰기 + if (!row.reviewerEvaluationId) { + return + } + if (!reviewerDetailsMap.has(row.reviewerEvaluationId)) { const reviewerAttachments = attachmentsByReviewerId.get(row.reviewerEvaluationId) || [] @@ -1233,7 +1246,7 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis reviewerName: row.reviewerName || "", reviewerEmail: row.reviewerEmail || "", departmentCode: row.departmentCode || "", - departmentName: DEPARTMENT_CODE_LABELS[row.departmentCode as keyof typeof DEPARTMENT_CODE_LABELS] || row.departmentCode || "", + departmentName: (row.departmentCode && DEPARTMENT_CODE_LABELS[row.departmentCode as keyof typeof DEPARTMENT_CODE_LABELS]) || row.departmentCode || "", isCompleted: row.isCompleted || false, completedAt: row.completedAt, reviewerComment: row.reviewerComment, @@ -1241,7 +1254,7 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis // 📎 리뷰어별 첨부파일 통계 totalAttachments: reviewerAttachments.length, - totalAttachmentSize: reviewerAttachments.reduce((sum, att) => sum + att.fileSize, 0), + totalAttachmentSize: reviewerAttachments.reduce((sum, att) => sum + (att.fileSize || 0), 0), questionsWithAttachments: new Set(reviewerAttachments.map(att => attachmentsData.find(a => a.attachmentId === att.id)?.criteriaId ).filter(Boolean)).size, @@ -1249,8 +1262,11 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis } // 평가 항목이 있는 경우에만 추가 - if (row.criteriaId && row.detailId) { - const reviewer = reviewerDetailsMap.get(row.reviewerEvaluationId)! + if (row.criteriaId && row.detailId && row.reviewerEvaluationId) { + const reviewer = reviewerDetailsMap.get(row.reviewerEvaluationId) + if (!reviewer) { + return + } const itemAttachments = attachmentsByDetailId.get(row.detailId) || [] reviewer.evaluationItems.push({ @@ -1270,7 +1286,7 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis // 📎 항목별 첨부파일 정보 attachments: itemAttachments, attachmentCount: itemAttachments.length, - attachmentTotalSize: itemAttachments.reduce((sum, att) => sum + att.fileSize, 0), + attachmentTotalSize: itemAttachments.reduce((sum, att) => sum + (att.fileSize || 0), 0), }) } }) @@ -1278,7 +1294,7 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis // 📎 6. 전체 첨부파일 통계 계산 const attachmentStats = { totalFiles: attachmentsData.length, - totalSize: attachmentsData.reduce((sum, att) => sum + att.fileSize, 0), + totalSize: attachmentsData.reduce((sum, att) => sum + (att.fileSize || 0), 0), reviewersWithAttachments: attachmentsByReviewerId.size, questionsWithAttachments: new Set(attachmentsData.map(att => att.criteriaId).filter(Boolean)).size, } @@ -1299,7 +1315,6 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis id: evaluationTargets.id, vendorId: evaluationTargets.vendorId, evaluationYear: evaluationTargets.evaluationYear, - evaluationRound: evaluationTargets.evaluationRound, division: evaluationTargets.division, }) .from(evaluationTargets) @@ -1310,7 +1325,7 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis if (currentEvaluationTarget.length > 0) { const target = currentEvaluationTarget[0] - // 같은 업체, 같은 년도, 같은 라운드의 다른 division 평가가 있는지 확인 + // 같은 업체, 같은 년도의 다른 division 평가가 있는지 확인 const siblingEvaluations = await db .select({ periodicEvaluationId: periodicEvaluations.id, @@ -1323,13 +1338,12 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis .where( and( eq(evaluationTargets.vendorId, target.vendorId), - eq(evaluationTargets.evaluationYear, target.evaluationYear), - eq(evaluationTargets.evaluationRound, target.evaluationRound || "") + eq(evaluationTargets.evaluationYear, target.evaluationYear) ) ) // 조선과 해양 평가가 모두 있는지 확인 - const shipbuilding = siblingEvaluations.find(e => e.division === "SHIPBUILDING") + const shipbuilding = siblingEvaluations.find(e => e.division === "SHIP" || e.division === "SHIPBUILDING") const offshore = siblingEvaluations.find(e => e.division === "PLANT") if (shipbuilding && offshore) { @@ -1363,8 +1377,20 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis } } + if (!evaluationInfo[0]) { + throw new Error("평가 정보를 찾을 수 없습니다") + } + + const info = evaluationInfo[0] return { - evaluationInfo: evaluationInfo[0], + evaluationInfo: { + id: info.id, + vendorName: info.vendorName || "", + vendorCode: info.vendorCode || "", + evaluationYear: info.evaluationYear || 0, + division: info.division || "", + status: info.status, + }, reviewerDetails: Array.from(reviewerDetailsMap.values()), attachmentStats, consolidatedInfo, |
