diff options
Diffstat (limited to 'lib/vendor-investigation/service.ts')
| -rw-r--r-- | lib/vendor-investigation/service.ts | 139 |
1 files changed, 134 insertions, 5 deletions
diff --git a/lib/vendor-investigation/service.ts b/lib/vendor-investigation/service.ts index f81f78f6..3ccbe880 100644 --- a/lib/vendor-investigation/service.ts +++ b/lib/vendor-investigation/service.ts @@ -1,6 +1,6 @@ "use server"; // Next.js 서버 액션에서 직접 import하려면 (선택) -import { items, vendorInvestigationAttachments, vendorInvestigations, vendorInvestigationsView, vendorPossibleItems, vendors, siteVisitRequests } from "@/db/schema/" +import { items, vendorInvestigationAttachments, vendorInvestigations, vendorInvestigationsView, vendorPossibleItems, vendors, siteVisitRequests, vendorPQSubmissions } from "@/db/schema/" import { GetVendorsInvestigationSchema, updateVendorInvestigationSchema, updateVendorInvestigationProgressSchema, updateVendorInvestigationResultSchema } from "./validations" import { asc, desc, ilike, inArray, and, or, gte, lte, eq, isNull, count } from "drizzle-orm"; import { revalidateTag, unstable_noStore, revalidatePath } from "next/cache"; @@ -131,6 +131,46 @@ export async function getExistingInvestigationsForVendors(vendorIds: number[]) { } } +// PQ 제출 타입 조회 (investigation.pqSubmissionId → type) +export default async function getPQSubmissionTypeAction(pqSubmissionId: number) { + try { + const row = await db + .select({ type: vendorPQSubmissions.type }) + .from(vendorPQSubmissions) + .where(eq(vendorPQSubmissions.id, pqSubmissionId)) + .limit(1) + .then(rows => rows[0]); + if (!row) return { success: false, error: "PQ submission not found" }; + return { success: true, type: row.type as "GENERAL" | "PROJECT" | "NON_INSPECTION" }; + } catch (e) { + return { success: false, error: e instanceof Error ? e.message : "Unknown error" }; + } +} + +// 실사 계획 취소 액션: 상태를 QM_REVIEW_CONFIRMED로 되돌림 +export async function cancelInvestigationPlanAction(investigationId: number) { + try { + await db + .update(vendorInvestigations) + .set({ + investigationStatus: "QM_REVIEW_CONFIRMED", + updatedAt: new Date(), + }) + .where(eq(vendorInvestigations.id, investigationId)) + + revalidateTag("vendor-investigations") + revalidatePath("/evcp/vendor-investigation") + + return { success: true } + } catch (error) { + console.error("실사 계획 취소 오류:", error) + return { + success: false, + error: error instanceof Error ? error.message : "알 수 없는 오류", + } + } +} + interface RequestInvestigateVendorsInput { ids: number[] } @@ -228,7 +268,7 @@ export async function updateVendorInvestigationProgressAction(formData: FormData processedEntries.confirmedAt = new Date(textEntries.confirmedAt) } - // 3) Zod로 파싱/검증 + // 3) Zod로 파싱/검증 (4개 필수값 규칙 포함) const parsed = updateVendorInvestigationProgressSchema.parse(processedEntries) // 4) 업데이트 데이터 준비 @@ -250,7 +290,7 @@ export async function updateVendorInvestigationProgressAction(formData: FormData updateData.confirmedAt = parsed.confirmedAt } - // 실사 방법이 설정되면 PLANNED -> IN_PROGRESS로 상태 변경 + // 실사 방법이 설정되면 QM_REVIEW_CONFIRMED -> IN_PROGRESS로 상태 변경 if (parsed.investigationMethod) { updateData.investigationStatus = "IN_PROGRESS" } @@ -334,10 +374,12 @@ export async function updateVendorInvestigationResultAction(formData: FormData) if (parsed.evaluationResult) { if (parsed.evaluationResult === "REJECTED") { updateData.investigationStatus = "CANCELED" - } else if (parsed.evaluationResult === "SUPPLEMENT" || - parsed.evaluationResult === "SUPPLEMENT_REINSPECT" || + } else if (parsed.evaluationResult === "SUPPLEMENT" || + parsed.evaluationResult === "SUPPLEMENT_REINSPECT" || parsed.evaluationResult === "SUPPLEMENT_DOCUMENT") { updateData.investigationStatus = "SUPPLEMENT_REQUIRED" + // 보완 요청이 있었음을 기록 + updateData.hasSupplementRequested = true } else if (parsed.evaluationResult === "APPROVED") { updateData.investigationStatus = "COMPLETED" } @@ -1150,6 +1192,93 @@ export async function completeSupplementReinspectionAction({ } } +// 실사 보완요청 메일 발송 액션 +export async function requestInvestigationSupplementAction({ + investigationId, + vendorId, + comment, +}: { + investigationId: number; + vendorId: number; + comment: string; +}) { + unstable_noStore(); + try { + const headersList = await import("next/headers").then(m => m.headers()); + const host = headersList.get('host') || 'localhost:3000'; + + // 실사/벤더 정보 조회 + const investigation = await db + .select({ + id: vendorInvestigations.id, + pqSubmissionId: vendorInvestigations.pqSubmissionId, + investigationAddress: vendorInvestigations.investigationAddress, + }) + .from(vendorInvestigations) + .where(eq(vendorInvestigations.id, investigationId)) + .then(rows => rows[0]); + + const vendor = await db + .select({ email: vendors.email, vendorName: vendors.vendorName }) + .from(vendors) + .where(eq(vendors.id, vendorId)) + .then(rows => rows[0]); + + if (!vendor?.email) { + return { success: false, error: "벤더 이메일 정보가 없습니다." }; + } + + // PQ 번호 조회 + let pqNumber = "N/A"; + if (investigation?.pqSubmissionId) { + const pqRow = await db + .select({ pqNumber: vendorPQSubmissions.pqNumber }) + .from(vendorPQSubmissions) + .where(eq(vendorPQSubmissions.id, investigation.pqSubmissionId)) + .then(rows => rows[0]); + if (pqRow) pqNumber = pqRow.pqNumber; + } + + // 메일 발송 + const portalUrl = process.env.NEXTAUTH_URL || `http://${host}`; + const reviewUrl = `${portalUrl}/evcp/vendor-investigation`; + + await sendEmail({ + to: vendor.email, + subject: `[eVCP] 실사 보완요청 - ${vendor.vendorName}`, + template: "pq-investigation-supplement-request", + context: { + vendorName: vendor.vendorName, + investigationNumber: pqNumber, + supplementComment: comment, + requestedAt: new Date().toLocaleString('ko-KR'), + reviewUrl: reviewUrl, + year: new Date().getFullYear(), + } + }); + + // 실사 상태를 SUPPLEMENT_REQUIRED로 변경 (이미 되어있을 수 있음) + await db + .update(vendorInvestigations) + .set({ + investigationStatus: "SUPPLEMENT_REQUIRED", + updatedAt: new Date(), + }) + .where(eq(vendorInvestigations.id, investigationId)); + + revalidateTag("vendor-investigations"); + revalidateTag("pq-submissions"); + + return { success: true }; + } catch (error) { + console.error("실사 보완요청 메일 발송 오류:", error); + return { + success: false, + error: error instanceof Error ? error.message : "알 수 없는 오류", + }; + } +} + // 보완 서류제출 응답 제출 액션 export async function submitSupplementDocumentResponseAction({ investigationId, |
