diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-06 03:18:19 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-11-06 03:18:19 +0000 |
| commit | ceaf46cd523f2bc94bbb35429e5ec0708a242caf (patch) | |
| tree | 09a1cae97541096857ca9d25a4cc264eb76614d0 | |
| parent | 67fe86f4df464c8665c90870e4ae3c87165d4bb8 (diff) | |
(최겸) 구매 실사 의뢰 성공 시 캐시 재검증 및 email 추가
| -rw-r--r-- | lib/mail/templates/investigation-request-notification.hbs | 92 | ||||
| -rw-r--r-- | lib/pq/service.ts | 133 |
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,
|
