summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-06 03:18:19 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-06 03:18:19 +0000
commitceaf46cd523f2bc94bbb35429e5ec0708a242caf (patch)
tree09a1cae97541096857ca9d25a4cc264eb76614d0
parent67fe86f4df464c8665c90870e4ae3c87165d4bb8 (diff)
(최겸) 구매 실사 의뢰 성공 시 캐시 재검증 및 email 추가
-rw-r--r--lib/mail/templates/investigation-request-notification.hbs92
-rw-r--r--lib/pq/service.ts133
2 files changed, 219 insertions, 6 deletions
diff --git a/lib/mail/templates/investigation-request-notification.hbs b/lib/mail/templates/investigation-request-notification.hbs
new file mode 100644
index 00000000..2c0c916b
--- /dev/null
+++ b/lib/mail/templates/investigation-request-notification.hbs
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>eVCP 메일</title>
+ <style>
+ body {
+ margin: 0 !important;
+ padding: 20px !important;
+ background-color: #f4f4f4;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ }
+ .email-container {
+ max-width: 600px;
+ margin: 0 auto;
+ background-color: #ffffff;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ }
+ </style>
+</head>
+<body>
+ <div class="email-container">
+<table width="100%" cellpadding="0" cellspacing="0" style="margin-bottom:24px; border-bottom:1px solid #163CC4; padding-bottom:16px;">
+ <tr>
+ <td align="center">
+ <span style="display: block; text-align: left; color: #163CC4; font-weight: bold; font-size: 32px;">eVCP</span>
+ </td>
+ </tr>
+</table>
+
+<h2 style="font-size:28px; margin-bottom:16px;">실사 의뢰 요청 알림</h2>
+
+<p style="font-size:16px;">안녕하세요, {{recipientName}}님</p>
+
+<p style="font-size:16px;">새로운 실사 의뢰가 생성되었습니다.</p>
+
+<div style="background-color:#F1F5F9; padding:16px; border-radius:4px; margin:16px 0;">
+ <p style="font-size:16px; margin:0 0 8px 0;"><strong>의뢰 정보:</strong></p>
+
+ {{#if vendorNames}}
+ <p style="font-size:16px; margin:8px 0;"><strong>협력업체:</strong></p>
+ <ul style="margin:0; padding-left:20px;">
+ {{#each vendorNames}}
+ <li style="font-size:16px; margin-bottom:4px;">{{this}}</li>
+ {{/each}}
+ </ul>
+ {{/if}}
+
+ {{#if forecastedAt}}
+ <p style="font-size:16px; margin:16px 0 8px 0;"><strong>실사 예정일:</strong></p>
+ <p style="font-size:16px; margin:8px 0;">{{forecastedAt}}</p>
+ {{/if}}
+
+ {{#if investigationAddress}}
+ <p style="font-size:16px; margin:16px 0 8px 0;"><strong>실사 주소:</strong></p>
+ <p style="font-size:16px; margin:8px 0;">{{investigationAddress}}</p>
+ {{/if}}
+
+ {{#if investigationNotes}}
+ <p style="font-size:16px; margin:16px 0 8px 0;"><strong>메모:</strong></p>
+ <p style="font-size:16px; margin:8px 0;">{{investigationNotes}}</p>
+ {{/if}}
+
+ {{#if requesterName}}
+ <p style="font-size:16px; margin:16px 0 8px 0;"><strong>의뢰자:</strong></p>
+ <p style="font-size:16px; margin:8px 0;">{{requesterName}}</p>
+ {{/if}}
+</div>
+
+<p style="text-align: center; margin: 25px 0;">
+ <a href="{{portalUrl}}" target="_blank" style="display:inline-block; background-color:#163CC4; color:#ffffff; padding:10px 20px; text-decoration:none; border-radius:4px;">실사 관리 페이지 바로가기</a>
+</p>
+
+<p style="font-size:16px;">문의사항이 있으시면 시스템 관리자에게 연락해 주세요.</p>
+
+<p style="font-size:16px;">감사합니다.<br />eVCP 팀</p>
+
+<table width="100%" cellpadding="0" cellspacing="0" style="margin-top:32px; border-top:1px solid #e5e7eb; padding-top:16px;">
+ <tr>
+ <td align="center">
+ <p style="font-size:16px; color:#6b7280; margin:4px 0;">© {{currentYear}} EVCP. {{t "email.vendor.invitation.copyright"}}</p>
+ <p style="font-size:16px; color:#6b7280; margin:4px 0;">{{t "email.vendor.invitation.no_reply"}}</p>
+ </td>
+ </tr>
+</table>
+ </div>
+</body>
+</html>
+
diff --git a/lib/pq/service.ts b/lib/pq/service.ts
index fd751b0f..bc3f37a0 100644
--- a/lib/pq/service.ts
+++ b/lib/pq/service.ts
@@ -2920,13 +2920,134 @@ export async function requestInvestigationAction(
return created;
});
- // 캐시 무효화 (핸들러에서 호출 시에는 건너뛰기)
- if (!options?.skipRevalidation) {
- revalidateTag("vendor-investigations");
- revalidateTag("pq-submissions");
- revalidateTag("vendor-pq-submissions");
- revalidatePath("/evcp/pq_new");
+
+ // 이메일 발송 (트랜잭션 외부에서 실행)
+ try {
+ // 1. 협력업체 정보 조회 (이메일 포함)
+ const vendorIds = result.map(inv => inv.vendorId);
+ const uniqueVendorIds = [...new Set(vendorIds)];
+
+ const vendorInfos = await db
+ .select({
+ id: vendors.id,
+ vendorName: vendors.vendorName,
+ email: vendors.email,
+ })
+ .from(vendors)
+ .where(inArray(vendors.id, uniqueVendorIds));
+
+ // 2. QM 담당자 정보 조회
+ const qmManager = await db
+ .select({
+ id: users.id,
+ name: users.name,
+ email: users.email,
+ })
+ .from(users)
+ .where(eq(users.id, data.qmManagerId))
+ .limit(1)
+ .then(rows => rows[0]);
+
+ // 3. 요청자(현재 사용자) 정보 조회
+ const requester = await db
+ .select({
+ id: users.id,
+ name: users.name,
+ email: users.email,
+ })
+ .from(users)
+ .where(eq(users.id, currentUser.id))
+ .limit(1)
+ .then(rows => rows[0]);
+
+ const portalUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000";
+ const currentYear = new Date().getFullYear();
+ const forecastedAtFormatted = format(data.forecastedAt, "yyyy-MM-dd");
+
+ // 4. 협력업체별로 이메일 발송 (investigation-request.hbs 템플릿 사용)
+ const vendorEmailPromises = vendorInfos
+ .filter(vendor => vendor.email) // 이메일이 있는 경우만
+ .map(async (vendor) => {
+ try {
+ await sendEmail({
+ to: vendor.email!,
+ subject: "[eVCP] 협력업체 실사 요청",
+ template: "investigation-request",
+ context: {
+ language: "ko",
+ vendorIds: [vendor.id],
+ notes: data.investigationNotes || "실사가 예정되어 있습니다.",
+ portalUrl: `${portalUrl}/ko/partners/site-visit`,
+ currentYear: currentYear,
+ },
+ });
+ console.log(`협력업체 이메일 발송 완료: ${vendor.vendorName} (${vendor.email})`);
+ } catch (emailError) {
+ console.error(`협력업체 이메일 발송 실패: ${vendor.vendorName} (${vendor.email})`, emailError);
+ }
+ });
+
+ await Promise.all(vendorEmailPromises);
+
+ // 5. QM 담당자에게 알림 이메일 발송
+ if (qmManager?.email) {
+ try {
+ const vendorNames = vendorInfos.map(v => v.vendorName);
+
+ await sendEmail({
+ to: qmManager.email,
+ subject: "[eVCP] 실사 의뢰 요청 알림",
+ template: "investigation-request-notification",
+ context: {
+ language: "ko",
+ recipientName: qmManager.name,
+ vendorNames: vendorNames,
+ forecastedAt: forecastedAtFormatted,
+ investigationAddress: data.investigationAddress,
+ investigationNotes: data.investigationNotes || null,
+ requesterName: requester?.name || "알 수 없음",
+ portalUrl: `${portalUrl}/evcp/vendor-investigation`,
+ currentYear: currentYear,
+ },
+ });
+ console.log(`QM 담당자 이메일 발송 완료: ${qmManager.name} (${qmManager.email})`);
+ } catch (emailError) {
+ console.error(`QM 담당자 이메일 발송 실패: ${qmManager.name} (${qmManager.email})`, emailError);
+ }
+ }
+
+ // 6. 요청자(현재 사용자)에게 알림 이메일 발송 (QM 담당자와 다른 경우만)
+ // if (requester?.email && requester.id !== data.qmManagerId) {
+ // try {
+ // const vendorNames = vendorInfos.map(v => v.vendorName);
+
+ // await sendEmail({
+ // to: requester.email,
+ // subject: "[eVCP] 실사 의뢰 요청 알림",
+ // template: "investigation-request-notification",
+ // context: {
+ // language: "ko",
+ // recipientName: requester.name,
+ // vendorNames: vendorNames,
+ // forecastedAt: forecastedAtFormatted,
+ // investigationAddress: data.investigationAddress,
+ // investigationNotes: data.investigationNotes || null,
+ // requesterName: requester.name,
+ // portalUrl: `${portalUrl}/evcp/vendor-investigation`,
+ // currentYear: currentYear,
+ // },
+ // });
+ // console.log(`요청자 이메일 발송 완료: ${requester.name} (${requester.email})`);
+ // } catch (emailError) {
+ // console.error(`요청자 이메일 발송 실패: ${requester.name} (${requester.email})`, emailError);
+ // }
+ // }
+ } catch (emailErr) {
+ // 이메일 발송 실패는 로그만 남기고 전체 프로세스는 성공으로 처리
+ console.error("이메일 발송 중 오류 발생:", emailErr);
}
+ revalidatePath("/evcp/pq_new");
+ revalidatePath("/evcp/vendor-investigation");
return {
success: true,