summaryrefslogtreecommitdiff
path: root/lib/tbe-last/service.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tbe-last/service.ts')
-rw-r--r--lib/tbe-last/service.ts181
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",
+ });
+}
+