/** * 기술영업 RFQ 발송 결재 서버 액션 * * DRM 파일이 있는 기술영업 RFQ를 발송할 때 결재를 거치는 서버 액션 */ 'use server'; import { ApprovalSubmissionSaga } from '@/lib/approval'; import { mapTechSalesRfqSendToTemplateVariables } from './approval-handlers'; import { revalidatePath, revalidateTag } from 'next/cache'; interface TechSalesRfqSendApprovalData { // RFQ 기본 정보 rfqId: number; rfqCode?: string; rfqType: "SHIP" | "TOP" | "HULL"; // 발송 데이터 vendorIds: number[]; selectedContacts?: Array<{ vendorId: number; contactId: number; contactEmail: string; contactName: string; }>; drmAttachmentIds: number[]; // 첨부파일 정보 (파일명, 크기 등) drmAttachments: Array<{ fileName?: string | null; fileSize?: number | null; }>; // 신청 사유 applicationReason: string; // 결재 정보 currentUser: { id: number; epId: string | null; name?: string; email?: string; }; approvers?: string[]; // Knox EP ID 배열 } /** * 기술영업 RFQ 발송 결재 상신 (초기 발송) * * DRM 파일이 있는 경우 결재를 거쳐 RFQ를 발송합니다. */ export async function requestTechSalesRfqSendWithApproval(data: TechSalesRfqSendApprovalData) { // 1. 입력 검증 if (!data.currentUser.epId) { throw new Error('Knox EP ID가 필요합니다. 시스템 관리자에게 문의하세요.'); } if (!data.vendorIds || data.vendorIds.length === 0) { throw new Error('발송할 벤더를 선택해주세요.'); } if (!data.drmAttachmentIds || data.drmAttachmentIds.length === 0) { throw new Error('DRM 첨부파일이 없습니다. 결재가 필요하지 않습니다.'); } console.log('[TechSales RFQ Approval] Starting approval process for RFQ send'); console.log('[TechSales RFQ Approval] RFQ ID:', data.rfqId); console.log('[TechSales RFQ Approval] Vendors:', data.vendorIds.length); console.log('[TechSales RFQ Approval] DRM Attachments:', data.drmAttachmentIds.length); try { // 2. RFQ 상태를 "결재 진행중"으로 변경 const db = (await import('@/db/db')).default; const { techSalesRfqs, TECH_SALES_RFQ_STATUSES } = await import('@/db/schema/techSales'); const { eq } = await import('drizzle-orm'); await db.update(techSalesRfqs) .set({ status: TECH_SALES_RFQ_STATUSES.APPROVAL_IN_PROGRESS, updatedAt: new Date(), }) .where(eq(techSalesRfqs.id, data.rfqId)); console.log('[TechSales RFQ Approval] RFQ status updated to APPROVAL_IN_PROGRESS'); // 3. 벤더 이름 조회 const { getTechSalesRfqVendors } = await import('./service'); const vendorsResult = await getTechSalesRfqVendors(data.rfqId); const vendorNames = vendorsResult.data?.filter(v => data.vendorIds.includes(v.vendorId)) .map(v => v.vendorName) || []; // 4. 템플릿 변수 매핑 const variables = await mapTechSalesRfqSendToTemplateVariables({ attachments: data.drmAttachments, vendorNames: vendorNames, applicationReason: data.applicationReason, }); // 5. 결재 상신용 payload 구성 const approvalPayload = { rfqId: data.rfqId, rfqCode: data.rfqCode, vendorIds: data.vendorIds, selectedContacts: data.selectedContacts, drmAttachmentIds: data.drmAttachmentIds, currentUser: { id: data.currentUser.id, name: data.currentUser.name, email: data.currentUser.email, epId: data.currentUser.epId, }, }; // 6. Saga로 결재 상신 const saga = new ApprovalSubmissionSaga( 'tech_sales_rfq_send_with_drm', // 핸들러 키 approvalPayload, // 결재 승인 후 실행될 데이터 { title: `암호화해제 신청 - ${data.rfqCode || 'RFQ'}`, description: `${vendorNames.length}개 업체에 DRM 첨부파일 ${data.drmAttachmentIds.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('[TechSales RFQ Approval] ✅ Approval submitted successfully'); console.log('[TechSales RFQ Approval] Approval ID:', result.approvalId); console.log('[TechSales RFQ Approval] Pending Action ID:', result.pendingActionId); revalidateTag("techSalesRfqs"); revalidateTag("techSalesVendorQuotations"); revalidateTag(`techSalesRfq-${data.rfqId}`); revalidatePath(getTechSalesRevalidationPath(data.rfqType || "SHIP")); return { success: true, ...result, message: `결재가 상신되었습니다. (결재 ID: ${result.approvalId})`, }; } catch (error) { console.error('[TechSales RFQ Approval] ❌ Failed to submit approval:', error); throw new Error( error instanceof Error ? error.message : '기술영업 RFQ 발송 결재 상신에 실패했습니다.' ); } } /** * RFQ 타입에 따른 캐시 무효화 경로 반환 */ function getTechSalesRevalidationPath(rfqType: "SHIP" | "TOP" | "HULL"): string { switch (rfqType) { case "SHIP": return "/evcp/budgetary-tech-sales-ship"; case "TOP": return "/evcp/budgetary-tech-sales-top"; case "HULL": return "/evcp/budgetary-tech-sales-hull"; default: return "/evcp/budgetary-tech-sales-ship"; } } /** * 기술영업 RFQ 재발송 결재 상신 * * 이미 발송된 RFQ에 DRM 파일이 추가된 경우 재발송을 위한 결재 상신 */ export async function requestRfqResendWithDrmApproval(data: { rfqId: number; rfqCode?: string; drmFiles: Array<{ file: File; attachmentType: string; description?: string; }>; applicationReason: string; currentUser: { id: number; epId: string | null; name?: string; email?: string; }; approvers?: string[]; }) { if (!data.currentUser.epId) { throw new Error('Knox EP ID가 필요합니다.'); } console.log('[RFQ Resend Approval] Starting resend approval process'); console.log('[RFQ Resend Approval] RFQ ID:', data.rfqId); console.log('[RFQ Resend Approval] DRM Files:', data.drmFiles.length); try { // 템플릿 변수 매핑 const variables = await mapTechSalesRfqSendToTemplateVariables({ attachments: data.drmFiles.map(f => ({ fileName: f.file.name, fileSize: f.file.size, })), vendorNames: [], // 기존 벤더 목록은 후처리에서 조회 applicationReason: data.applicationReason, }); // 결재 payload 구성 const approvalPayload = { rfqId: data.rfqId, rfqCode: data.rfqCode, drmFiles: data.drmFiles, currentUser: { id: data.currentUser.id, name: data.currentUser.name, email: data.currentUser.email, epId: data.currentUser.epId, }, }; console.log('approvalPayload', approvalPayload); // Saga로 결재 상신 const saga = new ApprovalSubmissionSaga( 'tech_sales_rfq_resend_with_drm', // 핸들러 키 approvalPayload, { title: `DRM 파일 재발송 결재 - ${data.rfqCode || 'RFQ'}`, description: `이미 발송된 RFQ에 ${data.drmFiles.length}개의 DRM 파일이 추가되어 재발송을 요청합니다.`, templateName: '암호화해제 신청', variables, approvers: data.approvers, currentUser: { id: data.currentUser.id, name: data.currentUser.name, epId: data.currentUser.epId, email: data.currentUser.email, }, } ); const result = await saga.execute(); console.log('[RFQ Resend Approval] ✅ Resend approval submitted successfully'); return { success: true, ...result, message: `재발송 결재가 상신되었습니다. (결재 ID: ${result.approvalId})`, }; } catch (error) { console.error('[RFQ Resend Approval] ❌ Failed to submit resend approval:', error); throw new Error( error instanceof Error ? error.message : 'RFQ 재발송 결재 상신에 실패했습니다.' ); } }