1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
/**
* RFQ 발송 결재 서버 액션
*
* 첨부파일이 있는 RFQ를 발송할 때 결재를 거치는 서버 액션
*/
'use server';
import { ApprovalSubmissionSaga } from '@/lib/approval';
import { mapRfqSendToTemplateVariables } from './approval-handlers';
interface RfqSendApprovalData {
// RFQ 기본 정보
rfqId: number;
rfqCode?: string;
// 발송 데이터
vendors: Array<{
vendorId: number;
vendorName: string;
vendorCode?: string | null;
vendorCountry?: string | null;
selectedMainEmail: string;
additionalEmails: string[];
customEmails?: Array<{ email: string; name?: string }>;
currency?: string | null;
contractRequirements?: {
ndaYn: boolean;
generalGtcYn: boolean;
projectGtcYn: boolean;
agreementYn: boolean;
projectCode?: string;
};
isResend: boolean;
sendVersion?: number;
}>;
attachmentIds: number[];
// 첨부파일 정보 (파일명, 크기 등)
attachments: Array<{
fileName?: string | null;
fileSize?: number | null;
}>;
message?: string;
generatedPdfs?: Array<{
key: string;
buffer: number[];
fileName: string;
}>;
hasToSendEmail?: boolean;
// 신청 사유
applicationReason: string;
// 결재 정보
currentUser: {
id: number;
epId: string | null;
name?: string;
email?: string;
};
approvers?: string[]; // Knox EP ID 배열
}
/**
* RFQ 발송 결재 상신
*
* 첨부파일이 있는 경우 결재를 거쳐 RFQ를 발송합니다.
*/
export async function requestRfqSendWithApproval(data: RfqSendApprovalData) {
// 1. 입력 검증
if (!data.currentUser.epId) {
throw new Error('Knox EP ID가 필요합니다. 시스템 관리자에게 문의하세요.');
}
if (!data.vendors || data.vendors.length === 0) {
throw new Error('발송할 벤더를 선택해주세요.');
}
if (!data.attachmentIds || data.attachmentIds.length === 0) {
throw new Error('첨부파일이 없습니다. 결재가 필요하지 않습니다.');
}
console.log('[RFQ Approval] Starting approval process for RFQ send');
console.log('[RFQ Approval] RFQ ID:', data.rfqId);
console.log('[RFQ Approval] Vendors:', data.vendors.length);
console.log('[RFQ Approval] Attachments:', data.attachmentIds.length);
try {
// 2. 템플릿 변수 매핑
const variables = await mapRfqSendToTemplateVariables({
attachments: data.attachments,
vendorNames: data.vendors.map(v => v.vendorName),
applicationReason: data.applicationReason,
});
// 3. 결재 상신용 payload 구성
// ⚠️ cronjob 환경에서 실행되므로 currentUser 정보를 포함해야 함
const approvalPayload = {
rfqId: data.rfqId,
rfqCode: data.rfqCode,
vendors: data.vendors,
attachmentIds: data.attachmentIds,
message: data.message,
generatedPdfs: data.generatedPdfs,
hasToSendEmail: data.hasToSendEmail,
currentUser: {
id: data.currentUser.id,
name: data.currentUser.name,
email: data.currentUser.email,
epId: data.currentUser.epId,
},
};
// 4. Saga로 결재 상신
const saga = new ApprovalSubmissionSaga(
'rfq_send_with_attachments', // 핸들러 키
approvalPayload, // 결재 승인 후 실행될 데이터
{
title: `암호화해제 신청 - ${data.rfqCode || 'RFQ'}`,
description: `${data.vendors.length}개 업체에 첨부파일 ${data.attachmentIds.length}개를 포함한 암호화해제 신청`,
templateName: '암호화해제 신청', // DB에 있어야 함
variables,
approvers: data.approvers,
currentUser: {
id: data.currentUser.id,
epId: data.currentUser.epId,
email: data.currentUser.email,
},
}
);
const result = await saga.execute();
console.log('[RFQ Approval] ✅ Approval submitted successfully');
console.log('[RFQ Approval] Approval ID:', result.approvalId);
console.log('[RFQ Approval] Pending Action ID:', result.pendingActionId);
return {
success: true,
...result,
message: `결재가 상신되었습니다. (결재 ID: ${result.approvalId})`,
};
} catch (error) {
console.error('[RFQ Approval] ❌ Failed to submit approval:', error);
throw new Error(
error instanceof Error
? error.message
: 'RFQ 발송 결재 상신에 실패했습니다.'
);
}
}
|