/** * 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 발송 결재 상신에 실패했습니다.' ); } }