'use server' import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult } from "@/lib/soap/sender"; import { getCurrentSAPDate, getCurrentSAPTime } from "@/lib/soap/utils"; // ECC PO 생성 엔드포인트 (WSDL에 명시된 P2038_D 사용) const ECC_PO_ENDPOINT = "http://shii8dvddb01.hec.serp.shi.samsung.net:50000/sap/xi/engine?type=entry&version=3.0&Sender.Service=P2038_Q&Interface=http%3A%2F%2Fshi.samsung.co.kr%2FP2_MM%2FMMM%5EP2MM3015_SO"; // PO 헤더 데이터 타입 export interface POHeaderData { ANFNR: string; // Bidding Number (M) // BiddingNumber이지만, RFQ/Bidding 모두 받은 ANFNR 사용하면 됩니다. (RFQ/Bidding Header Key) LIFNR: string; // Vendor Account Number (M) ZPROC_IND: string; // Purchasing Processing State (M) ANGNR?: string; // Bidding Number // 더 이상 사용하지 않음. 보내지 않으면 됨. WAERS: string; // Currency Key (M) ZTERM: string; // Terms of Payment Key (M) INCO1: string; // Incoterms (Part 1) (M) //인코텀즈코드 3자리 INCO2: string; // Incoterms (Part 2) (M) //자유입력28자리, 인코텀즈 풀네임 28자리로 잘라 넣으면 될 듯... 데이터예시: 시방서에 따름 VSTEL?: string; // Shipping Point LSTEL?: string; // loading Point MWSKZ: string; // Tax on sales/purchases code (M) LANDS: string; // Country for Tax (M) ZRCV_DT: string; // Bidding Receive Date (M) ZATTEN_IND: string; // Attendance Indicator (M) IHRAN: string; // Bidding Submission Date (M) TEXT?: string; // PO Header note ZDLV_CNTLR?: string; // Delivery Controller ZDLV_PRICE_T?: string; // 납품대금연동제대상여부 ZDLV_PRICE_NOTE?: string; // 연동제 노트 } // PO 아이템 데이터 타입 export interface POItemData { ANFNR: string; // Bidding Number (M) ANFPS: string; // Item Number of Bidding (M) LIFNR: string; // Vendor Account Number (M) NETPR: string; // Net Price (M) PEINH: string; // Price Unit (M) BPRME: string; // Order Unit of Measure (M) NETWR: string; // Net Order Value (M) BRTWR: string; // Gross Order Value (M) LFDAT: string; // Item Delivery Date (M) ZCON_NO_PO?: string; // PR Consolidation Number EBELP?: string; // Series PO Item Seq } // PR 반환 데이터 타입 export interface PRReturnData { ANFNR: string; // PR Request Number (M) ANFPS: string; // Item Number of PR Request (M) EBELN: string; // Purchase Requisition Number (M) EBELP: string; // Item Number of Purchase Requisition (M) MSGTY: string; // Message Type (M) MSGTXT?: string; // Message Text } // PO 생성 요청 데이터 타입 export interface POCreateRequest { T_Bidding_HEADER: POHeaderData[]; T_Bidding_ITEM: POItemData[]; T_PR_RETURN: PRReturnData[]; EV_ERDAT?: string; // Extract Date EV_ERZET?: string; // Extract Time } // SOAP Body Content 생성 함수 function createPOSoapBodyContent(poData: POCreateRequest): Record { return { 'p1:MT_P2MM3015_S': { // WSDL에서 사용하는 p1 접두사 적용 'T_Bidding_HEADER': poData.T_Bidding_HEADER, 'T_Bidding_ITEM': poData.T_Bidding_ITEM, 'T_PR_RETURN': poData.T_PR_RETURN, ...(poData.EV_ERDAT && { 'EV_ERDAT': poData.EV_ERDAT }), ...(poData.EV_ERZET && { 'EV_ERZET': poData.EV_ERZET }) } }; } // PO 데이터 검증 함수 function validatePOData(poData: POCreateRequest): { isValid: boolean; errors: string[] } { const errors: string[] = []; // 헤더 데이터 검증 if (!poData.T_Bidding_HEADER || poData.T_Bidding_HEADER.length === 0) { errors.push('T_Bidding_HEADER는 필수입니다.'); } else { poData.T_Bidding_HEADER.forEach((header, index) => { const requiredFields = ['ANFNR', 'LIFNR', 'ZPROC_IND', 'WAERS', 'ZTERM', 'INCO1', 'INCO2', 'MWSKZ', 'LANDS', 'ZRCV_DT', 'ZATTEN_IND', 'IHRAN']; requiredFields.forEach(field => { if (!header[field as keyof POHeaderData]) { errors.push(`T_Bidding_HEADER[${index}].${field}는 필수입니다.`); } }); }); } // 아이템 데이터 검증 if (!poData.T_Bidding_ITEM || poData.T_Bidding_ITEM.length === 0) { errors.push('T_Bidding_ITEM은 필수입니다.'); } else { poData.T_Bidding_ITEM.forEach((item, index) => { const requiredFields = ['ANFNR', 'ANFPS', 'LIFNR', 'NETPR', 'PEINH', 'BPRME', 'NETWR', 'BRTWR', 'LFDAT']; requiredFields.forEach(field => { if (!item[field as keyof POItemData]) { errors.push(`T_Bidding_ITEM[${index}].${field}는 필수입니다.`); } }); }); } // PR 반환 데이터 검증 if (!poData.T_PR_RETURN || poData.T_PR_RETURN.length === 0) { errors.push('T_PR_RETURN은 필수입니다.'); } else { poData.T_PR_RETURN.forEach((prReturn, index) => { const requiredFields = ['ANFNR', 'ANFPS', 'EBELN', 'EBELP', 'MSGTY']; requiredFields.forEach(field => { if (!prReturn[field as keyof PRReturnData]) { errors.push(`T_PR_RETURN[${index}].${field}는 필수입니다.`); } }); }); } return { isValid: errors.length === 0, errors }; } // ECC로 PO 생성 SOAP XML 전송하는 함수 async function sendPOToECC(poData: POCreateRequest): Promise { try { // 데이터 검증 const validation = validatePOData(poData); if (!validation.isValid) { return { success: false, message: `데이터 검증 실패: ${validation.errors.join(', ')}` }; } // SOAP Body Content 생성 const soapBodyContent = createPOSoapBodyContent(poData); // SOAP 전송 설정 const config: SoapSendConfig = { endpoint: ECC_PO_ENDPOINT, envelope: soapBodyContent, soapAction: 'http://sap.com/xi/WebService/soap1.1', timeout: 60000, // PO 생성은 60초 타임아웃 retryCount: 3, retryDelay: 2000, namespace: 'http://shi.samsung.co.kr/P2_MM/MMM', // ECC MM 모듈 네임스페이스 prefix: 'p1' // WSDL에서 사용하는 p1 접두사 }; // 로그 정보 const logInfo: SoapLogInfo = { direction: 'OUTBOUND', system: 'S-ERP ECC', interface: 'IF_ECC_EVCP_PO_CREATE' }; console.log(`📤 PO 생성 요청 전송 시작 - ANFNR: ${poData.T_Bidding_HEADER[0]?.ANFNR}`); console.log(`🔍 헤더 ${poData.T_Bidding_HEADER.length}개, 아이템 ${poData.T_Bidding_ITEM.length}개, PR 반환 ${poData.T_PR_RETURN.length}개`); // SOAP XML 전송 const result = await sendSoapXml(config, logInfo); if (result.success) { console.log(`✅ PO 생성 요청 전송 성공 - ANFNR: ${poData.T_Bidding_HEADER[0]?.ANFNR}`); } else { console.error(`❌ PO 생성 요청 전송 실패 - ANFNR: ${poData.T_Bidding_HEADER[0]?.ANFNR}, 오류: ${result.message}`); } return result; } catch (error) { console.error('❌ PO 생성 전송 중 오류 발생:', error); return { success: false, message: error instanceof Error ? error.message : 'Unknown error' }; } } // ======================================== // 메인 PO 생성 및 전송 함수들 // ======================================== // 단일 PO 생성 요청 처리 export async function createPurchaseOrder(poData: POCreateRequest): Promise<{ success: boolean; message: string; responseData?: string; statusCode?: number; headers?: Record; endpoint?: string; requestXml?: string; bidding_number?: string; }> { try { console.log(`🚀 PO 생성 요청 시작 - ANFNR: ${poData.T_Bidding_HEADER[0]?.ANFNR}`); const result = await sendPOToECC(poData); return { success: result.success, message: result.success ? 'PO 생성 요청이 성공적으로 전송되었습니다.' : result.message, responseData: result.responseText, statusCode: result.statusCode, headers: result.headers, endpoint: result.endpoint, requestXml: result.requestXml, bidding_number: poData.T_Bidding_HEADER[0]?.ANFNR }; } catch (error) { console.error('❌ PO 생성 요청 처리 실패:', error); return { success: false, message: error instanceof Error ? error.message : 'Unknown error' }; } } // 여러 PO 배치 생성 요청 처리 export async function createMultiplePurchaseOrders(poDataList: POCreateRequest[]): Promise<{ success: boolean; message: string; results?: Array<{ bidding_number: string; success: boolean; error?: string }>; }> { try { console.log(`🚀 배치 PO 생성 요청 시작: ${poDataList.length}개`); const results: Array<{ bidding_number: string; success: boolean; error?: string }> = []; for (const poData of poDataList) { try { const bidding_number = poData.T_Bidding_HEADER[0]?.ANFNR || 'Unknown'; console.log(`📤 PO 생성 처리 중: ${bidding_number}`); const result = await sendPOToECC(poData); if (result.success) { console.log(`✅ PO 생성 성공: ${bidding_number}`); results.push({ bidding_number, success: true }); } else { console.error(`❌ PO 생성 실패: ${bidding_number}, 오류: ${result.message}`); results.push({ bidding_number, success: false, error: result.message }); } // 배치 처리간 지연 (시스템 부하 방지) if (poDataList.length > 1) { await new Promise(resolve => setTimeout(resolve, 1000)); } } catch (error) { const bidding_number = poData.T_Bidding_HEADER[0]?.ANFNR || 'Unknown'; console.error(`❌ PO 생성 처리 실패: ${bidding_number}`, error); results.push({ bidding_number, success: false, error: error instanceof Error ? error.message : 'Unknown error' }); } } const successCount = results.filter(r => r.success).length; const failCount = results.length - successCount; console.log(`🎉 배치 PO 생성 완료: 성공 ${successCount}개, 실패 ${failCount}개`); return { success: failCount === 0, message: `배치 PO 생성 완료: 성공 ${successCount}개, 실패 ${failCount}개`, results }; } catch (error) { console.error('❌ 배치 PO 생성 중 전체 오류 발생:', error); return { success: false, message: error instanceof Error ? error.message : 'Unknown error' }; } } // 테스트용 PO 생성 함수 (샘플 데이터 포함) export async function createTestPurchaseOrder(): Promise<{ success: boolean; message: string; responseData?: string; testData?: POCreateRequest; }> { try { console.log('🧪 테스트용 PO 생성 시작'); // 테스트용 샘플 데이터 생성 const testPOData: POCreateRequest = { T_Bidding_HEADER: [{ ANFNR: 'TEST001', LIFNR: '1000000001', ZPROC_IND: 'A', ANGNR: 'TEST001', WAERS: 'KRW', ZTERM: '0001', INCO1: 'FOB', INCO2: 'Seoul, Korea', VSTEL: '001', LSTEL: '001', MWSKZ: 'V0', LANDS: 'KR', ZRCV_DT: getCurrentSAPDate(), ZATTEN_IND: 'Y', IHRAN: getCurrentSAPDate(), TEXT: 'Test PO Creation', ZDLV_CNTLR: '001', ZDLV_PRICE_T: 'N', ZDLV_PRICE_NOTE: 'Test Note' }], T_Bidding_ITEM: [{ ANFNR: 'TEST001', ANFPS: '00001', LIFNR: '1000000001', NETPR: '1000.00', PEINH: '1', BPRME: 'EA', NETWR: '1000.00', BRTWR: '1100.00', LFDAT: getCurrentSAPDate(), ZCON_NO_PO: 'CON001', EBELP: '00001' }], T_PR_RETURN: [{ ANFNR: 'TEST001', ANFPS: '00001', EBELN: 'PR001', EBELP: '00001', MSGTY: 'S', MSGTXT: 'Test message' }], EV_ERDAT: getCurrentSAPDate(), EV_ERZET: getCurrentSAPTime() }; const result = await sendPOToECC(testPOData); return { success: result.success, message: result.success ? '테스트 PO 생성이 성공적으로 전송되었습니다.' : result.message, responseData: result.responseText, testData: testPOData }; } catch (error) { console.error('❌ 테스트 PO 생성 실패:', error); return { success: false, message: error instanceof Error ? error.message : 'Unknown error' }; } } // PO 생성 요청 상태 확인 함수 (향후 확장용) export async function checkPOCreationStatus(biddingNumber: string): Promise<{ success: boolean; message: string; status?: string; }> { try { console.log(`🔍 PO 생성 상태 확인: ${biddingNumber}`); // 향후 ECC에서 상태 조회 API가 제공될 경우 구현 // 현재는 기본 응답만 반환 return { success: true, message: 'PO 생성 상태 확인 기능은 향후 구현 예정입니다.', status: 'PENDING' }; } catch (error) { console.error('❌ PO 상태 확인 실패:', error); return { success: false, message: error instanceof Error ? error.message : 'Unknown error' }; } } // 사용하지 않는 유틸리티 함수들 삭제 (linter 오류 해결)