From 69fd0851bb1bc1d767d50eecef044e20f325a8fa Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 18 Sep 2025 01:44:39 +0000 Subject: (최겸) 구매 PQ 요청 시 기본계약 통합 단일 이메일 발송 건 수정 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/mail/templates/contract-sign-request.hbs | 18 ++-- lib/vendors/service.ts | 129 +++++++++++++++++++++------ lib/vendors/table/request-pq-dialog.tsx | 46 +++++++++- 3 files changed, 155 insertions(+), 38 deletions(-) diff --git a/lib/mail/templates/contract-sign-request.hbs b/lib/mail/templates/contract-sign-request.hbs index 5fa00e12..32b889a8 100644 --- a/lib/mail/templates/contract-sign-request.hbs +++ b/lib/mail/templates/contract-sign-request.hbs @@ -44,9 +44,13 @@

-

계약서 정보:

-

계약서 종류: {{templateName}}

-

계약 번호: {{contractId}}

+

요청된 계약서 목록 (총 {{templateCount}}개):

+ + {{#each templates}} +
+

{{templateName}}

+
+ {{/each}}

@@ -71,14 +75,6 @@ 감사합니다.

-
-

- 담당자 연락처:
- 이메일: contact@company.com
- 전화: 02-123-4567 -

-
-
diff --git a/lib/vendors/service.ts b/lib/vendors/service.ts index 45a0e0b5..9a37f5d7 100644 --- a/lib/vendors/service.ts +++ b/lib/vendors/service.ts @@ -2943,32 +2943,7 @@ export async function requestBasicContractInfo({ }) .returning(); - // 3-2. 협력업체에 이메일 발송 - const subject = `[${process.env.COMPANY_NAME || '회사명'}] 기본계약서 서명 요청`; - - const headersList = await headers(); - const host = headersList.get('host') || 'localhost:3000'; - // 로그인 또는 서명 페이지 URL 생성 - const baseUrl = process.env.NEXT_PUBLIC_URL || `http://${host}`; - const loginUrl = `${baseUrl}/partners/basic-contract`; - console.log("loginUrl-basic-contract", loginUrl); - - // 사용자 언어 설정 (기본값은 한국어) - const userLang = "ko"; - - // 이메일 발송 - await sendEmail({ - to: vendor.email, - subject, - template: "contract-sign-request", // 이메일 템플릿 이름 - context: { - vendorName: vendor.vendorName, - contractId: newContract.id, - templateName: template.templateName, - loginUrl, - language: userLang, - }, - }); + // 이메일 발송은 별도 함수(sendBasicContractEmail)에서 처리 return { vendorId: vendor.id, success: true }; } catch (err) { @@ -3015,6 +2990,108 @@ export async function requestBasicContractInfo({ } } +/** + * 기본계약서 이메일 발송 함수 + * 협력업체당 하나의 이메일만 발송하며, 전달받은 템플릿 정보를 포함 + */ +export async function sendBasicContractEmail({ + vendorIds, + templateIds, + requestedBy +}: { + vendorIds: number[]; + templateIds: number[]; + requestedBy: number; +}): Promise<{ success?: boolean; error?: string }> { + if (!vendorIds || vendorIds.length === 0) { + return { error: "협력업체 ID가 제공되지 않았습니다." }; + } + + if (!templateIds || templateIds.length === 0) { + return { error: "템플릿 ID가 제공되지 않았습니다." }; + } + + try { + // 협력업체 정보 가져오기 + const vendorList = await db + .select() + .from(vendors) + .where(inArray(vendors.id, vendorIds)); + + if (!vendorList || vendorList.length === 0) { + return { error: "선택한 협력업체 정보를 찾을 수 없습니다." }; + } + + // 템플릿 정보 가져오기 + const templateList = await db + .select() + .from(basicContractTemplates) + .where(inArray(basicContractTemplates.id, templateIds)); + + if (!templateList || templateList.length === 0) { + return { error: "선택한 템플릿 정보를 찾을 수 없습니다." }; + } + + // 각 협력업체에 이메일 발송 + const emailResults = await Promise.all( + vendorList.map(async (vendor) => { + if (!vendor.email) return { vendorId: vendor.id, success: true }; // 이메일이 없으면 스킵 + + try { + const subject = `기본계약서 서명 요청`; + + const headersList = await headers(); + const host = headersList.get('host') || 'localhost:3000'; + const baseUrl = process.env.NEXT_PUBLIC_URL || `http://${host}`; + const loginUrl = `${baseUrl}/partners/basic-contract`; + + // 사용자 언어 설정 (기본값은 한국어) + const userLang = "ko"; + + // 이메일 발송 (요청된 템플릿 정보 포함) + await sendEmail({ + to: vendor.email, + subject, + template: "contract-sign-request", + context: { + vendorName: vendor.vendorName, + templates: templateList, // 요청된 템플릿 목록 + templateCount: templateList.length, + loginUrl, + language: userLang, + }, + }); + + return { vendorId: vendor.id, success: true }; + } catch (err) { + console.error(`협력업체 ${vendor.id} 이메일 발송 중 오류:`, err); + return { vendorId: vendor.id, success: false, error: getErrorMessage(err) }; + } + }) + ); + + // 실패한 이메일 발송 확인 + const failedEmails = emailResults.filter(r => !r.success); + + if (failedEmails.length > 0) { + console.error("일부 협력업체 이메일 발송 실패:", failedEmails); + return { + success: true, + error: `${emailResults.length - failedEmails.length}개 협력업체 이메일 발송 성공, ${failedEmails.length}개 실패` + }; + } + + return { success: true }; + } catch (error) { + console.error("기본계약서 이메일 발송 중 오류 발생:", error); + return { + error: error instanceof Error + ? error.message + : "기본계약서 이메일 발송 중 오류가 발생했습니다." + }; + } +} + /** * 비밀유지 계약서 첨부파일 저장 서버 액션 */ diff --git a/lib/vendors/table/request-pq-dialog.tsx b/lib/vendors/table/request-pq-dialog.tsx index 767b979f..aeb0c717 100644 --- a/lib/vendors/table/request-pq-dialog.tsx +++ b/lib/vendors/table/request-pq-dialog.tsx @@ -39,7 +39,7 @@ import { Input } from "@/components/ui/input" import { Badge } from "@/components/ui/badge" import { Progress } from "@/components/ui/progress" import { Vendor } from "@/db/schema/vendors" -import { requestBasicContractInfo, requestPQVendors } from "../service" +import { requestBasicContractInfo, requestPQVendors, sendBasicContractEmail } from "../service" import { getProjectsWithPQList } from "@/lib/pq/service" import type { Project } from "@/lib/pq/service" import { useSession } from "next-auth/react" @@ -364,6 +364,14 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro setProgressValue((completedSteps / totalSteps) * 100) } } + //5단계: 각 협력업체들에게 기본계약서 이메일 발송 + if (selectedTemplateIds.length > 0) { + setCurrentStep(`기본계약서 이메일 발송 중... (${selectedTemplateIds.length}개 템플릿)`) + console.log("📋 기본계약서 이메일 발송 시작", selectedTemplateIds.length, "개 템플릿") + await processBasicContractsEmail(selectedTemplateIds, vendors) + completedSteps++ + setProgressValue((completedSteps / totalSteps) * 100) + } setCurrentStep("완료!") setProgressValue(100) @@ -506,6 +514,7 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro } console.log(`✅ 기본계약 생성 완료: ${template.templateName}`) + } finally { // 임시 WebViewer 정리 @@ -601,6 +610,41 @@ export function RequestPQDialog({ vendors, showTrigger = true, onSuccess, ...pro } } + const processBasicContractsEmail = async (templateIds: number[], vendors: any[]) => { + if (!session?.user?.id) { + toast.error("인증 정보가 없습니다") + return + } + try { + const vendorIds = vendors.map(v => v.id) + const userId = Number(session.user.id) + + // 2. 성공한 템플릿이 있으면 이메일 발송 + if (templateIds.length > 0) { + const emailResult = await sendBasicContractEmail({ + vendorIds, + templateIds, + requestedBy: userId + }) + + if (emailResult.success) { + toast.success(`${templateIds.length}개 템플릿에 대한 기본계약서가 생성되었고, ${vendorIds.length}개 협력업체에 이메일이 발송되었습니다`) + } else { + toast.warning(`계약서는 생성되었으나 일부 이메일 발송 실패: ${emailResult.error}`) + } + } else { + toast.error("기본계약서 생성에 실패했습니다") + } + + } catch (error) { + console.error('기본계약서 이메일 발송 중 오류:', error) + toast.error(`기본계약서 이메일 발송 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`) + } +} + + + + const dialogContent = (
{/* 선택된 협력업체 정보 */} -- cgit v1.2.3