summaryrefslogtreecommitdiff
path: root/lib/soap/ecc/send/create-po.ts
diff options
context:
space:
mode:
Diffstat (limited to 'lib/soap/ecc/send/create-po.ts')
-rw-r--r--lib/soap/ecc/send/create-po.ts417
1 files changed, 417 insertions, 0 deletions
diff --git a/lib/soap/ecc/send/create-po.ts b/lib/soap/ecc/send/create-po.ts
new file mode 100644
index 00000000..3bd28057
--- /dev/null
+++ b/lib/soap/ecc/send/create-po.ts
@@ -0,0 +1,417 @@
+'use server'
+
+import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult } from "@/lib/soap/sender";
+import { getCurrentSAPDate, getCurrentSAPTime } from "@/lib/soap/utils";
+
+// ECC PO 생성 엔드포인트
+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";
+
+// PO 헤더 데이터 타입
+export interface POHeaderData {
+ ANFNR: string; // Bidding Number (M)
+ 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)
+ INCO2: string; // Incoterms (Part 2) (M)
+ 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<string, unknown> {
+ return {
+ 'ns0:MT_P2MM3015_S': {
+ '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<SoapSendResult> {
+ 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
+ };
+
+ // 로그 정보
+ 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;
+ 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,
+ 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'
+ };
+ }
+}
+
+// ========================================
+// 유틸리티 함수들
+// ========================================
+
+// 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하여 사용