diff options
| author | joonhoekim <26rote@gmail.com> | 2025-08-25 10:52:24 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-08-25 10:52:24 +0000 |
| commit | 83f67ed333f0237b434a41d1eceef417c0d48313 (patch) | |
| tree | eb911edaccf778e78be5564416748ff577a8aa76 /lib | |
| parent | 780d56edd3772813b4e557061a3c90d9f7d1ddd0 (diff) | |
(김준회) 설정 변경 및 WSDL namespace 반영, 로그인 완료시 dashboard 이동 (콜백 없을 때)
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/shi-api/shi-api-utils.ts | 2 | ||||
| -rw-r--r-- | lib/soap/ecc/mapper/po-mapper.ts | 18 | ||||
| -rw-r--r-- | lib/soap/ecc/send/cancel-rfq.ts | 10 | ||||
| -rw-r--r-- | lib/soap/ecc/send/create-po.ts | 7 | ||||
| -rw-r--r-- | lib/soap/ecc/send/pcr-confirm.ts | 7 | ||||
| -rw-r--r-- | lib/soap/ecc/send/rfq-info.ts | 7 | ||||
| -rw-r--r-- | lib/soap/mdg/mapper/project-mapper.ts | 22 | ||||
| -rw-r--r-- | lib/soap/sender.ts | 20 |
8 files changed, 63 insertions, 30 deletions
diff --git a/lib/shi-api/shi-api-utils.ts b/lib/shi-api/shi-api-utils.ts index 7ea582f6..955cddd9 100644 --- a/lib/shi-api/shi-api-utils.ts +++ b/lib/shi-api/shi-api-utils.ts @@ -133,7 +133,7 @@ export const getAllNonsapUser = async () => { await db.insert(users) .values(mappedChunk) .onConflictDoUpdate({ - target: users.nonsapUserId, + target: users.email, set: { name: users.name, employeeNumber: users.employeeNumber, diff --git a/lib/soap/ecc/mapper/po-mapper.ts b/lib/soap/ecc/mapper/po-mapper.ts index a6363a87..6e282b98 100644 --- a/lib/soap/ecc/mapper/po-mapper.ts +++ b/lib/soap/ecc/mapper/po-mapper.ts @@ -98,10 +98,20 @@ export async function mapECCPOHeaderToBusiness( } }; + // projectId와 vendorId 필수 체크 + if (!projectId) { + debugError('프로젝트를 찾을 수 없어 매핑을 건너뜁니다', { pspid: eccHeader.PSPID }); + throw new Error(`프로젝트를 찾을 수 없습니다: PSPID=${eccHeader.PSPID}`); + } + if (!vendorId) { + debugError('벤더를 찾을 수 없어 매핑을 건너뜁니다', { lifnr: eccHeader.LIFNR }); + throw new Error(`벤더를 찾을 수 없습니다: LIFNR=${eccHeader.LIFNR}`); + } + // 매핑 const mappedData: ContractData = { - projectId: projectId || 1, // TODO: 기본값 설정, 실제로는 유효한 projectId 필요 - vendorId: vendorId || 1, // TODO: 기본값 설정, 실제로는 유효한 vendorId 필요 + projectId, + vendorId, contractNo: eccHeader.EBELN || '', contractName: eccHeader.ZTITLE || eccHeader.EBELN || '', status: eccHeader.ZPO_CNFM_STAT || 'ACTIVE', @@ -249,14 +259,14 @@ export async function mapECCPODetailToBusiness( const calculatedTaxAmount = (unitPriceNum * quantity * taxRateNum) / 100; taxAmount = calculatedTaxAmount.toString(); } catch (error) { - debugError('세액 계산 오류', { unitPrice, taxRate, quantity, error }); + debugError('세액(taxAmount) 계산 오류((unitPriceNum * quantity * taxRateNum) / 100)', { unitPrice, taxRate, quantity, error }); } } // 매핑 const mappedData: ContractItemData = { contractId, - itemId: itemId || 1, // TODO: 기본값 설정, 실제로는 유효한 itemId 필요 + itemId: itemId!, // 아이템이 없으면 자동 생성되므로 null이 될 수 없음 description: eccDetail.MAKTX || null, quantity, unitPrice, diff --git a/lib/soap/ecc/send/cancel-rfq.ts b/lib/soap/ecc/send/cancel-rfq.ts index fcddddf8..b26ca38b 100644 --- a/lib/soap/ecc/send/cancel-rfq.ts +++ b/lib/soap/ecc/send/cancel-rfq.ts @@ -2,8 +2,8 @@ import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult } from "@/lib/soap/sender"; -// ECC RFQ 취소 엔드포인트 -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"; +// 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"; // RFQ 취소 요청 데이터 타입 export interface CancelRFQRequest { @@ -70,7 +70,8 @@ async function sendCancelRFQToECC(rfqData: CancelRFQRequest): Promise<SoapSendRe soapAction: 'http://sap.com/xi/WebService/soap1.1', timeout: 30000, // RFQ 취소는 30초 타임아웃 retryCount: 3, - retryDelay: 1000 + retryDelay: 1000, + namespace: 'http://shi.samsung.co.kr/P2_MM/MMM' // ECC MM 모듈 네임스페이스 }; // 로그 정보 @@ -83,6 +84,9 @@ async function sendCancelRFQToECC(rfqData: CancelRFQRequest): Promise<SoapSendRe const rfqNumbers = rfqData.T_ANFNR.map(item => item.ANFNR).join(', '); console.log(`📤 RFQ 취소 요청 전송 시작 - RFQ Numbers: ${rfqNumbers}`); console.log(`🔍 취소 대상 RFQ ${rfqData.T_ANFNR.length}개`); + console.log(`🌐 엔드포인트: ${ECC_CANCEL_RFQ_ENDPOINT}`); + console.log(`📋 네임스페이스: ${config.namespace}`); + console.log(`🔐 SOAPAction: ${config.soapAction}`); // SOAP XML 전송 const result = await sendSoapXml(config, logInfo); diff --git a/lib/soap/ecc/send/create-po.ts b/lib/soap/ecc/send/create-po.ts index 3bd28057..d44f091b 100644 --- a/lib/soap/ecc/send/create-po.ts +++ b/lib/soap/ecc/send/create-po.ts @@ -3,8 +3,8 @@ 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"; +// 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"; // PO 헤더 데이터 타입 export interface POHeaderData { @@ -154,7 +154,8 @@ async function sendPOToECC(poData: POCreateRequest): Promise<SoapSendResult> { soapAction: 'http://sap.com/xi/WebService/soap1.1', timeout: 60000, // PO 생성은 60초 타임아웃 retryCount: 3, - retryDelay: 2000 + retryDelay: 2000, + namespace: 'http://shi.samsung.co.kr/P2_MM/MMM' // ECC MM 모듈 네임스페이스 }; // 로그 정보 diff --git a/lib/soap/ecc/send/pcr-confirm.ts b/lib/soap/ecc/send/pcr-confirm.ts index 7ac2d931..46d1a909 100644 --- a/lib/soap/ecc/send/pcr-confirm.ts +++ b/lib/soap/ecc/send/pcr-confirm.ts @@ -3,8 +3,8 @@ import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult } from "@/lib/soap/sender"; -// ECC PCR 확인 엔드포인트 -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"; +// 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"; // PCR 확인 요청 데이터 타입 export interface PCRConfirmRequest { @@ -167,7 +167,8 @@ async function sendPCRConfirmToECC(pcrData: PCRConfirmRequest): Promise<SoapSend soapAction: 'http://sap.com/xi/WebService/soap1.1', timeout: 30000, // PCR 확인은 30초 타임아웃 retryCount: 3, - retryDelay: 1000 + retryDelay: 1000, + namespace: 'http://shi.samsung.co.kr/P2_MM/MMM' // ECC MM 모듈 네임스페이스 }; // 로그 정보 diff --git a/lib/soap/ecc/send/rfq-info.ts b/lib/soap/ecc/send/rfq-info.ts index 43fe821f..d313a74b 100644 --- a/lib/soap/ecc/send/rfq-info.ts +++ b/lib/soap/ecc/send/rfq-info.ts @@ -19,8 +19,8 @@ import { sendSoapXml, type SoapSendConfig, type SoapLogInfo, type SoapSendResult } from "@/lib/soap/sender"; import { getCurrentSAPDate } from "@/lib/soap/utils"; -// ECC RFQ 정보 전송 엔드포인트 -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"; +// 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"; // RFQ 헤더 데이터 타입 export interface RFQHeaderData { @@ -145,7 +145,8 @@ async function sendRFQToECC(rfqData: RFQInfoRequest): Promise<SoapSendResult> { soapAction: 'http://sap.com/xi/WebService/soap1.1', timeout: 60000, // RFQ 정보 전송은 60초 타임아웃 retryCount: 3, - retryDelay: 2000 + retryDelay: 2000, + namespace: 'http://shi.samsung.co.kr/P2_MM/MMM' // ECC MM 모듈 네임스페이스 }; // 로그 정보 diff --git a/lib/soap/mdg/mapper/project-mapper.ts b/lib/soap/mdg/mapper/project-mapper.ts index 112dd4f5..6de2b38a 100644 --- a/lib/soap/mdg/mapper/project-mapper.ts +++ b/lib/soap/mdg/mapper/project-mapper.ts @@ -76,10 +76,9 @@ export async function mapAndSaveMDGProjectData( /** * MDG 프로젝트 데이터를 비즈니스 테이블 projects 구조로 변환 - * TODO: 실제 매핑 로직은 사용자가 추가할 예정 * id: serial("id").primaryKey(), << 자동 - code: varchar("code", { length: 50 }).notNull(), << + code: varchar("code", { length: 50 }).notNull(), name: text("name").notNull(), type: varchar("type", { length: 20 }).default("ship").notNull(), pspid: char('pspid', { length: 24 }).unique(), // 프로젝트ID (ECC), TODO: 매핑 필요 @@ -94,12 +93,15 @@ function mapMDGToProject(mdgProject: MDGProjectData): ProjectData | null { return null; } - // TODO: 사용자가 실제 매핑 로직을 추가할 예정 // 현재는 기본 구조만 제공 const mappedProject: ProjectData = { code: mdgProject.PROJ_NO || '', - name: mdgProject.PROJ_NM || mdgProject.PROJ_NO || '', - type: 'ship', // 기본값, 필요시 매핑 로직 추가 + name: mdgProject.PROJ_DSC || '', + type: checkProjectType(mdgProject.TYPE || ''), // 기본값, 필요시 매핑 로직 추가 + // type 매핑 방법: TYPE 네임스페이스에서 null 값을 수신한 경우 조선(ship)이고, 그 외는 해양(plant)이다. + // 수신할 수 있는 경우의 수는 H, T, H/T, null 인데, 필요시 hull / top 구분 처리하자. 현재는 조선/해양 구분만 필요하고, 기존 코드들도 plant 값인지 ship 값인지만 따지고 있다. + // 의미상으로는 H = 해양 Hull, T = 해양 Top, H/T = 해양 Hull과 Top 둘 다 하는 프로젝트 인데, 일단은 조선/해양 구분만 ship/plant 값을 넣어주는 것으로 하자. + // TODO: pspid를 구해야 하는데 mdg에서 안주는 것 같음. 나프로에게 문의함. pspid: mdgProject.PROJ_NO || null, // ECC 프로젝트 ID와 연결 // id, createdAt, updatedAt는 자동 생성 }; @@ -167,3 +169,13 @@ async function saveProjectsToDatabase(mappedProjects: ProjectData[]): Promise<vo throw error; } } + +// === Utility Functions === + +function checkProjectType(type: string) { + if (type == 'H' || type == 'T' || type == 'H/T') { + return 'plant'; + } else { + return 'ship'; + } +}
\ No newline at end of file diff --git a/lib/soap/sender.ts b/lib/soap/sender.ts index 580d0c5a..5a61462e 100644 --- a/lib/soap/sender.ts +++ b/lib/soap/sender.ts @@ -12,11 +12,12 @@ export interface SoapAuthConfig { // SOAP 전송 설정 타입 export interface SoapSendConfig { endpoint: string; - envelope: Record<string, any>; + envelope: Record<string, unknown>; soapAction?: string; timeout?: number; retryCount?: number; retryDelay?: number; + namespace?: string; // 네임스페이스를 동적으로 설정할 수 있도록 추가 } // 로깅 정보 타입 @@ -64,8 +65,8 @@ function createXmlBuilder() { // SOAP Envelope 생성 function createSoapEnvelope( namespace: string, - bodyContent: Record<string, any> -): Record<string, any> { + bodyContent: Record<string, unknown> +): Record<string, unknown> { return { 'soap:Envelope': { '@_xmlns:soap': 'http://schemas.xmlsoap.org/soap/envelope/', @@ -77,7 +78,7 @@ function createSoapEnvelope( // XML 생성 export async function generateSoapXml( - envelope: Record<string, any>, + envelope: Record<string, unknown>, xmlDeclaration: string = '<?xml version="1.0" encoding="UTF-8"?>\n' ): Promise<string> { const builder = createXmlBuilder(); @@ -95,9 +96,10 @@ 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( - 'http://shi.samsung.co.kr/P2_MD/MDZ', + namespace, config.envelope ); @@ -249,7 +251,7 @@ export async function sendSoapXmlWithRetry( // 간단한 SOAP 전송 함수 (기본 설정 사용) export async function sendSimpleSoapXml( endpoint: string, - bodyContent: Record<string, any>, + bodyContent: Record<string, unknown>, logInfo: SoapLogInfo, options?: { namespace?: string; @@ -263,6 +265,7 @@ export async function sendSimpleSoapXml( envelope: bodyContent, soapAction: options?.soapAction, timeout: options?.timeout || 30000, // 기본 30초 + namespace: options?.namespace, // 네임스페이스 옵션 추가 }; const auth = options?.auth || getDefaultAuth(); @@ -272,7 +275,7 @@ export async function sendSimpleSoapXml( // MDG 전용 SOAP 전송 함수 (기존 action.ts와 호환) export async function sendMdgSoapXml( - bodyContent: Record<string, any>, + bodyContent: Record<string, unknown>, logInfo: SoapLogInfo, auth?: SoapAuthConfig ): Promise<SoapSendResult> { @@ -281,6 +284,7 @@ export async function sendMdgSoapXml( envelope: bodyContent, soapAction: 'http://sap.com/xi/WebService/soap1.1', timeout: 60000, // MDG는 60초 타임아웃 + namespace: 'http://shi.samsung.co.kr/P2_MD/MDZ', // MDG 전용 네임스페이스 명시 }; return await sendSoapXml(config, logInfo, auth); |
