diff options
Diffstat (limited to 'lib/tbe-last/service.ts')
| -rw-r--r-- | lib/tbe-last/service.ts | 181 |
1 files changed, 179 insertions, 2 deletions
diff --git a/lib/tbe-last/service.ts b/lib/tbe-last/service.ts index 32d5a5f5..34c274f5 100644 --- a/lib/tbe-last/service.ts +++ b/lib/tbe-last/service.ts @@ -1,7 +1,7 @@ // lib/tbe-last/service.ts 'use server' -import { unstable_cache } from "next/cache"; +import { revalidatePath, unstable_cache } from "next/cache"; import db from "@/db/db"; import { and, desc, asc, eq, sql, or, isNull, isNotNull, ne, inArray } from "drizzle-orm"; import { tbeLastView, tbeDocumentsView } from "@/db/schema"; @@ -547,4 +547,181 @@ function getReviewStatusClass(status?: string): string { default: return "unreviewed" } -}
\ No newline at end of file +} + + +interface RfqInfo { + rfqCode: string; + rfqTitle: string; + rfqDueDate: Date | null; + projectCode: string; + projectName: string; + packageNo: string; + packageName: string; + picName: string; + rfqId: number; // rfqLastId 추가 +} + +interface VendorInfo { + sessionId: number; + vendorId: number; // vendor ID 추가 + vendorCode: string; + vendorName: string; +} + +export async function requestTBEForRFQ( + rfqInfo: RfqInfo, + vendors: VendorInfo[] +) { + try { + const session = await getServerSession(authOptions) + if (!session?.user) { + return { success: false, error: "인증이 필요합니다" } + } + + // 벤더별 이메일 정보 조회 + const vendorEmails = await db + .select({ + vendorId: rfqLastDetails.vendorsId, + emailSentTo: rfqLastDetails.emailSentTo, + }) + .from(rfqLastDetails) + .where( + and( + eq(rfqLastDetails.rfqsLastId, rfqInfo.rfqId), + inArray(rfqLastDetails.vendorsId, vendors.map(v => v.vendorId)), + eq(rfqLastDetails.isLatest, true) + ) + ); + + // 이메일 정보 매핑 + const vendorEmailMap = new Map(); + vendorEmails.forEach(ve => { + if (ve.emailSentTo) { + try { + const emailData = JSON.parse(ve.emailSentTo); + vendorEmailMap.set(ve.vendorId, emailData); + } catch (error) { + console.error(`이메일 파싱 실패 - vendorId: ${ve.vendorId}`, error); + } + } + }); + + // 1. 트랜잭션으로 모든 세션 상태 업데이트 + await db.transaction(async (tx) => { + // 세션 상태 업데이트 + const sessionIds = vendors.map(v => v.sessionId); + + await tx + .update(rfqLastTbeSessions) + .set({ + status: "진행중", + updatedAt: new Date(), + }) + .where(inArray(rfqLastTbeSessions.id, sessionIds)); + }); + + // 2. 각 벤더에게 이메일 발송 + const emailPromises = vendors.map(async (vendor) => { + const emailInfo = vendorEmailMap.get(vendor.vendorId); + + if (!emailInfo) { + console.warn(`벤더 ${vendor.vendorName}의 이메일 정보가 없습니다.`); + return { success: false, vendor: vendor.vendorName, error: "이메일 정보 없음" }; + } + + try { + // to와 cc 이메일 추출 + const toEmails = Array.isArray(emailInfo.to) ? emailInfo.to : [emailInfo.to]; + const ccEmails = Array.isArray(emailInfo.cc) ? emailInfo.cc : []; + + // 모든 to 이메일 주소로 발송 + const emailResults = await Promise.all( + toEmails.filter(Boolean).map(toEmail => + sendEmail({ + to: toEmail, + template: "tbe-request", + subject: `[TBE 요청] ${rfqInfo.rfqCode} - 기술입찰평가 서류 제출 요청`, + context: { + vendorName: vendor.vendorName, + vendorCode: vendor.vendorCode, + rfqCode: rfqInfo.rfqCode, + rfqTitle: rfqInfo.rfqTitle, + rfqDueDate: rfqInfo.rfqDueDate ? + new Date(rfqInfo.rfqDueDate).toLocaleDateString("ko-KR") : + "미정", + projectCode: rfqInfo.projectCode, + projectName: rfqInfo.projectName, + packageNo: rfqInfo.packageNo, + packageName: rfqInfo.packageName, + picName: rfqInfo.picName, + picEmail: session.user.email, + picPhone: process.env.DEFAULT_PIC_PHONE || "", + tbeDeadline: calculateTBEDeadline(rfqInfo.rfqDueDate), + companyName: process.env.COMPANY_NAME || "Your Company", + }, + cc: [ + ...ccEmails.filter(Boolean) + ], + }) + ) + ); + + return { + success: true, + vendor: vendor.vendorName, + emailsSent: emailResults.length + }; + + } catch (error) { + console.error(`이메일 발송 실패 - ${vendor.vendorName}:`, error); + return { success: false, vendor: vendor.vendorName, error }; + } + }); + + const emailResults = await Promise.allSettled(emailPromises); + + // 3. 결과 확인 + const successResults = emailResults.filter( + r => r.status === "fulfilled" && r.value?.success + ); + const failedResults = emailResults.filter( + r => r.status === "rejected" || (r.status === "fulfilled" && !r.value?.success) + ); + + if (failedResults.length > 0) { + console.warn(`${failedResults.length}개 벤더의 이메일 발송 실패`); + } + + revalidatePath("/evcp/tbe-last"); + + return { + success: true, + message: `${successResults.length}개 벤더에 TBE 요청 완료`, + emailsSent: successResults.length, + emailsFailed: failedResults.length + }; + + } catch (error) { + console.error("TBE 요청 실패:", error); + return { + success: false, + error: error instanceof Error ? error.message : "TBE 요청 중 오류가 발생했습니다." + }; + } +} + +// TBE 제출 기한 계산 (RFQ 마감일 7일 전) +function calculateTBEDeadline(rfqDueDate: Date | null): string { + if (!rfqDueDate) return "추후 공지"; + + const deadline = new Date(rfqDueDate); + deadline.setDate(deadline.getDate() - 7); // 7일 전 + + return deadline.toLocaleDateString("ko-KR", { + year: "numeric", + month: "long", + day: "numeric", + }); +} + |
