diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/soap/ecc/send/cancel-rfq.ts | 57 | ||||
| -rw-r--r-- | lib/soap/ecc/send/create-po.ts | 30 | ||||
| -rw-r--r-- | lib/soap/ecc/send/pcr-confirm.ts | 240 | ||||
| -rw-r--r-- | lib/soap/ecc/send/rfq-info.ts | 114 | ||||
| -rw-r--r-- | lib/soap/mdg/send/vendor-master/action.ts | 152 | ||||
| -rw-r--r-- | lib/soap/sender.ts | 14 |
6 files changed, 73 insertions, 534 deletions
diff --git a/lib/soap/ecc/send/cancel-rfq.ts b/lib/soap/ecc/send/cancel-rfq.ts index b26ca38b..bcbe0c7a 100644 --- a/lib/soap/ecc/send/cancel-rfq.ts +++ b/lib/soap/ecc/send/cancel-rfq.ts @@ -21,7 +21,7 @@ export interface CancelRFQResponse { // SOAP Body Content 생성 함수 function createCancelRFQSoapBodyContent(rfqData: CancelRFQRequest): Record<string, unknown> { return { - 'ns0:MT_P2MM3016_S': { + 'p1:MT_P2MM3016_S': { // WSDL에서 사용하는 p1 접두사 적용 'T_ANFNR': rfqData.T_ANFNR } }; @@ -71,7 +71,8 @@ async function sendCancelRFQToECC(rfqData: CancelRFQRequest): Promise<SoapSendRe timeout: 30000, // RFQ 취소는 30초 타임아웃 retryCount: 3, retryDelay: 1000, - namespace: 'http://shi.samsung.co.kr/P2_MM/MMM' // ECC MM 모듈 네임스페이스 + namespace: 'http://shi.samsung.co.kr/P2_MM/MMM', // ECC MM 모듈 네임스페이스 + prefix: 'p1' // WSDL에서 사용하는 p1 접두사 }; // 로그 정보 @@ -297,54 +298,4 @@ export async function cancelTestRFQ(): Promise<{ } } -// ======================================== -// 유틸리티 함수들 -// ======================================== - -// RFQ 취소 데이터 생성 헬퍼 함수 -function createCancelRFQData(rfqNumbers: string[]): CancelRFQRequest { - return { - T_ANFNR: rfqNumbers.map(rfqNumber => ({ ANFNR: rfqNumber })) - }; -} - -// RFQ 번호 검증 함수 -function validateRFQNumber(rfqNumber: string): { isValid: boolean; error?: string } { - if (!rfqNumber || typeof rfqNumber !== 'string') { - return { isValid: false, error: 'RFQ 번호는 문자열이어야 합니다.' }; - } - - const trimmed = rfqNumber.trim(); - if (trimmed === '') { - return { isValid: false, error: 'RFQ 번호는 비어있을 수 없습니다.' }; - } - - // 기본적인 길이 검증 (10자 제한 - WSDL에서 CHAR 10으로 정의) - if (trimmed.length > 10) { - return { isValid: false, error: 'RFQ 번호는 10자를 초과할 수 없습니다.' }; - } - - return { isValid: true }; -} - -// 여러 RFQ 번호 검증 함수 -function validateRFQNumbers(rfqNumbers: string[]): { isValid: boolean; errors: string[] } { - const errors: string[] = []; - - if (!Array.isArray(rfqNumbers) || rfqNumbers.length === 0) { - errors.push('최소 1개 이상의 RFQ 번호가 필요합니다.'); - return { isValid: false, errors }; - } - - rfqNumbers.forEach((rfqNumber, index) => { - const validation = validateRFQNumber(rfqNumber); - if (!validation.isValid) { - errors.push(`RFQ[${index}]: ${validation.error}`); - } - }); - - return { - isValid: errors.length === 0, - errors - }; -}
\ No newline at end of file +// 사용하지 않는 유틸리티 함수들 삭제 (linter 오류 해결)
\ No newline at end of file diff --git a/lib/soap/ecc/send/create-po.ts b/lib/soap/ecc/send/create-po.ts index d44f091b..0d992fb3 100644 --- a/lib/soap/ecc/send/create-po.ts +++ b/lib/soap/ecc/send/create-po.ts @@ -70,7 +70,7 @@ export interface POCreateRequest { // SOAP Body Content 생성 함수 function createPOSoapBodyContent(poData: POCreateRequest): Record<string, unknown> { return { - 'ns0:MT_P2MM3015_S': { + '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, @@ -155,7 +155,8 @@ async function sendPOToECC(poData: POCreateRequest): Promise<SoapSendResult> { timeout: 60000, // PO 생성은 60초 타임아웃 retryCount: 3, retryDelay: 2000, - namespace: 'http://shi.samsung.co.kr/P2_MM/MMM' // ECC MM 모듈 네임스페이스 + namespace: 'http://shi.samsung.co.kr/P2_MM/MMM', // ECC MM 모듈 네임스페이스 + prefix: 'p1' // WSDL에서 사용하는 p1 접두사 }; // 로그 정보 @@ -392,27 +393,4 @@ export async function checkPOCreationStatus(biddingNumber: string): Promise<{ } } -// ======================================== -// 유틸리티 함수들 -// ======================================== - -// PO 데이터 생성 헬퍼 함수 -function createPOData( - headers: POHeaderData[], - items: POItemData[], - prReturns: PRReturnData[], - options?: { - extractDate?: string; - extractTime?: string; - } -): POCreateRequest { - return { - T_Bidding_HEADER: headers, - T_Bidding_ITEM: items, - T_PR_RETURN: prReturns, - EV_ERDAT: options?.extractDate || getCurrentSAPDate(), - EV_ERZET: options?.extractTime || getCurrentSAPTime() - }; -} - -// 날짜/시간 포맷 변환 함수들은 @/lib/soap/utils에서 import하여 사용 +// 사용하지 않는 유틸리티 함수들 삭제 (linter 오류 해결) diff --git a/lib/soap/ecc/send/pcr-confirm.ts b/lib/soap/ecc/send/pcr-confirm.ts index 46d1a909..72d4a574 100644 --- a/lib/soap/ecc/send/pcr-confirm.ts +++ b/lib/soap/ecc/send/pcr-confirm.ts @@ -64,7 +64,7 @@ export interface PCRConfirmResponse { // SOAP Body Content 생성 함수 function createPCRConfirmSoapBodyContent(pcrData: PCRConfirmRequest): Record<string, unknown> { return { - 'ns0:MT_P2MM3019_S': { + 'p1:MT_P2MM3019_S': { // WSDL에서 사용하는 p1 접두사 적용 'ZMM_PCR': pcrData.ZMM_PCR } }; @@ -168,7 +168,8 @@ async function sendPCRConfirmToECC(pcrData: PCRConfirmRequest): Promise<SoapSend timeout: 30000, // PCR 확인은 30초 타임아웃 retryCount: 3, retryDelay: 1000, - namespace: 'http://shi.samsung.co.kr/P2_MM/MMM' // ECC MM 모듈 네임스페이스 + namespace: 'http://shi.samsung.co.kr/P2_MM/MMM', // ECC MM 모듈 네임스페이스 + prefix: 'p1' // WSDL에서 사용하는 p1 접두사 }; // 로그 정보 @@ -447,237 +448,4 @@ export async function confirmTestPCR(): Promise<{ } } -// ======================================== -// 유틸리티 함수들 -// ======================================== - -// PCR 확인 데이터 생성 헬퍼 함수 -function createPCRConfirmData(pcrItems: Array<{ - PCR_REQ: string; - PCR_REQ_SEQ: string; - PCR_DEC_DATE: string; - EBELN: string; - EBELP: string; - PCR_STATUS: string; - WAERS?: string; - PCR_NETPR?: string; - PEINH?: string; - PCR_NETWR?: string; - REJ_CD?: string; - REJ_RSN?: string; - CONFIRM_CD?: string; - CONFIRM_RSN?: string; -}>): PCRConfirmRequest { - return { - ZMM_PCR: pcrItems - }; -} - -// PCR 요청번호 검증 함수 -function validatePCRNumber(pcrReq: string): { isValid: boolean; error?: string } { - if (!pcrReq || typeof pcrReq !== 'string') { - return { isValid: false, error: 'PCR 요청번호는 문자열이어야 합니다.' }; - } - - const trimmed = pcrReq.trim(); - if (trimmed === '') { - return { isValid: false, error: 'PCR 요청번호는 비어있을 수 없습니다.' }; - } - - // 기본적인 길이 검증 (10자 제한 - WSDL에서 CHAR 10으로 정의) - if (trimmed.length > 10) { - return { isValid: false, error: 'PCR 요청번호는 10자를 초과할 수 없습니다.' }; - } - - return { isValid: true }; -} - -// PCR 요청순번 검증 함수 -function validatePCRSequence(pcrReqSeq: string): { isValid: boolean; error?: string } { - if (!pcrReqSeq || typeof pcrReqSeq !== 'string') { - return { isValid: false, error: 'PCR 요청순번은 문자열이어야 합니다.' }; - } - - const trimmed = pcrReqSeq.trim(); - if (trimmed === '') { - return { isValid: false, error: 'PCR 요청순번은 비어있을 수 없습니다.' }; - } - - // 숫자 형식 검증 (NUMC 5자리) - if (!/^\d{1,5}$/.test(trimmed)) { - return { isValid: false, error: 'PCR 요청순번은 1-5자리 숫자여야 합니다.' }; - } - - return { isValid: true }; -} - -// PCR 결정일 검증 함수 -function validatePCRDecisionDate(pcrDecDate: string): { isValid: boolean; error?: string } { - if (!pcrDecDate || typeof pcrDecDate !== 'string') { - return { isValid: false, error: 'PCR 결정일은 문자열이어야 합니다.' }; - } - - const trimmed = pcrDecDate.trim(); - if (trimmed === '') { - return { isValid: false, error: 'PCR 결정일은 비어있을 수 없습니다.' }; - } - - // YYYYMMDD 형식 검증 - if (!/^\d{8}$/.test(trimmed)) { - return { isValid: false, error: 'PCR 결정일은 YYYYMMDD 형식이어야 합니다.' }; - } - - // 날짜 유효성 검증 - const year = parseInt(trimmed.substring(0, 4)); - const month = parseInt(trimmed.substring(4, 6)); - const day = parseInt(trimmed.substring(6, 8)); - - if (month < 1 || month > 12) { - return { isValid: false, error: 'PCR 결정일의 월이 올바르지 않습니다.' }; - } - - if (day < 1 || day > 31) { - return { isValid: false, error: 'PCR 결정일의 일이 올바르지 않습니다.' }; - } - - // 실제 날짜 검증 - const date = new Date(year, month - 1, day); - if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) { - return { isValid: false, error: 'PCR 결정일이 유효하지 않은 날짜입니다.' }; - } - - return { isValid: true }; -} - -// PCR 상태 검증 함수 -function validatePCRStatus(pcrStatus: string): { isValid: boolean; error?: string } { - if (!pcrStatus || typeof pcrStatus !== 'string') { - return { isValid: false, error: 'PCR 상태는 문자열이어야 합니다.' }; - } - - const trimmed = pcrStatus.trim(); - if (trimmed === '') { - return { isValid: false, error: 'PCR 상태는 비어있을 수 없습니다.' }; - } - - if (trimmed.length !== 1) { - return { isValid: false, error: 'PCR 상태는 1자리여야 합니다.' }; - } - - return { isValid: true }; -} - -// 구매오더 검증 함수 -function validatePurchaseOrder(ebeln: string): { isValid: boolean; error?: string } { - if (!ebeln || typeof ebeln !== 'string') { - return { isValid: false, error: '구매오더는 문자열이어야 합니다.' }; - } - - const trimmed = ebeln.trim(); - if (trimmed === '') { - return { isValid: false, error: '구매오더는 비어있을 수 없습니다.' }; - } - - if (trimmed.length > 10) { - return { isValid: false, error: '구매오더는 10자를 초과할 수 없습니다.' }; - } - - return { isValid: true }; -} - -// 구매오더 품번 검증 함수 -function validatePurchaseOrderItem(ebelp: string): { isValid: boolean; error?: string } { - if (!ebelp || typeof ebelp !== 'string') { - return { isValid: false, error: '구매오더 품번은 문자열이어야 합니다.' }; - } - - const trimmed = ebelp.trim(); - if (trimmed === '') { - return { isValid: false, error: '구매오더 품번은 비어있을 수 없습니다.' }; - } - - // 숫자 형식 검증 (NUMC 5자리) - if (!/^\d{1,5}$/.test(trimmed)) { - return { isValid: false, error: '구매오더 품번은 1-5자리 숫자여야 합니다.' }; - } - - return { isValid: true }; -} - -// 통화 검증 함수 -function validateCurrency(waers?: string): { isValid: boolean; error?: string } { - if (!waers) { - return { isValid: true }; // 선택 필드 - } - - if (typeof waers !== 'string') { - return { isValid: false, error: '통화는 문자열이어야 합니다.' }; - } - - const trimmed = waers.trim(); - if (trimmed.length > 5) { - return { isValid: false, error: '통화는 5자를 초과할 수 없습니다.' }; - } - - return { isValid: true }; -} - -// 여러 PCR 아이템 검증 함수 -function validatePCRItems(pcrItems: Array<{ - PCR_REQ: string; - PCR_REQ_SEQ: string; - PCR_DEC_DATE: string; - EBELN: string; - EBELP: string; - PCR_STATUS: string; - WAERS?: string; -}>): { isValid: boolean; errors: string[] } { - const errors: string[] = []; - - if (!Array.isArray(pcrItems) || pcrItems.length === 0) { - errors.push('최소 1개 이상의 PCR 아이템이 필요합니다.'); - return { isValid: false, errors }; - } - - pcrItems.forEach((item, index) => { - const pcrReqValid = validatePCRNumber(item.PCR_REQ); - if (!pcrReqValid.isValid) { - errors.push(`PCR[${index}] 요청번호: ${pcrReqValid.error}`); - } - - const pcrSeqValid = validatePCRSequence(item.PCR_REQ_SEQ); - if (!pcrSeqValid.isValid) { - errors.push(`PCR[${index}] 요청순번: ${pcrSeqValid.error}`); - } - - const pcrDateValid = validatePCRDecisionDate(item.PCR_DEC_DATE); - if (!pcrDateValid.isValid) { - errors.push(`PCR[${index}] 결정일: ${pcrDateValid.error}`); - } - - const eBelnValid = validatePurchaseOrder(item.EBELN); - if (!eBelnValid.isValid) { - errors.push(`PCR[${index}] 구매오더: ${eBelnValid.error}`); - } - - const eBelpValid = validatePurchaseOrderItem(item.EBELP); - if (!eBelpValid.isValid) { - errors.push(`PCR[${index}] 구매오더품번: ${eBelpValid.error}`); - } - - const statusValid = validatePCRStatus(item.PCR_STATUS); - if (!statusValid.isValid) { - errors.push(`PCR[${index}] 상태: ${statusValid.error}`); - } - - const currencyValid = validateCurrency(item.WAERS); - if (!currencyValid.isValid) { - errors.push(`PCR[${index}] 통화: ${currencyValid.error}`); - } - }); - - return { - isValid: errors.length === 0, - errors - }; -} +// 사용하지 않는 유틸리티 함수들 삭제 (linter 오류 해결) diff --git a/lib/soap/ecc/send/rfq-info.ts b/lib/soap/ecc/send/rfq-info.ts index d313a74b..777c1dfe 100644 --- a/lib/soap/ecc/send/rfq-info.ts +++ b/lib/soap/ecc/send/rfq-info.ts @@ -62,7 +62,7 @@ export interface RFQInfoResponse { // SOAP Body Content 생성 함수 function createRFQSoapBodyContent(rfqData: RFQInfoRequest): Record<string, unknown> { return { - 'ns0:MT_P2MM3014_S': { + 'p1:MT_P2MM3014_S': { // WSDL에서 사용하는 p1 접두사 적용 'T_RFQ_HEADER': rfqData.T_RFQ_HEADER, 'T_RFQ_ITEM': rfqData.T_RFQ_ITEM } @@ -146,7 +146,8 @@ async function sendRFQToECC(rfqData: RFQInfoRequest): Promise<SoapSendResult> { timeout: 60000, // RFQ 정보 전송은 60초 타임아웃 retryCount: 3, retryDelay: 2000, - namespace: 'http://shi.samsung.co.kr/P2_MM/MMM' // ECC MM 모듈 네임스페이스 + namespace: 'http://shi.samsung.co.kr/P2_MM/MMM', // ECC MM 모듈 네임스페이스 + prefix: 'p1' // WSDL에서 사용하는 p1 접두사 }; // 로그 정보 @@ -359,112 +360,3 @@ export async function checkRFQInformationStatus(rfqNumber: string): Promise<{ } } -// ======================================== -// 유틸리티 함수들 -// ======================================== - -// RFQ 데이터 생성 헬퍼 함수 -function createRFQData( - headers: RFQHeaderData[], - items: RFQItemData[] -): RFQInfoRequest { - return { - T_RFQ_HEADER: headers, - T_RFQ_ITEM: items - }; -} - -// RFQ 헤더 데이터 생성 헬퍼 함수 -function createRFQHeader( - rfqNumber: string, // ANFNR - CHAR(10) - vendorNumber: string, // LIFNR - CHAR(10) - currency: string = 'KRW', // WAERS - CUKY(5) - paymentTerms: string = '0001', // ZTERM - CHAR(4) - incoterms1: string = 'FOB', // INCO1 - CHAR(3) - incoterms2: string = 'Seoul, Korea', // INCO2 - CHAR(28) - taxCode: string = 'V0', // MWSKZ - CHAR(2) - countryForTax: string = 'KR', // LANDS - CHAR(3) (Country for Tax Return) - options?: { - shippingPlace?: string; // VSTEL - CHAR(3) (Place of Shipping) - destinationPlace?: string; // LSTEL - CHAR(3) (Place of Destination) - } -): RFQHeaderData { - return { - ANFNR: rfqNumber, - LIFNR: vendorNumber, - WAERS: currency, - ZTERM: paymentTerms, - INCO1: incoterms1, - INCO2: incoterms2, - MWSKZ: taxCode, - LANDS: countryForTax, - VSTEL: options?.shippingPlace, - LSTEL: options?.destinationPlace - }; -} - -// RFQ 아이템 데이터 생성 헬퍼 함수 -function createRFQItem( - rfqNumber: string, // ANFNR - CHAR(10) - itemNumber: string, // ANFPS - NUMC(5,0) - netPrice: string, // NETPR - CURR(11,2) Net Price in Purchasing Document (in Document Currency) - netOrderValue: string, // NETWR - CURR(13,2) Net Order Value in PO Currency - grossOrderValue: string, // BRTWR - CURR(13,2) Gross order value in PO currency - deliveryDate?: string // LFDAT - DATS(8) Item delivery date (선택) -): RFQItemData { - return { - ANFNR: rfqNumber, - ANFPS: itemNumber, - NETPR: netPrice, - NETWR: netOrderValue, - BRTWR: grossOrderValue, - LFDAT: deliveryDate - }; -} - -// RFQ 번호 기준으로 헤더와 아이템 매칭 검증 -function validateRFQMatching(headers: RFQHeaderData[], items: RFQItemData[]): { - isValid: boolean; - errors: string[]; - orphanItems: string[]; -} { - const errors: string[] = []; - const orphanItems: string[] = []; - - const headerRFQNumbers = new Set(headers.map(h => h.ANFNR)); - - for (const item of items) { - if (!headerRFQNumbers.has(item.ANFNR)) { - orphanItems.push(item.ANFNR); - errors.push(`RFQ 아이템 '${item.ANFNR}-${item.ANFPS}'에 해당하는 헤더가 없습니다.`); - } - } - - return { - isValid: errors.length === 0, - errors, - orphanItems - }; -} - -// RFQ 데이터 요약 정보 생성 -function getRFQDataSummary(rfqData: RFQInfoRequest): { - rfqNumbers: string[]; - totalHeaders: number; - totalItems: number; - itemsPerRFQ: Record<string, number>; -} { - const rfqNumbers = [...new Set(rfqData.T_RFQ_HEADER.map(h => h.ANFNR))]; - const itemsPerRFQ: Record<string, number> = {}; - - for (const rfqNumber of rfqNumbers) { - itemsPerRFQ[rfqNumber] = rfqData.T_RFQ_ITEM.filter(i => i.ANFNR === rfqNumber).length; - } - - return { - rfqNumbers, - totalHeaders: rfqData.T_RFQ_HEADER.length, - totalItems: rfqData.T_RFQ_ITEM.length, - itemsPerRFQ - }; -} diff --git a/lib/soap/mdg/send/vendor-master/action.ts b/lib/soap/mdg/send/vendor-master/action.ts index 8bb2f633..d95f630c 100644 --- a/lib/soap/mdg/send/vendor-master/action.ts +++ b/lib/soap/mdg/send/vendor-master/action.ts @@ -17,127 +17,75 @@ import { VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_PORG_ZVPFN } from "@/db/schema/MDG/mdg"; import { eq, sql, desc } from "drizzle-orm"; -import { withSoapLogging } from "../../../utils"; -import { XMLBuilder } from 'fast-xml-parser'; import { CSV_FIELDS } from './csv-fields'; - -// 환경변수에서 인증 정보 가져오기 -const MDG_SOAP_USERNAME = process.env.MDG_SOAP_USERNAME; -const MDG_SOAP_PASSWORD = process.env.MDG_SOAP_PASSWORD; +import { sendSoapXml, type SoapSendConfig, type SoapLogInfo } from "@/lib/soap/sender"; // SAP XI 엔드포인트 URL const MDG_ENDPOINT_URL = "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_MD%2FMDZ%5EP2MD3007_AO&QualityOfService=ExactlyOnce"; // CSV 필드 정의는 ./csv-fields.ts에서 import -// SAP XI 호환 XML 생성 함수 (수정된 버전) -function generateSAPXICompatibleXML(supplierMaster: Record<string, string>): string { - // XML 선언을 별도로 처리 - const xmlDeclaration = '<?xml version="1.0" encoding="UTF-8"?>\n'; - - // SOAP Envelope 구조 정의 - const soapEnvelope = { - 'soap:Envelope': { - '@_xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/', - '@_xmlns:ns0': 'http://shi.samsung.co.kr/P2_MD/MDZ', - 'soap:Body': { - 'ns0:MT_P2MD3007_S': { - 'SUPPLIER_MASTER': supplierMaster - } - } +// SOAP Body Content 생성 함수 (WSDL 요구사항에 맞게 수정) +function createVendorMasterSoapBodyContent(supplierMaster: Record<string, string>): Record<string, unknown> { + return { + 'p1:MT_P2MD3007_S': { // WSDL에서 사용하는 p1 접두사 적용 + 'SUPPLIER_MASTER': supplierMaster } }; - - const builder = new XMLBuilder({ - ignoreAttributes: false, - format: true, - attributeNamePrefix: '@_', - textNodeName: '#text', - suppressEmptyNode: true, // 빈 노드는 self-closing 태그로 처리 - suppressUnpairedNode: false, - indentBy: ' ', // 2칸 들여쓰기 - processEntities: false, // 엔티티 변환 방지 - suppressBooleanAttributes: false, - cdataPropName: false, - tagValueProcessor: (name, val) => val, // 값 처리기 - attributeValueProcessor: (name, val) => val // 속성 처리기 - }); - - const xmlBody = builder.build(soapEnvelope); - - // XML 선언과 Body 결합 - const completeXML = xmlDeclaration + xmlBody; - - console.log('🔍 생성된 XML (전체):', completeXML); - - return completeXML; } -// XML을 MDG로 전송하는 함수 (성공했던 구조 사용) -async function sendXMLToMDG(xmlData: string): Promise<{ +// MDG로 VENDOR 마스터 SOAP XML 전송하는 함수 (sender.ts 사용) +async function sendVendorMasterToMDGInternal(supplierMaster: Record<string, string>): Promise<{ success: boolean; message: string; responseText?: string; }> { try { - const responseText = await withSoapLogging( - 'OUTBOUND', - 'S-ERP MDG', - 'IF_MDZ_EVCP_VENDOR_MASTER', - xmlData, - async () => { - // 성공했던 전송 방식 그대로 사용 - const headers: Record<string, string> = { - 'Content-Type': 'text/xml; charset=utf-8', - 'SOAPAction': 'http://sap.com/xi/WebService/soap1.1', - }; - - // Basic Authentication 헤더 추가 - if (MDG_SOAP_USERNAME && MDG_SOAP_PASSWORD) { - const credentials = Buffer.from(`${MDG_SOAP_USERNAME}:${MDG_SOAP_PASSWORD}`).toString('base64'); - headers['Authorization'] = `Basic ${credentials}`; - console.log('🔐 Basic Authentication 헤더 추가 완료'); - } else { - console.warn('⚠️ MDG SOAP 인증 정보가 환경변수에 설정되지 않았습니다.'); - } - - console.log('📤 MDG 전송 시작'); - console.log('🔍 전송 XML (첫 500자):', xmlData.substring(0, 500)); - - const res = await fetch(MDG_ENDPOINT_URL, { - method: 'POST', - headers, - body: xmlData, - }); - - const text = await res.text(); - - console.log('📥 MDG 응답 수신:', res.status, res.statusText); - console.log('🔍 응답 XML (첫 500자):', text.substring(0, 500)); + // SOAP Body Content 생성 + const soapBodyContent = createVendorMasterSoapBodyContent(supplierMaster); + + // SOAP 전송 설정 + const config: SoapSendConfig = { + endpoint: MDG_ENDPOINT_URL, + envelope: soapBodyContent, + soapAction: 'http://sap.com/xi/WebService/soap1.1', + timeout: 60000, // VENDOR 마스터 전송은 60초 타임아웃 + retryCount: 3, + retryDelay: 2000, + namespace: 'http://shi.samsung.co.kr/P2_MD/MDZ', // MDG 전용 네임스페이스 + prefix: 'p1' // WSDL에서 사용하는 p1 접두사 + }; - if (!res.ok) { - throw new Error(`HTTP ${res.status}: ${res.statusText}`); - } + // 로그 정보 + const logInfo: SoapLogInfo = { + direction: 'OUTBOUND', + system: 'S-ERP MDG', + interface: 'IF_MDZ_EVCP_VENDOR_MASTER' + }; - // SOAP Fault 검사 - if (text.includes('soap:Fault') || text.includes('SOAP:Fault')) { - throw new Error(`MDG SOAP Fault: ${text}`); - } + console.log(`📤 VENDOR 마스터 전송 시작`); + console.log(`🔍 SUPPLIER_MASTER 데이터: ${Object.keys(supplierMaster).length}개 필드`); - return text; - } - ); + // SOAP XML 전송 + const result = await sendSoapXml(config, logInfo); + + if (result.success) { + console.log(`✅ VENDOR 마스터 전송 성공`); + } else { + console.error(`❌ VENDOR 마스터 전송 실패, 오류: ${result.message}`); + } return { - success: true, - message: '전송 성공', - responseText, + success: result.success, + message: result.success ? '전송 성공' : result.message, + responseText: result.responseText }; + } catch (error) { - console.error('❌ XML 전송 실패:', error); + console.error('❌ VENDOR 마스터 전송 중 오류 발생:', error); return { success: false, - message: error instanceof Error ? error.message : 'Unknown error', + message: error instanceof Error ? error.message : 'Unknown error' }; } } @@ -280,8 +228,7 @@ export async function sendVendorMasterToMDG(vendorCodes: string[]): Promise<{ const supplierMaster = buildSupplierMasterData(vendorData); console.log(`📄 VENDOR ${vendorCode} 데이터 생성 완료`); - const generatedXML = generateSAPXICompatibleXML(supplierMaster); - const result = await sendXMLToMDG(generatedXML); + const result = await sendVendorMasterToMDGInternal(supplierMaster); if (result.success) { console.log(`✅ VENDOR ${vendorCode} MDG 전송 성공`); @@ -395,17 +342,14 @@ export async function sendTestVendorDataToMDG(formData: Record<string, string>): supplierMaster[f.field] = formData[f.field] ?? ''; }); - const generatedXML = generateSAPXICompatibleXML(supplierMaster); - - console.log('📄 SAP XI 호환 XML 생성 완료'); + console.log('📄 SUPPLIER_MASTER 데이터 생성 완료'); - const result = await sendXMLToMDG(generatedXML); + const result = await sendVendorMasterToMDGInternal(supplierMaster); return { success: result.success, message: result.success ? '테스트 송신이 완료되었습니다.' : result.message, - responseData: result.responseText, - generatedXML + responseData: result.responseText }; } catch (error) { diff --git a/lib/soap/sender.ts b/lib/soap/sender.ts index 5a61462e..3728429b 100644 --- a/lib/soap/sender.ts +++ b/lib/soap/sender.ts @@ -18,6 +18,7 @@ export interface SoapSendConfig { retryCount?: number; retryDelay?: number; namespace?: string; // 네임스페이스를 동적으로 설정할 수 있도록 추가 + prefix: string; // 네임스페이스 접두사 (필수) } // 로깅 정보 타입 @@ -65,12 +66,13 @@ function createXmlBuilder() { // SOAP Envelope 생성 function createSoapEnvelope( namespace: string, - bodyContent: Record<string, unknown> + bodyContent: Record<string, unknown>, + prefix: string ): Record<string, unknown> { return { 'soap:Envelope': { '@_xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/', - '@_xmlns:ns0': namespace, + [`@_xmlns:${prefix}`]: namespace, // 동적 접두사 생성 'soap:Body': bodyContent } }; @@ -96,11 +98,12 @@ export async function sendSoapXml( // 인증 정보 설정 (기본값 사용) const authConfig = auth || getDefaultAuth(); - // XML 생성 (네임스페이스를 동적으로 설정) + // XML 생성 (네임스페이스와 접두사를 동적으로 설정) const namespace = config.namespace || 'http://shi.samsung.co.kr/P2_MD/MDZ'; const soapEnvelope = createSoapEnvelope( namespace, - config.envelope + config.envelope, + config.prefix ); const xmlData = await generateSoapXml(soapEnvelope); @@ -255,6 +258,7 @@ export async function sendSimpleSoapXml( logInfo: SoapLogInfo, options?: { namespace?: string; + prefix?: string; soapAction?: string; auth?: SoapAuthConfig; timeout?: number; @@ -266,6 +270,7 @@ export async function sendSimpleSoapXml( soapAction: options?.soapAction, timeout: options?.timeout || 30000, // 기본 30초 namespace: options?.namespace, // 네임스페이스 옵션 추가 + prefix: options?.prefix || 'p1', // 기본 접두사 p1 }; const auth = options?.auth || getDefaultAuth(); @@ -285,6 +290,7 @@ export async function sendMdgSoapXml( soapAction: 'http://sap.com/xi/WebService/soap1.1', timeout: 60000, // MDG는 60초 타임아웃 namespace: 'http://shi.samsung.co.kr/P2_MD/MDZ', // MDG 전용 네임스페이스 명시 + prefix: 'p1', // MDG 전용 접두사 }; return await sendSoapXml(config, logInfo, auth); |
