summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-03 07:05:05 +0000
committerjoonhoekim <26rote@gmail.com>2025-09-03 07:05:05 +0000
commitc5fc7195505d82c60fc9dffc3744782b91d7ebb7 (patch)
tree588350714f121f8c394a9634fb2d669309420a37
parent0ed64cf896ad5b18dbee958aaa7fec17ffd9dfa9 (diff)
(김준회) ECC 송신건 엔드포인트 오류 수정 및 테스트 송신페이지 개선
-rw-r--r--app/[lng]/admin/ecc/page.tsx44
-rw-r--r--lib/soap/ecc/send/cancel-rfq.ts10
-rw-r--r--lib/soap/ecc/send/create-po.ts10
-rw-r--r--lib/soap/ecc/send/pcr-confirm.ts10
-rw-r--r--lib/soap/ecc/send/rfq-info.ts10
-rw-r--r--lib/soap/sender.ts88
6 files changed, 136 insertions, 36 deletions
diff --git a/app/[lng]/admin/ecc/page.tsx b/app/[lng]/admin/ecc/page.tsx
index bb3168c4..257ecfea 100644
--- a/app/[lng]/admin/ecc/page.tsx
+++ b/app/[lng]/admin/ecc/page.tsx
@@ -25,6 +25,11 @@ interface TestResult {
responseData?: string
timestamp: string
duration?: number
+ statusCode?: number
+ endpoint?: string
+ headers?: Record<string, string>
+ requestXml?: string
+ requestHeaders?: Record<string, string>
}
export default function ECCSenderTestPage() {
@@ -46,7 +51,12 @@ export default function ECCSenderTestPage() {
message: result.message,
responseData: result.responseData,
timestamp: new Date().toLocaleString('ko-KR'),
- duration
+ duration,
+ statusCode: result.statusCode,
+ endpoint: result.endpoint,
+ headers: result.headers,
+ requestXml: result.requestXml,
+ requestHeaders: result.requestHeaders
}
setTestResults(prev => ({ ...prev, [testName]: testResult }))
@@ -681,6 +691,11 @@ function TestResultCard({ result, title }: { result: TestResult; title: string }
<XCircle className="h-4 w-4 text-red-600" />
)}
<span className="font-semibold">{title}</span>
+ {typeof result.statusCode !== 'undefined' && (
+ <Badge variant="outline" className="text-xs">
+ HTTP {result.statusCode}
+ </Badge>
+ )}
<Badge variant="outline" className="text-xs">
{result.duration}ms
</Badge>
@@ -691,6 +706,33 @@ function TestResultCard({ result, title }: { result: TestResult; title: string }
<AlertDescription className="mt-2">
<div className="space-y-2">
<p>{result.message}</p>
+ {result.endpoint && (
+ <p className="text-xs text-muted-foreground break-all"><span className="font-medium">Endpoint:</span> {result.endpoint}</p>
+ )}
+ {result.headers && (
+ <details className="text-xs">
+ <summary className="cursor-pointer font-medium">응답 헤더 보기</summary>
+ <pre className="mt-2 p-2 bg-gray-100 rounded text-xs overflow-auto max-h-40">
+ {Object.entries(result.headers).map(([k, v]) => `${k}: ${v}`).join('\n')}
+ </pre>
+ </details>
+ )}
+ {result.requestHeaders && (
+ <details className="text-xs">
+ <summary className="cursor-pointer font-medium">요청 헤더 보기</summary>
+ <pre className="mt-2 p-2 bg-gray-100 rounded text-xs overflow-auto max-h-40">
+ {Object.entries(result.requestHeaders).map(([k, v]) => `${k}: ${v}`).join('\n')}
+ </pre>
+ </details>
+ )}
+ {result.requestXml && (
+ <details className="text-xs">
+ <summary className="cursor-pointer font-medium">요청 XML 보기</summary>
+ <pre className="mt-2 p-2 bg-gray-100 rounded text-xs overflow-auto max-h-60 whitespace-pre-wrap break-words">
+ {result.requestXml}
+ </pre>
+ </details>
+ )}
{result.responseData && (
<details className="text-xs">
<summary className="cursor-pointer font-medium">응답 데이터 보기</summary>
diff --git a/lib/soap/ecc/send/cancel-rfq.ts b/lib/soap/ecc/send/cancel-rfq.ts
index bcbe0c7a..aeba6dd6 100644
--- a/lib/soap/ecc/send/cancel-rfq.ts
+++ b/lib/soap/ecc/send/cancel-rfq.ts
@@ -3,7 +3,7 @@
import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult } from "@/lib/soap/sender";
// ECC RFQ 취소 엔드포인트 (WSDL에 명시된 P2038_D 사용)
-const ECC_CANCEL_RFQ_ENDPOINT = "http://shii8dvddb01.hec.serp.shi.samsung.net:50000/sap/xi/engine?type=entry&version=3.0&Sender.Service=P2038_D&Interface=http%3A%2F%2Fshi.samsung.co.kr%2FP2_MM%2FMMM%5EP2MM3016_SO&QualityOfService=ExactlyOnce";
+const ECC_CANCEL_RFQ_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%5EP2MM3016_SO";
// RFQ 취소 요청 데이터 타입
export interface CancelRFQRequest {
@@ -118,6 +118,10 @@ export async function cancelRFQ(rfqNumber: string): Promise<{
success: boolean;
message: string;
responseData?: string;
+ statusCode?: number;
+ headers?: Record<string, string>;
+ endpoint?: string;
+ requestXml?: string;
rfqNumber?: string;
}> {
try {
@@ -135,6 +139,10 @@ export async function cancelRFQ(rfqNumber: string): Promise<{
success: result.success,
message: result.success ? 'RFQ 취소 요청이 성공적으로 전송되었습니다.' : result.message,
responseData: result.responseText,
+ statusCode: result.statusCode,
+ headers: result.headers,
+ endpoint: result.endpoint,
+ requestXml: result.requestXml,
rfqNumber
};
diff --git a/lib/soap/ecc/send/create-po.ts b/lib/soap/ecc/send/create-po.ts
index 0d992fb3..444d9cc1 100644
--- a/lib/soap/ecc/send/create-po.ts
+++ b/lib/soap/ecc/send/create-po.ts
@@ -4,7 +4,7 @@ import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult
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_D&Interface=http%3A%2F%2Fshi.samsung.co.kr%2FP2_MM%2FMMM%5EP2MM3015_SO&QualityOfService=ExactlyOnce";
+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 {
@@ -198,6 +198,10 @@ export async function createPurchaseOrder(poData: POCreateRequest): Promise<{
success: boolean;
message: string;
responseData?: string;
+ statusCode?: number;
+ headers?: Record<string, string>;
+ endpoint?: string;
+ requestXml?: string;
bidding_number?: string;
}> {
try {
@@ -209,6 +213,10 @@ export async function createPurchaseOrder(poData: POCreateRequest): Promise<{
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
};
diff --git a/lib/soap/ecc/send/pcr-confirm.ts b/lib/soap/ecc/send/pcr-confirm.ts
index 72d4a574..439ec6f8 100644
--- a/lib/soap/ecc/send/pcr-confirm.ts
+++ b/lib/soap/ecc/send/pcr-confirm.ts
@@ -4,7 +4,7 @@
import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult } from "@/lib/soap/sender";
// ECC PCR 확인 엔드포인트 (WSDL에 명시된 P2038_D 사용)
-const ECC_PCR_CONFIRM_ENDPOINT = "http://shii8dvddb01.hec.serp.shi.samsung.net:50000/sap/xi/engine?type=entry&version=3.0&Sender.Service=P2038_D&Interface=http%3A%2F%2Fshi.samsung.co.kr%2FP2_MM%2FMMM%5EP2MM3019_SO&QualityOfService=ExactlyOnce";
+const ECC_PCR_CONFIRM_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%5EP2MM3019_SO";
// PCR 확인 요청 데이터 타입
export interface PCRConfirmRequest {
@@ -227,6 +227,10 @@ export async function confirmPCR(pcrItem: {
success: boolean;
message: string;
responseData?: string;
+ statusCode?: number;
+ headers?: Record<string, string>;
+ endpoint?: string;
+ requestXml?: string;
pcrRequest?: string;
}> {
try {
@@ -242,6 +246,10 @@ export async function confirmPCR(pcrItem: {
success: result.success,
message: result.success ? 'PCR 확인 요청이 성공적으로 전송되었습니다.' : result.message,
responseData: result.responseText,
+ statusCode: result.statusCode,
+ headers: result.headers,
+ endpoint: result.endpoint,
+ requestXml: result.requestXml,
pcrRequest: `${pcrItem.PCR_REQ}-${pcrItem.PCR_REQ_SEQ}`
};
diff --git a/lib/soap/ecc/send/rfq-info.ts b/lib/soap/ecc/send/rfq-info.ts
index 777c1dfe..ffc47fc6 100644
--- a/lib/soap/ecc/send/rfq-info.ts
+++ b/lib/soap/ecc/send/rfq-info.ts
@@ -20,7 +20,7 @@ import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult
import { getCurrentSAPDate } from "@/lib/soap/utils";
// ECC RFQ 정보 전송 엔드포인트 (WSDL에 명시된 P2038_D 사용)
-const ECC_RFQ_ENDPOINT = "http://shii8dvddb01.hec.serp.shi.samsung.net:50000/sap/xi/engine?type=entry&version=3.0&Sender.Service=P2038_D&Interface=http%3A%2F%2Fshi.samsung.co.kr%2FP2_MM%2FMMM%5EP2MM3014_SO&QualityOfService=ExactlyOnce";
+const ECC_RFQ_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%5EP2MM3014_SO";
// RFQ 헤더 데이터 타입
export interface RFQHeaderData {
@@ -189,6 +189,10 @@ export async function sendRFQInformation(rfqData: RFQInfoRequest): Promise<{
success: boolean;
message: string;
responseData?: string;
+ statusCode?: number;
+ headers?: Record<string, string>;
+ endpoint?: string;
+ requestXml?: string;
rfq_number?: string;
}> {
try {
@@ -200,6 +204,10 @@ export async function sendRFQInformation(rfqData: RFQInfoRequest): Promise<{
success: result.success,
message: result.success ? 'RFQ 정보가 성공적으로 전송되었습니다.' : result.message,
responseData: result.responseText,
+ statusCode: result.statusCode,
+ headers: result.headers,
+ endpoint: result.endpoint,
+ requestXml: result.requestXml,
rfq_number: rfqData.T_RFQ_HEADER[0]?.ANFNR
};
diff --git a/lib/soap/sender.ts b/lib/soap/sender.ts
index 3728429b..1dfc8730 100644
--- a/lib/soap/sender.ts
+++ b/lib/soap/sender.ts
@@ -35,6 +35,9 @@ export interface SoapSendResult {
responseText?: string;
statusCode?: number;
headers?: Record<string, string>;
+ endpoint?: string;
+ requestXml?: string;
+ requestHeaders?: Record<string, string>;
}
// 기본 환경변수에서 인증 정보 가져오기
@@ -111,34 +114,46 @@ export async function sendSoapXml(
console.log('📤 SOAP XML 전송 시작');
console.log('🔍 전송 XML (첫 500자):', xmlData.substring(0, 500));
+ // 요청 헤더 및 fetch 옵션을 사전에 구성
+ const requestHeaders: Record<string, string> = {
+ 'Content-Type': 'text/xml; charset=utf-8',
+ 'SOAPAction': config.soapAction || 'http://sap.com/xi/WebService/soap1.1',
+ };
+
+ // Basic Authentication 헤더 추가
+ if (authConfig.username && authConfig.password) {
+ const credentials = Buffer.from(`${authConfig.username}:${authConfig.password}`).toString('base64');
+ requestHeaders['Authorization'] = `Basic ${credentials}`;
+ console.log('🔐 Basic Authentication 헤더 추가 완료');
+ } else {
+ console.warn('⚠️ SOAP 인증 정보가 설정되지 않았습니다.');
+ }
+
+ const fetchOptions: RequestInit = {
+ method: 'POST',
+ headers: requestHeaders,
+ body: xmlData,
+ };
+
+ // Body 루트 요소(p1:MT_...)에 기본 네임스페이스를 부여하여 하위 무접두사 요소들도 동일 네임스페이스로 인식되도록 처리
+ const envelopeObj = soapEnvelope as Record<string, any>;
+ const bodyObj = envelopeObj['soap:Envelope']?.['soap:Body'] as Record<string, any> | undefined;
+ if (bodyObj && typeof bodyObj === 'object') {
+ const rootKeys = Object.keys(bodyObj);
+ if (rootKeys.length > 0) {
+ const rootKey = rootKeys[0];
+ if (bodyObj[rootKey] && typeof bodyObj[rootKey] === 'object') {
+ bodyObj[rootKey]['@_xmlns'] = namespace;
+ }
+ }
+ }
+
const result = await withSoapLogging(
logInfo.direction,
logInfo.system,
logInfo.interface,
xmlData,
async () => {
- // 헤더 설정
- const headers: Record<string, string> = {
- 'Content-Type': 'text/xml; charset=utf-8',
- 'SOAPAction': config.soapAction || 'http://sap.com/xi/WebService/soap1.1',
- };
-
- // Basic Authentication 헤더 추가
- if (authConfig.username && authConfig.password) {
- const credentials = Buffer.from(`${authConfig.username}:${authConfig.password}`).toString('base64');
- headers['Authorization'] = `Basic ${credentials}`;
- console.log('🔐 Basic Authentication 헤더 추가 완료');
- } else {
- console.warn('⚠️ SOAP 인증 정보가 설정되지 않았습니다.');
- }
-
- // fetch 옵션 설정
- const fetchOptions: RequestInit = {
- method: 'POST',
- headers,
- body: xmlData,
- };
-
// 타임아웃 설정
if (config.timeout) {
const controller = new AbortController();
@@ -171,14 +186,22 @@ export async function sendSoapXml(
console.log('📥 SOAP 응답 수신:', response.status, response.statusText);
console.log('🔍 응답 XML (첫 500자):', responseText.substring(0, 500));
- // HTTP 상태 코드 확인
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
- }
-
- // SOAP Fault 검사
- if (responseText.includes('soap:Fault') || responseText.includes('SOAP:Fault')) {
- throw new Error(`SOAP Fault: ${responseText}`);
+ // HTTP 상태 코드가 비정상이거나 SOAP Fault 포함 시 실패로 처리하되 본문을 그대로 반환
+ if (!response.ok || responseText.includes('soap:Fault') || responseText.includes('SOAP:Fault')) {
+ const responseHeaders: Record<string, string> = {};
+ response.headers.forEach((value, key) => {
+ responseHeaders[key] = value;
+ });
+ return {
+ success: false,
+ message: !response.ok ? `HTTP ${response.status}: ${response.statusText}` : 'SOAP Fault',
+ responseText,
+ statusCode: response.status,
+ headers: responseHeaders,
+ endpoint: config.endpoint,
+ requestXml: xmlData,
+ requestHeaders
+ };
}
// 응답 헤더 수집
@@ -192,7 +215,10 @@ export async function sendSoapXml(
message: '전송 성공',
responseText,
statusCode: response.status,
- headers: responseHeaders
+ headers: responseHeaders,
+ endpoint: config.endpoint,
+ requestXml: xmlData,
+ requestHeaders
};
} catch (error) {