diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_BIDDING_PROJECT/route.ts | 269 |
1 files changed, 202 insertions, 67 deletions
diff --git a/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_BIDDING_PROJECT/route.ts b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_BIDDING_PROJECT/route.ts index 8a0b4b9c..a63fff40 100644 --- a/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_BIDDING_PROJECT/route.ts +++ b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_BIDDING_PROJECT/route.ts @@ -1,102 +1,208 @@ -// /app/api/soap/route.js import { NextRequest, NextResponse } from 'next/server'; -import { headers } from 'next/headers'; import { XMLParser } from 'fast-xml-parser'; -import { z } from 'zod'; import db from '@/db/db'; import { biddingProjects, projectSeries, NewBiddingProject, NewProjectSeries } from '@/db/schema/projects'; import { eq, and } from 'drizzle-orm'; +import { readFileSync } from 'fs'; +import { join } from 'path'; -// Zod schema for validating the incoming data -const BiddingProjectSchema = z.object({ - pspid: z.string(), - projNm: z.string().optional(), - sector: z.string().optional(), - projMsrm: z.coerce.number().optional(), - kunnr: z.string().optional(), - kunnrNm: z.string().optional(), - cls1: z.string().optional(), - cls1Nm: z.string().optional(), - ptype: z.string().optional(), - ptypeNm: z.string().optional(), - pmodelCd: z.string().optional(), - pmodelNm: z.string().optional(), - pmodelSz: z.string().optional(), - pmodelUom: z.string().optional(), - txt04: z.string().optional(), - txt30: z.string().optional(), - estmPm: z.string().optional(), -}); +// 요청 데이터 인터페이스 정의 +interface RequestData { + biddingProjects: BiddingProject[]; + projectSeries: ProjectSeries[]; +} -const ProjectSeriesSchema = z.object({ - pspid: z.string(), - sersNo: z.string(), - scDt: z.string().optional(), - klDt: z.string().optional(), - lcDt: z.string().optional(), - dlDt: z.string().optional(), - dockNo: z.string().optional(), - dockNm: z.string().optional(), -}); +interface BiddingProject { + pspid: string; + projNm?: string; + sector?: string; + projMsrm?: string; + kunnr?: string; + kunnrNm?: string; + cls1?: string; + cls1Nm?: string; + ptype?: string; + ptypeNm?: string; + pmodelCd?: string; + pmodelNm?: string; + pmodelSz?: string; + pmodelUom?: string; + txt04?: string; + txt30?: string; + estmPm?: string; +} -const RequestDataSchema = z.object({ - biddingProjects: z.array(BiddingProjectSchema).optional().default([]), - projectSeries: z.array(ProjectSeriesSchema).optional().default([]), -}); +interface ProjectSeries { + pspid: string; + sersNo: string; + scDt?: string; + klDt?: string; + lcDt?: string; + dlDt?: string; + dockNo?: string; + dockNm?: string; +} + +// WSDL 파일 제공 헬퍼 함수 +function serveWsdl() { + try { + const wsdlPath = join(process.cwd(), 'public', 'wsdl', 'IF_ECC_EVCP_BIDDING_PROJECT.wsdl'); + const wsdlContent = readFileSync(wsdlPath, 'utf-8'); + + return new NextResponse(wsdlContent, { + headers: { + 'Content-Type': 'text/xml; charset=utf-8', + }, + }); + } catch (error) { + console.error('Failed to read WSDL file:', error); + return new NextResponse('WSDL file not found', { status: 404 }); + } +} + +export async function GET(request: NextRequest) { + const url = new URL(request.url); + if (url.searchParams.has('wsdl')) { + return serveWsdl(); + } + + // 다른 GET 요청에 대한 처리는 405 Method Not Allowed 반환 + return new NextResponse('Method Not Allowed', { status: 405 }); +} export async function POST(request: NextRequest) { + // WSDL 요청 처리 + const url = new URL(request.url); + if (url.searchParams.has('wsdl')) { + return serveWsdl(); + } + try { - // SOAP 요청 본문 가져오기 + // 요청 본문 가져오기 const body = await request.text(); - const headersList = headers(); // 요청 로깅 - console.log('SOAP Request Headers:', headersList); + console.log('Request Body 일부:', body.substring(0, 200) + (body.length > 200 ? '...' : '')); // XML 파서 설정 const parser = new XMLParser({ ignoreAttributes: false, + attributeNamePrefix: '@_', + parseAttributeValue: false, // 문자열 값을 변환하지 않음 (숫자 등으로) + trimValues: true, // 앞뒤 공백 제거는 유지 isArray: (name: string) => { // biddingProjects와 projectSeries는 항상 배열로 처리 return ['biddingProjects', 'projectSeries'].includes(name); - } + }, + parseTagValue: false, // 태그 값도 타입 변환하지 않음 + allowBooleanAttributes: true }); // XML 파싱 const parsedData = parser.parse(body); - // SOAP Envelope 구조에서 실제 데이터 추출 - const soapBody = parsedData?.['soap:Envelope']?.['soap:Body']; - if (!soapBody) { - throw new Error('Invalid SOAP message structure'); + // 디버깅용 - 최상위 구조 확인 + console.log('XML root keys:', Object.keys(parsedData)); + + // XML 구조에서 실제 데이터 추출 + let requestData = null; + + // 가능한 경로 확인 + if (parsedData?.['soap:Envelope']?.['soap:Body']) { + const soapBody = parsedData['soap:Envelope']['soap:Body']; + requestData = extractRequestData(soapBody); + } else if (parsedData?.['SOAP:Envelope']?.['SOAP:Body']) { + const soapBody = parsedData['SOAP:Envelope']['SOAP:Body']; + requestData = extractRequestData(soapBody); + } else if (parsedData?.['Envelope']?.['Body']) { + const soapBody = parsedData['Envelope']['Body']; + requestData = extractRequestData(soapBody); + } else if (parsedData?.['soapenv:Envelope']?.['soapenv:Body']) { + const soapBody = parsedData['soapenv:Envelope']['soapenv:Body']; + requestData = extractRequestData(soapBody); + } else if (parsedData?.['IF_ECC_EVCP_BIDDING_PROJECTReq']) { + requestData = parsedData['IF_ECC_EVCP_BIDDING_PROJECTReq']; + console.log('Found direct IF_ECC_EVCP_BIDDING_PROJECTReq data'); + } else if (parsedData?.['ns1:IF_ECC_EVCP_BIDDING_PROJECTReq']) { + requestData = parsedData['ns1:IF_ECC_EVCP_BIDDING_PROJECTReq']; + console.log('Found direct ns1:IF_ECC_EVCP_BIDDING_PROJECTReq data'); + } else { + // 루트 레벨에서 biddingProjects 또는 projectSeries를 직접 찾기 + if (parsedData?.biddingProjects || parsedData?.projectSeries) { + requestData = parsedData; + console.log('Found data at root level'); + } else { + // 다른 모든 키에 대해 확인 + for (const key of Object.keys(parsedData)) { + const value = parsedData[key]; + // 데이터 구조가 맞는지 확인 (biddingProjects나 projectSeries가 있는지) + if (value && (value.biddingProjects || value.projectSeries)) { + requestData = value; + console.log(`Found data in root key: ${key}`); + break; + } + + // 키 이름에 IF_ECC_EVCP_BIDDING_PROJECTReq가 포함되어 있는지 확인 + if (key.includes('IF_ECC_EVCP_BIDDING_PROJECTReq')) { + requestData = value; + console.log(`Found data in root key with matching name: ${key}`); + break; + } + } + } } - // IF_ECC_EVCP_BIDDING_PROJECTReq에서 데이터 추출 - const requestData = soapBody['IF_ECC_EVCP_BIDDING_PROJECTReq']; if (!requestData) { - throw new Error('Missing request data'); + console.error('Could not find valid request data in the received payload'); + console.error('Received XML structure:', JSON.stringify(parsedData, null, 2)); + throw new Error('Missing request data - could not find IF_ECC_EVCP_BIDDING_PROJECTReq or project data'); } // 데이터 유효성 검증 - const validationResult = RequestDataSchema.safeParse({ - biddingProjects: requestData.biddingProjects || [], - projectSeries: requestData.projectSeries || [] - }); + console.log('Validating request data structure:', + `biddingProjects: ${requestData.biddingProjects ? 'found' : 'not found'}`, + `projectSeries: ${requestData.projectSeries ? 'found' : 'not found'}` + ); - if (!validationResult.success) { - console.error('Validation error:', validationResult.error); - throw new Error(`Invalid data format: ${validationResult.error.message}`); + // 샘플 데이터 로깅 + if (requestData.biddingProjects && Array.isArray(requestData.biddingProjects) && requestData.biddingProjects.length > 0) { + console.log('First biddingProject sample:', JSON.stringify(requestData.biddingProjects[0], null, 2)); } - const validatedData = validationResult.data; + if (requestData.projectSeries && Array.isArray(requestData.projectSeries) && requestData.projectSeries.length > 0) { + console.log('First projectSeries sample:', JSON.stringify(requestData.projectSeries[0], null, 2)); + } + + // 데이터 구조 정규화 + const normalizedData: RequestData = { + biddingProjects: Array.isArray(requestData.biddingProjects) + ? requestData.biddingProjects + : requestData.biddingProjects ? [requestData.biddingProjects] : [], + projectSeries: Array.isArray(requestData.projectSeries) + ? requestData.projectSeries + : requestData.projectSeries ? [requestData.projectSeries] : [] + }; + + // 기본 유효성 검사 - 필수 필드 확인 + for (const project of normalizedData.biddingProjects) { + if (!project.pspid) { + throw new Error('Missing required field: pspid in biddingProjects'); + } + } + + for (const series of normalizedData.projectSeries) { + if (!series.pspid || !series.sersNo) { + throw new Error('Missing required fields in projectSeries: pspid or sersNo'); + } + } // 데이터베이스 저장 - await saveToDatabase(validatedData); + await saveToDatabase(normalizedData); - console.log(`Processed ${validatedData.biddingProjects.length} bidding projects and ${validatedData.projectSeries.length} project series`); + console.log(`Processed ${normalizedData.biddingProjects.length} bidding projects and ${normalizedData.projectSeries.length} project series`); - // SOAP 응답 생성 (WSDL에 따라 빈 응답) - const soapResponse = `<?xml version="1.0" encoding="UTF-8"?> + // XML 응답 생성 + const xmlResponse = `<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://60.101.108.100/"> @@ -104,21 +210,21 @@ export async function POST(request: NextRequest) { </soap:Body> </soap:Envelope>`; - return new NextResponse(soapResponse, { + return new NextResponse(xmlResponse, { headers: { 'Content-Type': 'text/xml; charset=utf-8', }, }); } catch (error: unknown) { - console.error('SOAP Error:', error); + console.error('API Error:', error); - // 에러 응답 + // XML 에러 응답 const errorResponse = `<?xml version="1.0" encoding="UTF-8"?> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <soap:Fault> <faultcode>soap:Server</faultcode> - <faultstring>${error instanceof Error ? error.message : 'Unknown error'}</faultstring> + <faultstring>${error instanceof Error ? ('[from eVCP]: ' + error.message) : 'Unknown error'}</faultstring> </soap:Fault> </soap:Body> </soap:Envelope>`; @@ -132,8 +238,38 @@ export async function POST(request: NextRequest) { } } +// SOAP Body나 루트에서 요청 데이터 추출하는 헬퍼 함수 +function extractRequestData(data: Record<string, unknown>): Record<string, unknown> | null { + if (!data) return null; + + if (data['IF_ECC_EVCP_BIDDING_PROJECTReq']) { + return data['IF_ECC_EVCP_BIDDING_PROJECTReq'] as Record<string, unknown>; + } else if (data['tns:IF_ECC_EVCP_BIDDING_PROJECTReq']) { + return data['tns:IF_ECC_EVCP_BIDDING_PROJECTReq'] as Record<string, unknown>; + } else if (data['ns1:IF_ECC_EVCP_BIDDING_PROJECTReq']) { + return data['ns1:IF_ECC_EVCP_BIDDING_PROJECTReq'] as Record<string, unknown>; + } + + // 다른 키 검색 + for (const key of Object.keys(data)) { + if (key.includes('IF_ECC_EVCP_BIDDING_PROJECTReq')) { + return data[key] as Record<string, unknown>; + } + } + + // biddingProjects나 projectSeries가 직접 있는 경우 + if ( + (data.biddingProjects && typeof data.biddingProjects === 'object') || + (data.projectSeries && typeof data.projectSeries === 'object') + ) { + return data; + } + + return null; +} + // 데이터베이스 저장 함수 -async function saveToDatabase(data: z.infer<typeof RequestDataSchema>) { +async function saveToDatabase(data: RequestData) { // 트랜잭션으로 처리 try { // bidding projects 처리 @@ -147,8 +283,7 @@ async function saveToDatabase(data: z.infer<typeof RequestDataSchema>) { pspid: project.pspid, projNm: project.projNm, sector: project.sector, - // decimal 타입은 문자열로 변환하여 전달 - projMsrm: project.projMsrm !== undefined ? String(project.projMsrm) : null, + projMsrm: project.projMsrm, kunnr: project.kunnr, kunnrNm: project.kunnrNm, cls1: project.cls1, |
