diff options
| -rw-r--r-- | lib/evaluation/service.ts | 81 | ||||
| -rw-r--r-- | lib/evaluation/table/evaluation-details-dialog.tsx | 74 | ||||
| -rw-r--r-- | lib/evaluation/vendor-submission-service.ts | 66 | ||||
| -rw-r--r-- | lib/vendor-evaluation-submit/table/esg-evaluation-form-sheet.tsx | 7 | ||||
| -rw-r--r-- | types/evaluation-form.ts | 10 |
5 files changed, 231 insertions, 7 deletions
diff --git a/lib/evaluation/service.ts b/lib/evaluation/service.ts index b958e371..122d0777 100644 --- a/lib/evaluation/service.ts +++ b/lib/evaluation/service.ts @@ -1283,10 +1283,91 @@ export async function getEvaluationDetails(periodicEvaluationId: number): Promis questionsWithAttachments: new Set(attachmentsData.map(att => att.criteriaId).filter(Boolean)).size, } + // ๐ 7. ์กฐ์ /ํด์ ์ทจํฉ ์ ๋ณด ๊ณ์ฐ (๋์ ํ๊ฐ์ธ ๊ฒฝ์ฐ) + let consolidatedInfo: { + shipbuildingScore: number | null + shipbuildingGrade: string | null + offshoreScore: number | null + offshoreGrade: string | null + consolidatedScore: number | null + consolidatedGrade: string | null + } | undefined = undefined + + // ํ์ฌ ํ๊ฐ์ evaluationTarget ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ + const currentEvaluationTarget = await db + .select({ + id: evaluationTargets.id, + vendorId: evaluationTargets.vendorId, + evaluationYear: evaluationTargets.evaluationYear, + evaluationRound: evaluationTargets.evaluationRound, + division: evaluationTargets.division, + }) + .from(evaluationTargets) + .innerJoin(periodicEvaluations, eq(periodicEvaluations.evaluationTargetId, evaluationTargets.id)) + .where(eq(periodicEvaluations.id, periodicEvaluationId)) + .limit(1) + + if (currentEvaluationTarget.length > 0) { + const target = currentEvaluationTarget[0] + + // ๊ฐ์ ์
์ฒด, ๊ฐ์ ๋
๋, ๊ฐ์ ๋ผ์ด๋์ ๋ค๋ฅธ division ํ๊ฐ๊ฐ ์๋์ง ํ์ธ + const siblingEvaluations = await db + .select({ + periodicEvaluationId: periodicEvaluations.id, + division: evaluationTargets.division, + finalScore: periodicEvaluations.finalScore, + finalGrade: periodicEvaluations.finalGrade, + }) + .from(evaluationTargets) + .innerJoin(periodicEvaluations, eq(periodicEvaluations.evaluationTargetId, evaluationTargets.id)) + .where( + and( + eq(evaluationTargets.vendorId, target.vendorId), + eq(evaluationTargets.evaluationYear, target.evaluationYear), + eq(evaluationTargets.evaluationRound, target.evaluationRound || "") + ) + ) + + // ์กฐ์ ๊ณผ ํด์ ํ๊ฐ๊ฐ ๋ชจ๋ ์๋์ง ํ์ธ + const shipbuilding = siblingEvaluations.find(e => e.division === "SHIPBUILDING") + const offshore = siblingEvaluations.find(e => e.division === "PLANT") + + if (shipbuilding && offshore) { + // ๋ ํ๊ฐ๊ฐ ๋ชจ๋ ์์ผ๋ฉด ์ทจํฉ ์ ๋ณด ๊ณ์ฐ + const shipScore = shipbuilding.finalScore ? Number(shipbuilding.finalScore) : null + const offScore = offshore.finalScore ? Number(offshore.finalScore) : null + + let consolidatedScore: number | null = null + let consolidatedGrade: string | null = null + + // ๋ ๋ค ์ ์๊ฐ ์์ผ๋ฉด 50% ๋ฐ์ํ์ฌ ์ทจํฉ + if (shipScore !== null && offScore !== null) { + consolidatedScore = Math.round((shipScore * 0.5 + offScore * 0.5) * 10) / 10 + + // ์ทจํฉ ๋ฑ๊ธ ๊ณ์ฐ (์ ์ ๊ธฐ์ค) + if (consolidatedScore >= 90) consolidatedGrade = "S" + else if (consolidatedScore >= 80) consolidatedGrade = "A" + else if (consolidatedScore >= 70) consolidatedGrade = "B" + else if (consolidatedScore >= 60) consolidatedGrade = "C" + else consolidatedGrade = "D" + } + + consolidatedInfo = { + shipbuildingScore: shipScore, + shipbuildingGrade: shipbuilding.finalGrade, + offshoreScore: offScore, + offshoreGrade: offshore.finalGrade, + consolidatedScore, + consolidatedGrade, + } + } + } + return { evaluationInfo: evaluationInfo[0], reviewerDetails: Array.from(reviewerDetailsMap.values()), attachmentStats, + consolidatedInfo, } } catch (error) { diff --git a/lib/evaluation/table/evaluation-details-dialog.tsx b/lib/evaluation/table/evaluation-details-dialog.tsx index 2f682402..fe7c204a 100644 --- a/lib/evaluation/table/evaluation-details-dialog.tsx +++ b/lib/evaluation/table/evaluation-details-dialog.tsx @@ -228,6 +228,80 @@ export function EvaluationDetailsDialog({ </div> </div> + {/* ๐ ์กฐ์ /ํด์ ์ทจํฉ ์ ๋ณด (๋์ ํ๊ฐ์ธ ๊ฒฝ์ฐ) */} + {evaluationDetails?.consolidatedInfo && ( + <div className="mt-4 p-4 bg-blue-50 border border-blue-200 rounded-md"> + <div className="flex items-center gap-2 mb-3"> + <BarChart3 className="h-5 w-5 text-blue-600" /> + <span className="font-semibold text-blue-900">์กฐ์ /ํด์ ์ทจํฉ ๊ฒฐ๊ณผ</span> + </div> + <div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm"> + {/* ์กฐ์ ์ ์ */} + <div className="space-y-1"> + <div className="text-muted-foreground">์กฐ์ ํ์ ์ ์/๋ฑ๊ธ</div> + <div className="flex items-center gap-1"> + {evaluationDetails.consolidatedInfo.shipbuildingScore !== null ? ( + <> + <span className="font-bold text-blue-700"> + {evaluationDetails.consolidatedInfo.shipbuildingScore.toFixed(1)}์ + </span> + {evaluationDetails.consolidatedInfo.shipbuildingGrade && ( + <Badge variant="outline" className="text-xs h-5"> + {evaluationDetails.consolidatedInfo.shipbuildingGrade} + </Badge> + )} + </> + ) : ( + <span className="text-muted-foreground">-</span> + )} + </div> + </div> + + {/* ํด์ ์ ์ */} + <div className="space-y-1"> + <div className="text-muted-foreground">ํด์ ํ์ ์ ์/๋ฑ๊ธ</div> + <div className="flex items-center gap-1"> + {evaluationDetails.consolidatedInfo.offshoreScore !== null ? ( + <> + <span className="font-bold text-blue-700"> + {evaluationDetails.consolidatedInfo.offshoreScore.toFixed(1)}์ + </span> + {evaluationDetails.consolidatedInfo.offshoreGrade && ( + <Badge variant="outline" className="text-xs h-5"> + {evaluationDetails.consolidatedInfo.offshoreGrade} + </Badge> + )} + </> + ) : ( + <span className="text-muted-foreground">-</span> + )} + </div> + </div> + + {/* ์ทจํฉ ์ ์ (50% ๋ฐ์) */} + <div className="space-y-1"> + <div className="text-muted-foreground">์ทจํฉ ์ ์/๋ฑ๊ธ (50% ๋ฐ์)</div> + <div className="flex items-center gap-1"> + {evaluationDetails.consolidatedInfo.consolidatedScore !== null ? ( + <> + <span className="font-bold text-purple-700 text-base"> + {evaluationDetails.consolidatedInfo.consolidatedScore.toFixed(1)}์ + </span> + {evaluationDetails.consolidatedInfo.consolidatedGrade && ( + <Badge variant="default" className="bg-purple-600 text-xs h-5"> + {evaluationDetails.consolidatedInfo.consolidatedGrade} + </Badge> + )} + </> + ) : ( + <span className="text-muted-foreground">-</span> + )} + </div> + </div> + </div> + </div> + )} + </CardContent> </Card> </DialogHeader> diff --git a/lib/evaluation/vendor-submission-service.ts b/lib/evaluation/vendor-submission-service.ts index 388f382a..c06a9d2a 100644 --- a/lib/evaluation/vendor-submission-service.ts +++ b/lib/evaluation/vendor-submission-service.ts @@ -129,6 +129,7 @@ export async function getVendorSubmissionDetails(periodicEvaluationId: number): // ํ๋ ฅ์
์ฒด ์ ๋ณด vendorId: vendors.id, + companyId: evaluationSubmissions.companyId, vendorCode: vendors.vendorCode, vendorName: vendors.vendorName, vendorEmail: vendors.email, @@ -149,7 +150,7 @@ export async function getVendorSubmissionDetails(periodicEvaluationId: number): } const submission = submissionResult[0] - const submissionId = submission.id // evaluationSubmissions.id (integer) + let submissionId = submission.id // evaluationSubmissions.id (integer) const submissionUuid = submission.submissionId // evaluationSubmissions.submissionId (UUID) console.log("=== ํ๋ ฅ์
์ฒด ์ ์ถ ์์ธ ์กฐํ ์์ ===") @@ -157,6 +158,58 @@ export async function getVendorSubmissionDetails(periodicEvaluationId: number): console.log("submissionUuid:", submissionUuid) console.log("submission:", submission) + // ๐ ์กฐ์ /ํด์ ๋์ ์ ์ถ ์ผ์ด์ค ์ฒ๋ฆฌ: ํ์ฌ submission์ ์๋ต์ด ์์ผ๋ฉด ๊ฐ์ ๊ทธ๋ฃน์์ ์๋ต์ด ์๋ submission์ ์ฐพ์์ ์ฌ์ฉ + const hasResponses = await db + .select({ count: sql<number>`COUNT(*)::int` }) + .from(generalEvaluationResponses) + .where( + and( + eq(generalEvaluationResponses.submissionId, submissionId), + eq(generalEvaluationResponses.isActive, true) + ) + ) + .then(result => (result[0]?.count || 0) > 0) + + if (!hasResponses) { + console.log("ํ์ฌ submission์ ์๋ต ์์. ๊ฐ์ ๊ทธ๋ฃน์์ ์๋ต์ด ์๋ submission ์ฐพ๊ธฐ...") + + // ๊ฐ์ companyId, evaluationYear, evaluationRound๋ฅผ ๊ฐ์ง ๋ค๋ฅธ submission ์ค ์๋ต์ด ์๋ ๊ฒ์ ์ฐพ์ + const siblingSubmissions = await db + .select({ + id: evaluationSubmissions.id, + submissionId: evaluationSubmissions.submissionId, + }) + .from(evaluationSubmissions) + .where( + and( + eq(evaluationSubmissions.companyId, submission.companyId), + eq(evaluationSubmissions.evaluationYear, submission.evaluationYear), + eq(evaluationSubmissions.evaluationRound, submission.evaluationRound || ""), + eq(evaluationSubmissions.isActive, true) + ) + ) + + // ๊ฐ sibling submission์ ์๋ต์ด ์๋์ง ํ์ธ + for (const sibling of siblingSubmissions) { + const siblingHasResponses = await db + .select({ count: sql<number>`COUNT(*)::int` }) + .from(generalEvaluationResponses) + .where( + and( + eq(generalEvaluationResponses.submissionId, sibling.id), + eq(generalEvaluationResponses.isActive, true) + ) + ) + .then(result => (result[0]?.count || 0) > 0) + + if (siblingHasResponses) { + console.log(`์๋ต์ด ์๋ submission ๋ฐ๊ฒฌ: ${sibling.id}`) + submissionId = sibling.id + break + } + } + } + // 2. ์ผ๋ฐํ๊ฐ ํญ๋ชฉ๊ณผ ์๋ต ์กฐํ const generalEvaluationsResult = await db .select({ @@ -281,7 +334,7 @@ export async function getVendorSubmissionDetails(periodicEvaluationId: number): fileSize: row.fileSize, mimeType: row.mimeType, uploadedBy: row.uploadedBy, - createdAt: new Date(row.attachmentCreatedAt) + createdAt: row.attachmentCreatedAt ? new Date(row.attachmentCreatedAt) : new Date() }) } }) @@ -324,10 +377,10 @@ export async function getVendorSubmissionDetails(periodicEvaluationId: number): // 5. ์ฒจ๋ถํ์ผ ํต๊ณ ๊ณ์ฐ const allAttachments = generalEvaluationsResult - .filter(row => row.attachmentId) + .filter(row => row.attachmentId && row.fileSize !== null) .map(row => ({ id: row.attachmentId, - fileSize: row.fileSize + fileSize: row.fileSize || 0 })) const attachmentStats = { @@ -348,6 +401,11 @@ export async function getVendorSubmissionDetails(periodicEvaluationId: number): reviewComments: submission.reviewComments, averageEsgScore: submission.averageEsgScore ? Number(submission.averageEsgScore) : null, + // ์งํ๋ฅ ํต๊ณ + totalGeneralItems: submission.totalGeneralItems, + completedGeneralItems: submission.completedGeneralItems, + totalEsgItems: submission.totalEsgItems, + completedEsgItems: submission.completedEsgItems, vendor: { id: submission.vendorId, diff --git a/lib/vendor-evaluation-submit/table/esg-evaluation-form-sheet.tsx b/lib/vendor-evaluation-submit/table/esg-evaluation-form-sheet.tsx index 0ebe1f8c..f3778f26 100644 --- a/lib/vendor-evaluation-submit/table/esg-evaluation-form-sheet.tsx +++ b/lib/vendor-evaluation-submit/table/esg-evaluation-form-sheet.tsx @@ -115,7 +115,8 @@ export function EsgEvaluationFormSheet({ additionalComments: item.response?.additionalComments || '', }) - if (item.response?.selectedScore) { + // 0์ ๋ ์ ํจํ ์๋ต์ด๋ฏ๋ก undefined/null๋ง ์ฒดํฌ + if (item.response?.selectedScore !== undefined && item.response?.selectedScore !== null) { scores[item.item.id] = item.response.selectedScore } }) @@ -468,7 +469,7 @@ const handleExportData = async () => { <BarChart3Icon className="h-4 w-4" /> <span className="text-sm"> {evaluation.items.filter(item => - currentScores[item.item.id] >= 0 + currentScores[item.item.id] !== undefined ).length}/{evaluation.items.length} </span> </div> @@ -490,7 +491,7 @@ const handleExportData = async () => { <CardHeader className="pb-3"> <CardTitle className="text-sm flex items-center justify-between"> <span>{item.item.evaluationItem}</span> - {currentScores[item.item.id] > 0 && ( + {currentScores[item.item.id] !== undefined && currentScores[item.item.id] !== null && ( <Badge variant="default" className="bg-green-100 text-green-800"> {currentScores[item.item.id]}์ </Badge> diff --git a/types/evaluation-form.ts b/types/evaluation-form.ts index 449b1fa2..39b34bcd 100644 --- a/types/evaluation-form.ts +++ b/types/evaluation-form.ts @@ -139,4 +139,14 @@ export interface EvaluationDetailResponse { reviewersWithAttachments: number questionsWithAttachments: number } + + // ๐ ์กฐ์ /ํด์ ์ทจํฉ ์ ๋ณด (๋์ ํ๊ฐ์ธ ๊ฒฝ์ฐ) + consolidatedInfo?: { + shipbuildingScore: number | null + shipbuildingGrade: string | null + offshoreScore: number | null + offshoreGrade: string | null + consolidatedScore: number | null // 50% ๋ฐ์ ์ทจํฉ ์ ์ + consolidatedGrade: string | null // ์ทจํฉ ๋ฑ๊ธ + } }
\ No newline at end of file |
