summaryrefslogtreecommitdiff
path: root/lib/soap
diff options
context:
space:
mode:
Diffstat (limited to 'lib/soap')
-rw-r--r--lib/soap/ecc/send/cancel-rfq.ts57
-rw-r--r--lib/soap/ecc/send/create-po.ts30
-rw-r--r--lib/soap/ecc/send/pcr-confirm.ts240
-rw-r--r--lib/soap/ecc/send/rfq-info.ts114
-rw-r--r--lib/soap/mdg/send/vendor-master/action.ts152
-rw-r--r--lib/soap/sender.ts14
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);