diff options
Diffstat (limited to 'lib/soap/mdg/send')
| -rw-r--r-- | lib/soap/mdg/send/vendor-master/action.ts | 541 |
1 files changed, 242 insertions, 299 deletions
diff --git a/lib/soap/mdg/send/vendor-master/action.ts b/lib/soap/mdg/send/vendor-master/action.ts index b353eab0..24112316 100644 --- a/lib/soap/mdg/send/vendor-master/action.ts +++ b/lib/soap/mdg/send/vendor-master/action.ts @@ -18,19 +18,18 @@ import { } from "@/db/schema/MDG/mdg"; import { eq, sql, desc } from "drizzle-orm"; import { withSoapLogging } from "../../utils"; -import * as soap from 'soap'; import fs from 'fs'; import path from 'path'; - -// WSDL 파일 경로 -const WSDL_PATH = path.join(process.cwd(), 'public', 'wsdl', 'P2MD3007_AO.wsdl'); +import { XMLBuilder } from 'fast-xml-parser'; // 환경변수에서 인증 정보 가져오기 const MDG_SOAP_USERNAME = process.env.MDG_SOAP_USERNAME; const MDG_SOAP_PASSWORD = process.env.MDG_SOAP_PASSWORD; -const MDG_SOAP_AUTH_TYPE = process.env.MDG_SOAP_AUTH_TYPE || 'basic'; // 'basic' | 'wssecurity' -// CSV 파싱 및 필드 정의 ------------------------------------------------ +// 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 파싱 및 필드 정의 interface CsvField { table: string; field: string; @@ -59,151 +58,114 @@ try { console.error('CSV 로딩 실패:', e); } -// SOAP 클라이언트 생성 및 인증 설정 함수 -async function createSoapClient(): Promise<soap.Client> { - return new Promise((resolve, reject) => { - soap.createClient(WSDL_PATH, (err, client) => { - if (err) { - reject(err); - return; - } - - if (!client) { - reject(new Error('SOAP 클라이언트 생성 실패')); - return; - } - - // 인증 설정 - if (MDG_SOAP_USERNAME && MDG_SOAP_PASSWORD) { - try { - if (MDG_SOAP_AUTH_TYPE === 'wssecurity') { - // WS-Security 인증 설정 - const wsSecurity = new soap.WSSecurity(MDG_SOAP_USERNAME, MDG_SOAP_PASSWORD); - client.setSecurity(wsSecurity); - console.log('🔐 WS-Security 인증 설정 완료'); - } else { - // Basic Authentication 설정 (기본값) - const basicAuth = new soap.BasicAuthSecurity(MDG_SOAP_USERNAME, MDG_SOAP_PASSWORD); - client.setSecurity(basicAuth); - console.log('🔐 Basic Authentication 설정 완료'); - } - } catch (authError) { - console.warn('⚠️ 인증 설정 중 오류:', authError); +// 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 } - } else { - console.warn('⚠️ MDG SOAP 인증 정보가 환경변수에 설정되지 않았습니다.'); } + } + }; - resolve(client); - }); + 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; } -// SOAP 응답 타입 정의 -interface SoapResponse { - [key: string]: unknown; -} - -// SOAP 오류 타입 정의 -interface SoapError { - message: string; - body?: string; - statusCode?: number; -} - -// VENDOR 마스터 데이터를 MDG로 송신하는 액션 -export async function sendVendorMasterToMDG(vendorCodes: string[]): Promise<{ +// XML을 MDG로 전송하는 함수 (성공했던 구조 사용) +async function sendXMLToMDG(xmlData: string): Promise<{ success: boolean; message: string; - results?: Array<{ vendorCode: string; success: boolean; error?: string }>; + responseText?: string; }> { try { - console.log(`🚀 VENDOR_MASTER 송신 시작: ${vendorCodes.length}개 벤더`); - - const results: Array<{ vendorCode: string; success: boolean; error?: string }> = []; - - // 각 VENDOR 코드별로 개별 전송 (MDG 시스템의 처리 제한 고려) - for (const vendorCode of vendorCodes) { - try { - console.log(`📤 VENDOR ${vendorCode} 데이터 조회 중...`); - - // 데이터베이스에서 VENDOR 데이터 조회 - const vendorData = await fetchVendorData(vendorCode); - - if (!vendorData) { - results.push({ - vendorCode, - success: false, - error: 'VENDOR 데이터를 찾을 수 없습니다.' - }); - continue; - } - - // SOAP 요청 데이터 생성 - const soapData = buildSoapData(vendorData); - console.log(`📄 VENDOR ${vendorCode} SOAP 데이터 생성 완료`); - - // 데이터 구조 검증 (디버깅용) - const validation = validateSoapDataStructure(soapData); - if (!validation.isValid) { - console.warn(`⚠️ VENDOR ${vendorCode} 데이터 구조 문제:`, validation.issues); + 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.log(`✅ VENDOR ${vendorCode} 데이터 구조 검증 통과`); + console.warn('⚠️ MDG SOAP 인증 정보가 환경변수에 설정되지 않았습니다.'); } - // SOAP 클라이언트로 요청 전송 - await withSoapLogging( - 'OUTBOUND', - 'S-ERP MDG', - 'IF_MDZ_EVCP_VENDOR_MASTER', - JSON.stringify(soapData), - async () => { - const client = await createSoapClient(); - - return new Promise<SoapResponse>((resolve, reject) => { - client.P2MD3007_AO(soapData, (err: SoapError | null, result: SoapResponse) => { - if (err) { - reject(err); - } else { - console.log(`✅ VENDOR ${vendorCode} MDG 전송 성공`); - resolve(result); - } - }); - }); - } - ); + console.log('📤 MDG 전송 시작'); + console.log('🔍 전송 XML (첫 500자):', xmlData.substring(0, 500)); - results.push({ - vendorCode, - success: true + const res = await fetch(MDG_ENDPOINT_URL, { + method: 'POST', + headers, + body: xmlData, }); + + const text = await res.text(); - } catch (error) { - console.error(`❌ VENDOR ${vendorCode} 전송 실패:`, error); - results.push({ - vendorCode, - success: false, - error: error instanceof Error ? error.message : 'Unknown error' - }); + console.log('📥 MDG 응답 수신:', res.status, res.statusText); + console.log('🔍 응답 XML (첫 500자):', text.substring(0, 500)); + + if (!res.ok) { + throw new Error(`HTTP ${res.status}: ${res.statusText}`); + } + + // SOAP Fault 검사 + if (text.includes('soap:Fault') || text.includes('SOAP:Fault')) { + throw new Error(`MDG SOAP Fault: ${text}`); + } + + return text; } - } - - const successCount = results.filter(r => r.success).length; - const failCount = results.length - successCount; - - console.log(`🎉 VENDOR_MASTER 송신 완료: 성공 ${successCount}개, 실패 ${failCount}개`); - + ); + return { - success: failCount === 0, - message: `전송 완료: 성공 ${successCount}개, 실패 ${failCount}개`, - results + success: true, + message: '전송 성공', + responseText, }; - } catch (error) { - console.error('❌ VENDOR_MASTER 송신 중 전체 오류 발생:', error); + console.error('❌ XML 전송 실패:', error); return { success: false, - message: error instanceof Error ? error.message : 'Unknown error' + message: error instanceof Error ? error.message : 'Unknown error', }; } } @@ -211,7 +173,6 @@ export async function sendVendorMasterToMDG(vendorCodes: string[]): Promise<{ // 데이터베이스에서 VENDOR 데이터 조회 async function fetchVendorData(vendorCode: string) { try { - // 1. 헤더 데이터 조회 const [vendorHeader] = await db .select() .from(VENDOR_MASTER_BP_HEADER) @@ -222,7 +183,6 @@ async function fetchVendorData(vendorCode: string) { return null; } - // 2. 관련 데이터 병렬 조회 const [ addresses, adEmails, @@ -273,22 +233,19 @@ async function fetchVendorData(vendorCode: string) { } } -// SOAP 데이터 생성 (WSDL 구조에 맞춤) -function buildSoapData(vendorData: NonNullable<Awaited<ReturnType<typeof fetchVendorData>>>) { +// SUPPLIER_MASTER 데이터 생성 +function buildSupplierMasterData(vendorData: NonNullable<Awaited<ReturnType<typeof fetchVendorData>>>) { const { vendorHeader, addresses, adFaxes, adPostals, adTels, bpTaxnums, bpVengens } = vendorData; - // 값 추출 매핑 ------------------------------------ const mapping: Record<string, string | undefined> = { - // Header BP_HEADER: vendorHeader?.VNDRCD, ZZSRMCD: 'EVCP', - TITLE: '', // vendorHeader에 TITLE 필드가 없음 + TITLE: '', BU_SORT1: adPostals[0]?.VNDRNM_ABRV_1 ?? undefined, NAME_ORG1: adPostals[0]?.VNDRNM_1 ?? undefined, KTOKK: bpVengens[0]?.ACNT_GRP ?? undefined, MASTERFLAG: 'X', IBND_TYPE: 'U', - // Address mandatory (first) ADDRNO: addresses[0]?.ADDRNO, AD_NATION: adPostals[0]?.INTL_ADR_VER_ID ?? undefined, COUNTRY: adPostals[0]?.NTN_CD ?? undefined, @@ -296,17 +253,13 @@ function buildSoapData(vendorData: NonNullable<Awaited<ReturnType<typeof fetchVe POST_COD1: adPostals[0]?.CITY_ZIP_NO ?? undefined, CITY1: adPostals[0]?.VNDRNM_1 ?? undefined, MC_STREET: adPostals[0]?.ADR_1 ?? undefined, - // Phone/Fax mandatory fields AD_CONSNO: '001', T_COUNTRY: adTels[0]?.NTN_CD ?? 'KR', F_COUNTRY: adFaxes[0]?.NTN_CD ?? 'KR', - // Tax BP_TX_TYP: bpTaxnums[0]?.TX_NO_CTG ?? 'KR2', TAXNUM: bpVengens[0]?.VAT_REG_NO ?? undefined, - // Default others can be added as needed }; - // 필드 순서에 따라 데이터 생성 const seen = new Set<string>(); const uniqueFields = CSV_FIELDS.filter(f => { if (seen.has(f.field)) return false; @@ -319,22 +272,158 @@ function buildSoapData(vendorData: NonNullable<Awaited<ReturnType<typeof fetchVe supplierMaster[f.field] = mapping[f.field] ?? ''; }); - // SOAP 요청 구조 생성 (P2MD3007_S 중복 제거) - // SOAP 클라이언트가 MT_P2MD3007_S로 래핑하므로, 여기서는 SUPPLIER_MASTER만 반환 - return { - SUPPLIER_MASTER: supplierMaster - }; + return supplierMaster; +} + +// ======================================== +// 메인 송신 함수들 +// ======================================== + +// VENDOR 마스터 데이터를 MDG로 송신하는 액션 +export async function sendVendorMasterToMDG(vendorCodes: string[]): Promise<{ + success: boolean; + message: string; + results?: Array<{ vendorCode: string; success: boolean; error?: string }>; +}> { + try { + console.log(`🚀 VENDOR_MASTER 송신 시작: ${vendorCodes.length}개 벤더`); + + const results: Array<{ vendorCode: string; success: boolean; error?: string }> = []; + + for (const vendorCode of vendorCodes) { + try { + console.log(`📤 VENDOR ${vendorCode} 데이터 조회 중...`); + + const vendorData = await fetchVendorData(vendorCode); + + if (!vendorData) { + results.push({ + vendorCode, + success: false, + error: 'VENDOR 데이터를 찾을 수 없습니다.' + }); + continue; + } + + const supplierMaster = buildSupplierMasterData(vendorData); + console.log(`📄 VENDOR ${vendorCode} 데이터 생성 완료`); + + const generatedXML = generateSAPXICompatibleXML(supplierMaster); + const result = await sendXMLToMDG(generatedXML); + + if (result.success) { + console.log(`✅ VENDOR ${vendorCode} MDG 전송 성공`); + results.push({ + vendorCode, + success: true + }); + } else { + console.error(`❌ VENDOR ${vendorCode} 전송 실패: ${result.message}`); + results.push({ + vendorCode, + success: false, + error: result.message + }); + } + + } catch (error) { + console.error(`❌ VENDOR ${vendorCode} 전송 실패:`, error); + results.push({ + vendorCode, + 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(`🎉 VENDOR_MASTER 송신 완료: 성공 ${successCount}개, 실패 ${failCount}개`); + + return { + success: failCount === 0, + message: `전송 완료: 성공 ${successCount}개, 실패 ${failCount}개`, + results + }; + + } catch (error) { + console.error('❌ VENDOR_MASTER 송신 중 전체 오류 발생:', error); + return { + success: false, + message: error instanceof Error ? error.message : 'Unknown error' + }; + } +} + +// 테스트용 폼 데이터 송신 함수 +export async function sendTestVendorDataToMDG(formData: Record<string, string>): Promise<{ + success: boolean; + message: string; + responseData?: unknown; + generatedXML?: string; +}> { + try { + console.log('🚀 테스트용 VENDOR 데이터 송신 시작'); + + let csvFields: CsvField[] = []; + try { + const csvRaw = fs.readFileSync(CSV_PATH, 'utf-8'); + csvFields = parseCsv(csvRaw); + } catch (e) { + console.error('CSV 로딩 실패:', e); + return { + success: false, + message: 'CSV 필드 정의 파일을 로드할 수 없습니다.' + }; + } + + const seen = new Set<string>(); + const uniqueFields = csvFields.filter(f => { + if (seen.has(f.field)) return false; + seen.add(f.field); + return true; + }); + + const supplierMaster: Record<string, string> = {}; + uniqueFields.forEach(f => { + supplierMaster[f.field] = formData[f.field] ?? ''; + }); + + const generatedXML = generateSAPXICompatibleXML(supplierMaster); + + console.log('📄 SAP XI 호환 XML 생성 완료'); + + const result = await sendXMLToMDG(generatedXML); + + return { + success: result.success, + message: result.success ? '테스트 송신이 완료되었습니다.' : result.message, + responseData: result.responseText, + generatedXML + }; + + } catch (error) { + console.error('❌ 테스트 송신 실패:', error); + return { + success: false, + message: error instanceof Error ? error.message : 'Unknown error' + }; + } } -// 특정 VENDOR만 송신하는 유틸리티 함수 +// ======================================== +// 유틸리티 함수들 +// ======================================== + +// 특정 VENDOR만 송신 export async function sendSingleVendorToMDG(vendorCode: string) { return await sendVendorMasterToMDG([vendorCode]); } -// 모든 VENDOR 송신하는 유틸리티 함수 (주의: 대량 데이터 처리) +// 모든 VENDOR 송신 (주의: 대량 데이터 처리) export async function sendAllVendorsToMDG() { try { - // 모든 VENDOR 코드 조회 const vendors = await db .select({ VNDRCD: VENDOR_MASTER_BP_HEADER.VNDRCD }) .from(VENDOR_MASTER_BP_HEADER); @@ -350,7 +439,6 @@ export async function sendAllVendorsToMDG() { console.log(`⚠️ 전체 VENDOR 송신 요청: ${vendorCodes.length}개`); - // 배치 처리 (10개씩 분할하여 처리) const batchSize = 10; const results: Array<{ vendorCode: string; success: boolean; error?: string }> = []; @@ -363,7 +451,6 @@ export async function sendAllVendorsToMDG() { results.push(...batchResult.results); } - // 배치 간 잠깐 대기 (서버 부하 방지) if (i + batchSize < vendorCodes.length) { await new Promise(resolve => setTimeout(resolve, 1000)); } @@ -387,7 +474,7 @@ export async function sendAllVendorsToMDG() { } } -// 수정된 VENDOR만 송신하는 액션 (updatedAt != createdAt 조건) +// 수정된 VENDOR만 송신 export async function sendModifiedVendorsToMDG(): Promise<{ success: boolean; message: string; @@ -396,7 +483,6 @@ export async function sendModifiedVendorsToMDG(): Promise<{ try { console.log('🔍 수정된 VENDOR 데이터 조회 중...'); - // updatedAt과 createdAt이 다른 VENDOR 조회 (수정된 것들) const modifiedVendors = await db .select({ VNDRCD: VENDOR_MASTER_BP_HEADER.VNDRCD, @@ -405,7 +491,6 @@ export async function sendModifiedVendorsToMDG(): Promise<{ }) .from(VENDOR_MASTER_BP_HEADER) .where( - // PostgreSQL에서 timestamp 비교 (밀리초 차이 고려) sql`EXTRACT(EPOCH FROM ${VENDOR_MASTER_BP_HEADER.updatedAt}) - EXTRACT(EPOCH FROM ${VENDOR_MASTER_BP_HEADER.createdAt}) > 1` ); @@ -421,12 +506,6 @@ export async function sendModifiedVendorsToMDG(): Promise<{ console.log(`📋 수정된 VENDOR ${vendorCodes.length}개 발견:`, vendorCodes); - // 수정된 VENDOR들의 수정 시간 로그 - modifiedVendors.forEach(vendor => { - console.log(` - ${vendor.VNDRCD}: 생성 ${vendor.createdAt?.toISOString()}, 수정 ${vendor.updatedAt?.toISOString()}`); - }); - - // 배치 처리로 송신 const batchSize = 10; const results: Array<{ vendorCode: string; success: boolean; error?: string }> = []; @@ -439,7 +518,6 @@ export async function sendModifiedVendorsToMDG(): Promise<{ results.push(...batchResult.results); } - // 배치 간 대기 if (i + batchSize < vendorCodes.length) { await new Promise(resolve => setTimeout(resolve, 1000)); } @@ -465,7 +543,7 @@ export async function sendModifiedVendorsToMDG(): Promise<{ } } -// 테스트용 N건 송신 액션 +// 테스트용 N건 송신 export async function sendNVendorsToMDG( count: number, startFrom: number = 0 @@ -484,7 +562,6 @@ export async function sendNVendorsToMDG( console.log(`🧪 테스트용 VENDOR 송신: ${count}건 (${startFrom}번째부터)`); - // N건의 VENDOR 코드 조회 const vendors = await db .select({ VNDRCD: VENDOR_MASTER_BP_HEADER.VNDRCD }) .from(VENDOR_MASTER_BP_HEADER) @@ -502,7 +579,6 @@ export async function sendNVendorsToMDG( console.log(`📋 테스트 대상 VENDOR ${vendorCodes.length}개:`, vendorCodes); - // 송신 실행 const result = await sendVendorMasterToMDG(vendorCodes); console.log(`🧪 테스트 송신 완료: ${vendorCodes.length}개 처리`); @@ -521,7 +597,7 @@ export async function sendNVendorsToMDG( } } -// 최신 수정된 N건 송신 액션 (최근 수정 순) +// 최신 수정된 N건 송신 export async function sendRecentModifiedVendorsToMDG( count: number = 5 ): Promise<{ @@ -532,7 +608,6 @@ export async function sendRecentModifiedVendorsToMDG( try { console.log(`🕒 최근 수정된 VENDOR ${count}건 조회 중...`); - // 최근 수정된 순으로 N건 조회 const recentVendors = await db .select({ VNDRCD: VENDOR_MASTER_BP_HEADER.VNDRCD, @@ -540,7 +615,6 @@ export async function sendRecentModifiedVendorsToMDG( }) .from(VENDOR_MASTER_BP_HEADER) .where( - // 수정된 항목만 (updatedAt != createdAt) sql`EXTRACT(EPOCH FROM ${VENDOR_MASTER_BP_HEADER.updatedAt}) - EXTRACT(EPOCH FROM ${VENDOR_MASTER_BP_HEADER.createdAt}) > 1` ) .orderBy(desc(VENDOR_MASTER_BP_HEADER.updatedAt)) @@ -558,7 +632,6 @@ export async function sendRecentModifiedVendorsToMDG( console.log(`📋 최근 수정된 VENDOR ${vendorCodes.length}개:`, recentVendors.map(v => `${v.VNDRCD}(${v.updatedAt?.toISOString()})`)); - // 송신 실행 const result = await sendVendorMasterToMDG(vendorCodes); console.log(`🕒 최근 수정 데이터 송신 완료`); @@ -577,35 +650,7 @@ export async function sendRecentModifiedVendorsToMDG( } } -// SOAP 데이터 구조 검증 유틸리티 함수 -export function validateSoapDataStructure(soapData: Record<string, unknown>): { - isValid: boolean; - structure: string; - issues: string[]; -} { - const issues: string[] = []; - - // 올바른 구조인지 확인 - if (!soapData.SUPPLIER_MASTER) { - issues.push('SUPPLIER_MASTER 필드가 없습니다.'); - } - - // P2MD3007_S 중복 확인 (이제는 없어야 함) - if (soapData.P2MD3007_S) { - issues.push('P2MD3007_S 래핑이 발견되었습니다. 중복 구조로 인해 문제가 발생할 수 있습니다.'); - } - - // 구조 출력 - const structure = JSON.stringify(soapData, null, 2); - - return { - isValid: issues.length === 0, - structure, - issues - }; -} - -// 통계 조회 유틸리티 함수 +// 통계 조회 export async function getVendorSendStatistics(): Promise<{ total: number; modified: number; @@ -655,106 +700,4 @@ export async function getVendorSendStatistics(): Promise<{ } } -// 테스트용 폼 데이터 송신 함수 (SOAP 라이브러리 사용) -export async function sendTestVendorDataToMDG(formData: Record<string, string>): Promise<{ - success: boolean; - message: string; - responseData?: unknown; -}> { - try { - console.log('🧪 테스트용 VENDOR 데이터 SOAP 송신 시작'); - - // CSV 파일 동적 로드 (더 안전함) - let csvFields: CsvField[] = []; - try { - const csvRaw = fs.readFileSync(CSV_PATH, 'utf-8'); - csvFields = parseCsv(csvRaw); - } catch (e) { - console.error('CSV 로딩 실패:', e); - return { - success: false, - message: 'CSV 필드 정의 파일을 로드할 수 없습니다.' - }; - } - - // 필수 필드 검증 - const requiredFields = csvFields.filter(f => f.mandatory).map(f => f.field); - const missingFields = requiredFields.filter(field => !formData[field]?.trim()); - - if (missingFields.length > 0) { - return { - success: false, - message: `필수 필드가 누락되었습니다: ${missingFields.join(', ')}` - }; - } - - // 필드 순서에 따라 데이터 생성 - const seen = new Set<string>(); - const uniqueFields = csvFields.filter(f => { - if (seen.has(f.field)) return false; - seen.add(f.field); - return true; - }); - - const supplierMaster: Record<string, string> = {}; - uniqueFields.forEach(f => { - supplierMaster[f.field] = formData[f.field] ?? ''; - }); - - // SOAP 요청 구조 생성 - const soapData = { - SUPPLIER_MASTER: supplierMaster - }; - - console.log('📄 테스트 SOAP 데이터 생성 완료'); - - // 데이터 구조 검증 (디버깅용) - const validation = validateSoapDataStructure(soapData); - if (!validation.isValid) { - console.warn('⚠️ 테스트 데이터 구조 문제:', validation.issues); - return { - success: false, - message: `데이터 구조 오류: ${validation.issues.join(', ')}` - }; - } else { - console.log('✅ 테스트 데이터 구조 검증 통과'); - } - - // SOAP 클라이언트로 요청 전송 - const responseData = await withSoapLogging( - 'OUTBOUND', - 'MDG', - 'IF_MDZ_EVCP_VENDOR_MASTER_TEST', - JSON.stringify(soapData), - async () => { - const client = await createSoapClient(); - - return new Promise<SoapResponse>((resolve, reject) => { - client.P2MD3007_AO(soapData, (err: SoapError | null, result: SoapResponse) => { - if (err) { - reject(err); - } else { - console.log('✅ 테스트 MDG 전송 성공'); - resolve(result); - } - }); - }); - } - ); - - return { - success: true, - message: '테스트 송신이 완료되었습니다.', - responseData - }; - - } catch (error) { - console.error('❌ 테스트 송신 실패:', error); - return { - success: false, - message: error instanceof Error ? error.message : 'Unknown error' - }; - } -} - |
