summaryrefslogtreecommitdiff
path: root/lib/evaluation-target-list/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/evaluation-target-list/service.ts')
-rw-r--r--lib/evaluation-target-list/service.ts195
1 files changed, 191 insertions, 4 deletions
diff --git a/lib/evaluation-target-list/service.ts b/lib/evaluation-target-list/service.ts
index 9e21dc51..6de00329 100644
--- a/lib/evaluation-target-list/service.ts
+++ b/lib/evaluation-target-list/service.ts
@@ -1,6 +1,6 @@
'use server'
-import { and, or, desc, asc, ilike, eq, isNull, sql, count, inArray } from "drizzle-orm";
+import { and, or, desc, asc, ilike, eq, isNull, sql, count, inArray, gte, lte } from "drizzle-orm";
import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers";
import { filterColumns } from "@/lib/filter-columns";
@@ -22,7 +22,9 @@ import {
reviewerEvaluations,
evaluationSubmissions,
generalEvaluations,
- esgEvaluationItems
+ esgEvaluationItems,
+ contracts,
+ projects
} from "@/db/schema";
@@ -33,6 +35,7 @@ import { authOptions } from "@/app/api/auth/[...nextauth]/route"
import { sendEmail } from "../mail/sendEmail";
import type { SQL } from "drizzle-orm"
import { DEPARTMENT_CODE_LABELS } from "@/types/evaluation";
+import { revalidatePath } from "next/cache";
export async function selectEvaluationTargetsFromView(
tx: PgTransaction<any, any, any>,
@@ -214,8 +217,8 @@ export async function getEvaluationTargetsStats(evaluationYear: number) {
consensusTrue: sql<number>`sum(case when consensus_status = true then 1 else 0 end)`,
consensusFalse: sql<number>`sum(case when consensus_status = false then 1 else 0 end)`,
consensusNull: sql<number>`sum(case when consensus_status is null then 1 else 0 end)`,
- oceanDivision: sql<number>`sum(case when division = 'OCEAN' then 1 else 0 end)`,
- shipyardDivision: sql<number>`sum(case when division = 'SHIPYARD' then 1 else 0 end)`,
+ oceanDivision: sql<number>`sum(case when division = 'PLANT' then 1 else 0 end)`,
+ shipyardDivision: sql<number>`sum(case when division = 'SHIP' then 1 else 0 end)`,
})
.from(evaluationTargetsWithDepartments)
.where(eq(evaluationTargetsWithDepartments.evaluationYear, evaluationYear));
@@ -1165,4 +1168,188 @@ export async function requestEvaluationReview(targetIds: number[], message?: str
error: error instanceof Error ? error.message : "의견 요청 중 오류가 발생했습니다."
}
}
+}
+
+
+
+interface AutoGenerateResult {
+ success: boolean
+ message: string
+ error?: string
+ generatedCount?: number
+ skippedCount?: number
+ details?: {
+ shipTargets: number
+ plantTargets: number
+ duplicateSkipped: number
+ }
+}
+
+/**
+ * 자동으로 평가 대상을 생성하는 서버 액션
+ * 전년도 10월부터 현재년도 9월까지의 계약을 기준으로 평가 대상을 생성
+ */
+export async function autoGenerateEvaluationTargets(
+ evaluationYear: number,
+ adminUserId: number
+): Promise<AutoGenerateResult> {
+ try {
+ // 평가 기간 계산 (전년도 10월 ~ 현재년도 9월)
+ const startDate = `${evaluationYear - 1}-10-01`
+ const endDate = `${evaluationYear}-09-30`
+
+ console.log(`Generating evaluation targets for period: ${startDate} to ${endDate}`)
+
+ // 1. 해당 기간의 계약들과 관련 정보를 조회
+ const contractsWithDetails = await db
+ .select({
+ contractId: contracts.id,
+ vendorId: contracts.vendorId,
+ projectId: contracts.projectId,
+ startDate: contracts.startDate,
+ // vendor 정보
+ vendorCode: vendors.vendorCode,
+ vendorName: vendors.vendorName,
+ vendorType: vendors.country ==="KR"? "DOMESTIC":"FOREIGN", // DOMESTIC | FOREIGN
+ // project 정보
+ projectType: projects.type, // ship | plant
+ })
+ .from(contracts)
+ .innerJoin(vendors, eq(contracts.vendorId, vendors.id))
+ .innerJoin(projects, eq(contracts.projectId, projects.id))
+ .where(
+ and(
+ gte(contracts.startDate, startDate),
+ lte(contracts.startDate, endDate)
+ )
+ )
+
+ if (contractsWithDetails.length === 0) {
+ return {
+ success: true,
+ message: "해당 기간에 생성할 평가 대상이 없습니다.",
+ generatedCount: 0,
+ skippedCount: 0
+ }
+ }
+
+ console.log(`Found ${contractsWithDetails.length} contracts in the period`)
+
+ // 2. 벤더별, 구분별로 그룹화하여 중복 제거
+ const targetGroups = new Map<string, {
+ vendorId: number
+ vendorCode: string
+ vendorName: string
+ domesticForeign: "DOMESTIC" | "FOREIGN"
+ division: "SHIP" | "PLANT"
+ materialType: "EQUIPMENT" | "BULK" | "EQUIPMENT_BULK"
+ }>()
+
+ contractsWithDetails.forEach(contract => {
+ const division = contract.projectType === "ship" ? "SHIP" : "PLANT"
+ const key = `${contract.vendorId}-${division}`
+
+ if (!targetGroups.has(key)) {
+ targetGroups.set(key, {
+ vendorId: contract.vendorId,
+ vendorCode: contract.vendorCode,
+ vendorName: contract.vendorName,
+ domesticForeign: contract.vendorType === "DOMESTIC" ? "DOMESTIC" : "FOREIGN",
+ division: division as "SHIP" | "PLANT",
+ // 기본값으로 EQUIPMENT 설정 (추후 더 정교한 로직 필요시 수정)
+ materialType: "EQUIPMENT" as const
+ })
+ }
+ })
+
+ console.log(`Created ${targetGroups.size} unique vendor-division combinations`)
+
+ // 3. 이미 존재하는 평가 대상 확인
+ const existingTargetsKeys = new Set<string>()
+ if (targetGroups.size > 0) {
+ const vendorIds = Array.from(targetGroups.values()).map(t => t.vendorId)
+ const existingTargets = await db
+ .select({
+ vendorId: evaluationTargets.vendorId,
+ division: evaluationTargets.division
+ })
+ .from(evaluationTargets)
+ .where(
+ and(
+ eq(evaluationTargets.evaluationYear, evaluationYear),
+ inArray(evaluationTargets.vendorId, vendorIds)
+ )
+ )
+
+ existingTargets.forEach(target => {
+ existingTargetsKeys.add(`${target.vendorId}-${target.division}`)
+ })
+ }
+
+ console.log(`Found ${existingTargetsKeys.size} existing targets`)
+
+ // 4. 새로운 평가 대상만 필터링
+ const newTargets = Array.from(targetGroups.entries())
+ .filter(([key]) => !existingTargetsKeys.has(key))
+ .map(([_, target]) => target)
+
+ if (newTargets.length === 0) {
+ return {
+ success: true,
+ message: "이미 모든 평가 대상이 생성되어 있습니다.",
+ generatedCount: 0,
+ skippedCount: targetGroups.size
+ }
+ }
+
+ // 5. 평가 대상 생성
+ const evaluationTargetsToInsert = newTargets.map(target => ({
+ evaluationYear,
+ division: target.division,
+ vendorId: target.vendorId,
+ vendorCode: target.vendorCode,
+ vendorName: target.vendorName,
+ domesticForeign: target.domesticForeign,
+ materialType: target.materialType,
+ status: "PENDING" as const,
+ adminUserId,
+ ldClaimCount: 0,
+ ldClaimAmount: "0",
+ ldClaimCurrency: "KRW" as const
+ }))
+
+ // 배치로 삽입
+ await db.insert(evaluationTargets).values(evaluationTargetsToInsert)
+
+ // 통계 계산
+ const shipTargets = newTargets.filter(t => t.division === "SHIP").length
+ const plantTargets = newTargets.filter(t => t.division === "PLANT").length
+ const duplicateSkipped = existingTargetsKeys.size
+
+ console.log(`Successfully created ${newTargets.length} evaluation targets`)
+
+ // 캐시 무효화
+ revalidatePath("/evcp/evaluation-target-list")
+ revalidatePath("/procurement/evaluation-target-list")
+
+ return {
+ success: true,
+ message: `${newTargets.length}개의 평가 대상이 성공적으로 생성되었습니다.`,
+ generatedCount: newTargets.length,
+ skippedCount: duplicateSkipped,
+ details: {
+ shipTargets,
+ plantTargets,
+ duplicateSkipped
+ }
+ }
+
+ } catch (error) {
+ console.error("Error auto generating evaluation targets:", error)
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.",
+ message: "평가 대상 자동 생성에 실패했습니다."
+ }
+ }
} \ No newline at end of file