diff options
Diffstat (limited to 'lib/pq/service.ts')
| -rw-r--r-- | lib/pq/service.ts | 317 |
1 files changed, 312 insertions, 5 deletions
diff --git a/lib/pq/service.ts b/lib/pq/service.ts index bd83a33c..15e71c4d 100644 --- a/lib/pq/service.ts +++ b/lib/pq/service.ts @@ -551,7 +551,7 @@ export async function submitPQAction({ } // 제출 가능한 상태 확인 - const allowedStatuses = ["REQUESTED", "IN_PROGRESS", "SUBMITTED", "REJECTED"]; + const allowedStatuses = ["REQUESTED", "IN_PROGRESS", "SUBMITTED", "REJECTED", "SAFETY_REJECTED"]; if (existingSubmission) { if (!allowedStatuses.includes(existingSubmission.status)) { @@ -2277,6 +2277,289 @@ export async function updateSHICommentAction({ } } +// 안전 PQ 승인 액션 (HSE 단계) +export async function approveSafetyPQAction({ + pqSubmissionId, + vendorId, +}: { + pqSubmissionId: number; + vendorId: number; +}) { + unstable_noStore(); + + try { + const currentDate = new Date(); + + const pqSubmission = await db + .select({ + id: vendorPQSubmissions.id, + vendorId: vendorPQSubmissions.vendorId, + projectId: vendorPQSubmissions.projectId, + status: vendorPQSubmissions.status, + type: vendorPQSubmissions.type, + pqNumber: vendorPQSubmissions.pqNumber, + requesterId: vendorPQSubmissions.requesterId, + }) + .from(vendorPQSubmissions) + .where( + and( + eq(vendorPQSubmissions.id, pqSubmissionId), + eq(vendorPQSubmissions.vendorId, vendorId) + ) + ) + .then(rows => rows[0]); + + if (!pqSubmission) { + return { ok: false, error: "PQ submission not found" }; + } + + if (pqSubmission.status !== "SUBMITTED") { + return { + ok: false, + error: `Cannot approve safety PQ in current status: ${pqSubmission.status}` + }; + } + + await db + .update(vendorPQSubmissions) + .set({ + status: "SAFETY_APPROVED", + updatedAt: currentDate, + }) + .where(eq(vendorPQSubmissions.id, pqSubmissionId)); + + // 벤더 안전적격성 통과 표시 (해당없음 → 승인) + await db + .update(vendors) + .set({ + safetyQualificationPassed: true, + updatedAt: currentDate, + }) + .where(eq(vendors.id, vendorId)); + + // 메일 발송: PQ 제출자 + 벤더 + const headersList = await headers(); + const host = headersList.get("host") || "localhost:3000"; + const portalUrl = `${host}/partners/pq_new`; + + // 벤더 정보 조회 + const vendorInfo = await db + .select({ + vendorName: vendors.vendorName, + vendorEmail: vendors.email, + }) + .from(vendors) + .where(eq(vendors.id, vendorId)) + .then(rows => rows[0]); + + // PQ 제출자 이메일 조회 + let requesterEmail: string | null = null; + if (pqSubmission.requesterId) { + const requester = await db + .select({ email: users.email }) + .from(users) + .where(eq(users.id, pqSubmission.requesterId)) + .then(rows => rows[0]); + requesterEmail = requester?.email || null; + } + + const emailContext = { + pqNumber: pqSubmission.pqNumber || `PQ-${pqSubmission.id}`, + vendorName: vendorInfo?.vendorName || "", + statusText: "승인", + portalUrl, + }; + + // 벤더 메일 + if (vendorInfo?.vendorEmail) { + try { + await sendEmail({ + to: vendorInfo.vendorEmail, + subject: `[eVCP] 안전적격성 평가 승인 안내 (${emailContext.pqNumber})`, + template: "safety-pq-approved", + context: emailContext, + }); + } catch (emailError) { + console.error("Failed to send safety approve email to vendor:", emailError); + } + } + + // PQ 제출자 메일 + if (requesterEmail) { + try { + await sendEmail({ + to: requesterEmail, + subject: `[eVCP] 안전적격성 평가 승인 안내 (${emailContext.pqNumber})`, + template: "safety-pq-approved", + context: emailContext, + }); + } catch (emailError) { + console.error("Failed to send safety approve email to requester:", emailError); + } + } + + revalidateTag("pq-submissions"); + revalidateTag(`vendor-pq-submissions-${vendorId}`); + revalidateTag("vendors"); + if (pqSubmission.projectId) { + revalidateTag(`project-pq-submissions-${pqSubmission.projectId}`); + } + revalidatePath("/evcp/pq_new"); + + return { ok: true }; + } catch (error) { + console.error("Safety PQ approve error:", error); + return { ok: false, error: getErrorMessage(error) }; + } +} + +// 안전 PQ 거절 액션 (HSE 단계) +export async function rejectSafetyPQAction({ + pqSubmissionId, + vendorId, + rejectReason, +}: { + pqSubmissionId: number; + vendorId: number; + rejectReason: string; +}) { + unstable_noStore(); + + try { + const currentDate = new Date(); + + const pqSubmission = await db + .select({ + id: vendorPQSubmissions.id, + vendorId: vendorPQSubmissions.vendorId, + projectId: vendorPQSubmissions.projectId, + status: vendorPQSubmissions.status, + type: vendorPQSubmissions.type, + pqNumber: vendorPQSubmissions.pqNumber, + requesterId: vendorPQSubmissions.requesterId, + }) + .from(vendorPQSubmissions) + .where( + and( + eq(vendorPQSubmissions.id, pqSubmissionId), + eq(vendorPQSubmissions.vendorId, vendorId) + ) + ) + .then(rows => rows[0]); + + if (!pqSubmission) { + return { ok: false, error: "PQ submission not found" }; + } + + if (pqSubmission.status !== "SUBMITTED") { + return { + ok: false, + error: `Cannot reject safety PQ in current status: ${pqSubmission.status}` + }; + } + + await db + .update(vendorPQSubmissions) + .set({ + status: "SAFETY_REJECTED", + rejectedAt: currentDate, + rejectReason, + updatedAt: currentDate, + }) + .where(eq(vendorPQSubmissions.id, pqSubmissionId)); + + // 벤더 안전적격성 통과 여부를 거절로 기록 + await db + .update(vendors) + .set({ + safetyQualificationPassed: false, + updatedAt: currentDate, + }) + .where(eq(vendors.id, vendorId)); + + // 메일 발송: PQ 제출자 + 벤더 + const headersList = await headers(); + const host = headersList.get("host") || "localhost:3000"; + const portalUrl = `${host}/partners/pq_new`; + + const vendorInfo = await db + .select({ + vendorName: vendors.vendorName, + vendorEmail: vendors.email, + }) + .from(vendors) + .where(eq(vendors.id, vendorId)) + .then(rows => rows[0]); + + let requesterEmail: string | null = null; + if (pqSubmission.requesterId) { + const requester = await db + .select({ email: users.email }) + .from(users) + .where(eq(users.id, pqSubmission.requesterId)) + .then(rows => rows[0]); + requesterEmail = requester?.email || null; + } + + const emailContext = { + pqNumber: pqSubmission.pqNumber || `PQ-${pqSubmission.id}`, + vendorName: vendorInfo?.vendorName || "", + statusText: "거절", + rejectReason: rejectReason || "", + portalUrl, + }; + + if (vendorInfo?.vendorEmail) { + try { + await sendEmail({ + to: vendorInfo.vendorEmail, + subject: `[eVCP] 안전적격성 평가 거절 안내 (${emailContext.pqNumber})`, + template: "safety-pq-rejected", + context: emailContext, + }); + } catch (emailError) { + console.error("Failed to send safety reject email to vendor:", emailError); + } + } + + if (requesterEmail) { + try { + await sendEmail({ + to: requesterEmail, + subject: `[eVCP] 안전적격성 평가 거절 안내 (${emailContext.pqNumber})`, + template: "safety-pq-rejected", + context: emailContext, + }); + } catch (emailError) { + console.error("Failed to send safety reject email to requester:", emailError); + } + } + + if (pqSubmission.type === "GENERAL") { + await db + .update(vendors) + .set({ + status: "PQ_FAILED", + updatedAt: currentDate, + }) + .where(eq(vendors.id, vendorId)); + } + + revalidateTag("pq-submissions"); + revalidateTag(`vendor-pq-submissions-${vendorId}`); + revalidateTag("vendors"); + if (pqSubmission.projectId) { + revalidateTag(`project-pq-submissions-${pqSubmission.projectId}`); + } + revalidatePath("/evcp/pq_new"); + + return { ok: true }; + } catch (error) { + console.error("Safety PQ reject error:", error); + return { ok: false, error: getErrorMessage(error) }; + } +} + // PQ 승인 액션 export async function approvePQAction({ pqSubmissionId, @@ -2314,8 +2597,9 @@ export async function approvePQAction({ return { ok: false, error: "PQ submission not found" }; } - // 2. 상태 확인 (SUBMITTED 상태만 승인 가능) - if (pqSubmission.status !== "SUBMITTED") { + // 2. 상태 확인 (안전 승인 이후 승인 가능, 기존 SUBMITTED는 호환용) + const allowedStatuses = ["SAFETY_APPROVED", "SUBMITTED"]; + if (!allowedStatuses.includes(pqSubmission.status)) { return { ok: false, error: `Cannot approve PQ in current status: ${pqSubmission.status}` @@ -2363,6 +2647,27 @@ export async function approvePQAction({ }) .where(eq(vendorPQSubmissions.id, pqSubmissionId)); + // 5-1. 미실사 PQ라면 구매자체평가 실사 레코드를 자동 생성 + if (pqSubmission.type === "NON_INSPECTION") { + const existingInvestigation = await db + .select({ id: vendorInvestigations.id }) + .from(vendorInvestigations) + .where(eq(vendorInvestigations.pqSubmissionId, pqSubmissionId)) + .limit(1) + .then(rows => rows[0]); + + if (!existingInvestigation) { + await db.insert(vendorInvestigations).values({ + vendorId, + pqSubmissionId, + investigationStatus: "IN_PROGRESS", + investigationMethod: "PURCHASE_SELF_EVAL", + requestedAt: currentDate, + updatedAt: currentDate, + }); + } + } + // 6. 일반 PQ인 경우 벤더 상태 업데이트 (선택사항) if (pqSubmission.type === "GENERAL") { await db @@ -2788,8 +3093,9 @@ export async function rejectPQAction({ return { ok: false, error: "PQ submission not found" }; } - // 2. 상태 확인 (SUBMITTED 상태만 거부 가능) - if (pqSubmission.status !== "SUBMITTED") { + // 2. 상태 확인 (안전 승인 이후 거부 가능, 기존 SUBMITTED는 호환용) + const allowedStatuses = ["SAFETY_APPROVED", "SUBMITTED"]; + if (!allowedStatuses.includes(pqSubmission.status)) { return { ok: false, error: `Cannot reject PQ in current status: ${pqSubmission.status}` @@ -4872,6 +5178,7 @@ export async function updateInvestigationDetailsAction(input: { }); revalidateTag("pq-submissions"); + revalidateTag("vendor-regular-registrations"); revalidatePath("/evcp/pq_new"); return { |
