summaryrefslogtreecommitdiff
path: root/app/api/(S-ERP)
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/(S-ERP)')
-rw-r--r--app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_BIDDING_PROJECT/route.ts269
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,