summaryrefslogtreecommitdiff
path: root/lib/evaluation/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/evaluation/service.ts')
-rw-r--r--lib/evaluation/service.ts128
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,