diff options
| -rw-r--r-- | lib/mail/templates/contract-sign-request.hbs | 18 | ||||
| -rw-r--r-- | lib/vendors/service.ts | 129 | ||||
| -rw-r--r-- | 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 @@ </p>
<div style="background-color: #f3f4f6; border-radius: 4px; padding: 15px; margin: 20px 0;">
- <p style="font-size:16px; margin:4px 0;"><strong>계약서 정보:</strong></p>
- <p style="font-size:16px; margin:4px 0;">계약서 종류: {{templateName}}</p>
- <p style="font-size:16px; margin:4px 0;">계약 번호: {{contractId}}</p>
+ <p style="font-size:16px; margin:4px 0;"><strong>요청된 계약서 목록 (총 {{templateCount}}개):</strong></p>
+
+ {{#each templates}}
+ <div style="background-color: #ffffff; border: 1px solid #e5e7eb; border-radius: 4px; padding: 12px; margin: 8px 0;">
+ <p style="font-size:16px; margin:4px 0;"><strong>{{templateName}}</strong></p>
+ </div>
+ {{/each}}
</div>
<p style="font-size:16px; line-height:32px; margin-bottom:16px;">
@@ -71,14 +75,6 @@ 감사합니다.
</p>
-<div style="margin-top: 30px;">
- <p style="font-size:16px; line-height:24px;">
- <strong>담당자 연락처:</strong><br>
- 이메일: contact@company.com<br>
- 전화: 02-123-4567
- </p>
-</div>
-
<table width="100%" cellpadding="0" cellspacing="0" style="margin-top:32px; border-top:1px solid #e5e7eb; padding-top:16px;">
<tr>
<td align="center">
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) { @@ -3016,6 +2991,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 + : "기본계약서 이메일 발송 중 오류가 발생했습니다." + }; + } +} + +/** * 비밀유지 계약서 첨부파일 저장 서버 액션 */ export async function saveNdaAttachments(input: { 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 = (
<div className="space-y-4 py-2">
{/* 선택된 협력업체 정보 */}
|
