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.ts481
-rw-r--r--app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts66
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts2
13 files changed, 186 insertions, 383 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 ba2849a3..f5689be2 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,368 +1,179 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { XMLParser } from 'fast-xml-parser';
+import { NextRequest } from 'next/server';
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';
+import { biddingProjects, projectSeries } from '@/db/schema/projects';
+import {
+ ToXMLFields,
+ serveWsdl,
+ createXMLParser,
+ extractRequestData,
+ convertXMLToDBData,
+ createSoapResponse,
+ withSoapLogging,
+} from '@/lib/soap/utils';
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
-// 요청 데이터 인터페이스 정의
-interface RequestData {
- biddingProjects: BiddingProject[];
- projectSeries: ProjectSeries[];
-}
+// 스키마에서 타입 추론
+type BiddingProjectData = typeof biddingProjects.$inferInsert;
+type ProjectSeriesData = typeof projectSeries.$inferInsert;
-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;
- shipMHull?: string; // SHIP(조선), HULL(해양) 구분으로, 조선일 경우 NULL, 해양일 경우 'X'
-}
+// XML 구조 타입 정의 (XML에서 오는 추가 필드 포함)
+type BiddingProjectXML = ToXMLFields<Omit<BiddingProjectData, 'id' | 'createdAt' | 'updatedAt'>> & {
+ shipMHull?: string; // XML에서 오는 추가 필드
+};
+type ProjectSeriesXML = ToXMLFields<Omit<ProjectSeriesData, 'id' | 'createdAt' | 'updatedAt'>>;
-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 });
- }
+// 처리된 데이터 구조
+interface ProcessedBiddingData {
+ biddingProjects: BiddingProjectData[];
+ projectSeries: ProjectSeriesData[];
}
+// GET 요청 처리는 ?wsdl 달고 있으면 WSDL 서비스 제공
export async function GET(request: NextRequest) {
const url = new URL(request.url);
if (url.searchParams.has('wsdl')) {
- return serveWsdl();
+ return serveWsdl('IF_ECC_EVCP_BIDDING_PROJECT.wsdl');
}
-
- // 다른 GET 요청에 대한 처리는 405 Method Not Allowed 반환
- return new NextResponse('Method Not Allowed', { status: 405 });
+
+ return new Response('Method Not Allowed', { status: 405 });
}
+// POST 요청이 데이터 적재 요구 (SOAP)
export async function POST(request: NextRequest) {
- // WSDL 요청 처리
const url = new URL(request.url);
if (url.searchParams.has('wsdl')) {
- return serveWsdl();
+ return serveWsdl('IF_ECC_EVCP_BIDDING_PROJECT.wsdl');
}
- try {
- // 요청 본문 가져오기
- const body = await request.text();
-
- // 요청 로깅
- 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);
-
- // 디버깅용 - 최상위 구조 확인
- 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;
- }
- }
+ const body = await request.text();
+
+ // SOAP 로깅 래퍼 함수 사용
+ return withSoapLogging(
+ 'INBOUND',
+ 'ECC',
+ 'IF_ECC_EVCP_BIDDING_PROJECT',
+ body,
+ async () => {
+ console.log('🚀 BIDDING_PROJECT 수신 시작, 데이터 길이:', body.length);
+
+ // 1) XML 파싱
+ const parser = createXMLParser(['biddingProjects', 'projectSeries']);
+ const parsedData = parser.parse(body);
+
+ // 2) SOAP Body 또는 루트에서 요청 데이터 추출
+ const requestData = extractRequestData(parsedData, 'IF_ECC_EVCP_BIDDING_PROJECTReq');
+ if (!requestData) {
+ console.error('유효한 요청 데이터를 찾을 수 없습니다');
+ throw new Error('Missing request data - IF_ECC_EVCP_BIDDING_PROJECTReq not found');
}
- }
-
- if (!requestData) {
- 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');
- }
-
- // 데이터 유효성 검증
- console.log('Validating request data structure:',
- `biddingProjects: ${requestData.biddingProjects ? 'found' : 'not found'}`,
- `projectSeries: ${requestData.projectSeries ? 'found' : 'not found'}`
- );
-
- // 샘플 데이터 로깅
- if (requestData.biddingProjects && Array.isArray(requestData.biddingProjects) && requestData.biddingProjects.length > 0) {
- console.log('First biddingProject sample:', JSON.stringify(requestData.biddingProjects[0], null, 2));
- }
-
- 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');
+
+ // 3) XML 데이터를 DB 삽입 가능한 형태로 변환
+ const processedData = transformBiddingData(requestData as BiddingRequestXML);
+
+ // 4) 필수 필드 검증
+ for (const project of processedData.biddingProjects) {
+ if (!project.pspid) {
+ throw new Error('Missing required field: pspid in Bidding Project');
+ }
}
- }
-
- for (const series of normalizedData.projectSeries) {
- if (!series.pspid || !series.sersNo) {
- throw new Error('Missing required fields in projectSeries: pspid or sersNo');
+ for (const series of processedData.projectSeries) {
+ if (!series.pspid || !series.sersNo) {
+ throw new Error('Missing required fields in Project Series: pspid, sersNo');
+ }
}
+
+ // 5) 데이터베이스 저장
+ await saveToDatabase(processedData);
+
+ console.log(`🎉 처리 완료: ${processedData.biddingProjects.length}개 프로젝트, ${processedData.projectSeries.length}개 시리즈`);
+
+ // 6) 성공 응답 반환
+ return createSoapResponse('http://60.101.108.100/', {
+ 'tns:IF_ECC_EVCP_BIDDING_PROJECTRes': {
+ EV_TYPE: 'S',
+ },
+ });
}
-
- // 데이터베이스 저장
- await saveToDatabase(normalizedData);
-
- console.log(`Processed ${normalizedData.biddingProjects.length} bidding projects and ${normalizedData.projectSeries.length} project series`);
-
- // 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/">
- <soap:Body>
- </soap:Body>
-</soap:Envelope>`;
-
- return new NextResponse(xmlResponse, {
- headers: {
- 'Content-Type': 'text/xml; charset=utf-8',
- },
- });
- } catch (error: unknown) {
- 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 ? ('[from eVCP]: ' + error.message) : 'Unknown error'}</faultstring>
- </soap:Fault>
- </soap:Body>
-</soap:Envelope>`;
-
- return new NextResponse(errorResponse, {
- status: 500,
- headers: {
- 'Content-Type': 'text/xml; charset=utf-8',
+ ).catch((error) => {
+ // withSoapLogging에서 이미 에러 로그를 처리하므로, 여기서는 응답만 생성
+ return createSoapResponse('http://60.101.108.100/', {
+ 'tns:IF_ECC_EVCP_BIDDING_PROJECTRes': {
+ EV_TYPE: 'E',
+ EV_MESSAGE:
+ error instanceof Error ? error.message.slice(0, 100) : 'Unknown error',
},
});
- }
+ });
}
-// 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>;
+// -----------------------------------------------------------------------------
+// 데이터 변환 및 저장 관련 유틸리티
+// -----------------------------------------------------------------------------
+
+// Root XML Request 타입
+type BiddingRequestXML = {
+ biddingProjects?: BiddingProjectXML[];
+ projectSeries?: ProjectSeriesXML[];
+};
+
+// XML -> DB 데이터 변환 함수
+function transformBiddingData(requestData: BiddingRequestXML): ProcessedBiddingData {
+ const projects = requestData.biddingProjects || [];
+ const series = requestData.projectSeries || [];
+
+ // Bidding Projects 변환
+ const biddingProjectsConverted = projects.map((project) => {
+ const converted = convertXMLToDBData<BiddingProjectData>(
+ project as Record<string, string | undefined>,
+ undefined
+ );
+
+ // shipMHull 값을 기준으로 pjtType 결정
+ if (project.shipMHull) {
+ converted.pjtType = 'HULL'; // shipMHull 값이 존재하면 해양(HULL)
+ } else {
+ converted.pjtType = 'SHIP'; // shipMHull 값이 없으면 조선(SHIP)
}
- }
-
- // biddingProjects나 projectSeries가 직접 있는 경우
- if (
- (data.biddingProjects && typeof data.biddingProjects === 'object') ||
- (data.projectSeries && typeof data.projectSeries === 'object')
- ) {
- return data;
- }
-
- return null;
+
+ return converted;
+ });
+
+ // Project Series 변환
+ const projectSeriesConverted = series.map((seriesItem) => {
+ return convertXMLToDBData<ProjectSeriesData>(
+ seriesItem as Record<string, string | undefined>,
+ undefined
+ );
+ });
+
+ return {
+ biddingProjects: biddingProjectsConverted,
+ projectSeries: projectSeriesConverted,
+ };
}
// 데이터베이스 저장 함수
-async function saveToDatabase(data: RequestData) {
- // 트랜잭션으로 처리
+async function saveToDatabase(processedData: ProcessedBiddingData) {
+ console.log(`데이터베이스(배치) 저장 시작: ${processedData.biddingProjects.length}개 프로젝트, ${processedData.projectSeries.length}개 시리즈`);
+
try {
- // bidding projects 처리
- for (const project of data.biddingProjects) {
- // 기존 프로젝트 확인
- const existingProject = await db.query.biddingProjects.findFirst({
- where: eq(biddingProjects.pspid, project.pspid)
- });
-
- // shipMHull 값을 기준으로 pjtType 결정
- let pjtType: 'SHIP' | 'HULL' | null = null;
- if (project.shipMHull) {
- // shipMHull 값이 존재하면 해양(HULL)
- pjtType = 'HULL';
- } else {
- // shipMHull 값이 없으면 조선(SHIP)
- pjtType = 'SHIP';
- }
+ await db.transaction(async (tx) => {
+ // 1) Bidding Projects UPSERT (배치)
+ const biddingProjectRows = processedData.biddingProjects.filter((p): p is BiddingProjectData => !!p.pspid);
+ await bulkUpsert(tx, biddingProjects, biddingProjectRows, 'pspid');
- const projectValues: NewBiddingProject = {
- pspid: project.pspid,
- projNm: project.projNm,
- sector: project.sector,
- projMsrm: project.projMsrm,
- kunnr: project.kunnr,
- kunnrNm: project.kunnrNm,
- cls1: project.cls1,
- cls1Nm: project.cls1Nm,
- ptype: project.ptype,
- ptypeNm: project.ptypeNm,
- pmodelCd: project.pmodelCd,
- pmodelNm: project.pmodelNm,
- pmodelSz: project.pmodelSz,
- pmodelUom: project.pmodelUom,
- txt04: project.txt04,
- txt30: project.txt30,
- estmPm: project.estmPm,
- pjtType: pjtType
- };
-
- if (existingProject) {
- // 업데이트
- await db
- .update(biddingProjects)
- .set({
- ...projectValues,
- updatedAt: new Date()
- })
- .where(eq(biddingProjects.pspid, project.pspid));
- } else {
- // 신규 등록
- await db.insert(biddingProjects).values(projectValues);
- }
- }
-
- // project series 처리
- for (const series of data.projectSeries) {
- // 기존 시리즈 확인
- const existingSeries = await db.query.projectSeries.findFirst({
- where: and(
- eq(projectSeries.pspid, series.pspid),
- eq(projectSeries.sersNo, series.sersNo)
- )
- });
-
- const seriesValues: NewProjectSeries = {
- pspid: series.pspid,
- sersNo: series.sersNo,
- scDt: series.scDt,
- klDt: series.klDt,
- lcDt: series.lcDt,
- dlDt: series.dlDt,
- dockNo: series.dockNo,
- dockNm: series.dockNm
- };
-
- if (existingSeries) {
- // 업데이트
- await db
- .update(projectSeries)
- .set(seriesValues)
- .where(and(
- eq(projectSeries.pspid, series.pspid),
- eq(projectSeries.sersNo, series.sersNo)
- ));
- } else {
- // 신규 등록
- await db.insert(projectSeries).values(seriesValues);
- }
- }
- } catch (error: unknown) {
- console.error('Database operation failed:', error);
- throw new Error(`Database operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
+ // 2) Project Series 교체 (배치)
+ const projectKeys = biddingProjectRows.map((p) => p.pspid as string);
+ await bulkReplaceSubTableData(tx, projectSeries, processedData.projectSeries, projectSeries.pspid, projectKeys);
+ });
+
+ console.log(`데이터베이스(배치) 저장 완료: ${processedData.biddingProjects.length}개 프로젝트, ${processedData.projectSeries.length}개 시리즈`);
+ return true;
+ } catch (error) {
+ console.error('데이터베이스(배치) 저장 중 오류 발생:', error);
+ throw error;
}
} \ No newline at end of file
diff --git a/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts
index 31b61ffc..fdf0c8d4 100644
--- a/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts
+++ b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts
@@ -1,6 +1,9 @@
import { NextRequest } from 'next/server';
import db from '@/db/db';
-
+import {
+ PR_INFORMATION_T_BID_HEADER,
+ PR_INFORMATION_T_BID_ITEM,
+} from '@/db/schema/ECC/ecc';
import {
ToXMLFields,
serveWsdl,
@@ -11,14 +14,13 @@ import {
createErrorResponse,
createSuccessResponse,
createSoapResponse,
- replaceSubTableData,
withSoapLogging,
} from '@/lib/soap/utils';
-
import {
- PR_INFORMATION_T_BID_HEADER,
- PR_INFORMATION_T_BID_ITEM,
-} from '@/db/schema/ECC/ecc';
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
+
// 스키마에서 타입 추론
type BidHeaderData = typeof PR_INFORMATION_T_BID_HEADER.$inferInsert;
@@ -34,6 +36,7 @@ interface ProcessedPRData {
bidItems: BidItemData[];
}
+// GET 요청 처리는 ?wsdl 달고 있으면 WSDL 서비스 제공
export async function GET(request: NextRequest) {
const url = new URL(request.url);
if (url.searchParams.has('wsdl')) {
@@ -43,6 +46,7 @@ export async function GET(request: NextRequest) {
return new Response('Method Not Allowed', { status: 405 });
}
+// POST 요청이 데이터 적재 요구 (SOAP)
export async function POST(request: NextRequest) {
const url = new URL(request.url);
if (url.searchParams.has('wsdl')) {
@@ -54,7 +58,7 @@ export async function POST(request: NextRequest) {
// SOAP 로깅 래퍼 함수 사용
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'ECC',
'IF_ECC_EVCP_PR_INFORMATION',
body,
async () => {
@@ -155,45 +159,33 @@ function transformPRData(requestData: PRRequestXML): ProcessedPRData[] {
// 데이터베이스 저장 함수
async function saveToDatabase(processedPRs: ProcessedPRData[]) {
- console.log(`데이터베이스 저장 시작: ${processedPRs.length}개 PR 데이터`);
+ console.log(`데이터베이스(배치) 저장 시작: ${processedPRs.length}개 PR 데이터`);
try {
await db.transaction(async (tx) => {
- for (const prData of processedPRs) {
- const { bidHeader, bidItems } = prData;
+ // 1) 부모 테이블 데이터 준비 (키 없는 이상데이터 제거)
+ const bidHeaderRows = processedPRs
+ .map((c) => c.bidHeader)
+ .filter((h): h is BidHeaderData => !!h.ANFNR);
- if (!bidHeader.ANFNR) {
- console.warn('ANFNR가 없는 헤더 발견, 건너뜁니다.');
- continue;
- }
+ const bidHeaderKeys = bidHeaderRows.map((h) => h.ANFNR as string);
- // 1. 헤더 테이블 Upsert (ANFNR 기준)
- await tx
- .insert(PR_INFORMATION_T_BID_HEADER)
- .values(bidHeader)
- .onConflictDoUpdate({
- target: PR_INFORMATION_T_BID_HEADER.ANFNR,
- set: {
- ...bidHeader,
- updatedAt: new Date(),
- },
- });
-
- // 2. 아이템 테이블 전체 교체 (ANFNR FK 기준)
- await replaceSubTableData(
- tx,
- PR_INFORMATION_T_BID_ITEM,
- bidItems,
- 'ANFNR',
- bidHeader.ANFNR
- );
- }
+ // 2) 하위 테이블 데이터 평탄화
+ const bidItems = processedPRs.flatMap((c) => c.bidItems);
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, PR_INFORMATION_T_BID_HEADER, bidHeaderRows, 'ANFNR');
+
+ // 4) 하위 테이블 교체 (배치)
+ await Promise.all([
+ bulkReplaceSubTableData(tx, PR_INFORMATION_T_BID_ITEM, bidItems, PR_INFORMATION_T_BID_ITEM.ANFNR, bidHeaderKeys),
+ ]);
});
- console.log(`데이터베이스 저장 완료: ${processedPRs.length}개 PR`);
+ console.log(`데이터베이스(배치) 저장 완료: ${processedPRs.length}개 PR`);
return true;
} catch (error) {
- console.error('데이터베이스 저장 중 오류 발생:', error);
+ console.error('데이터베이스(배치) 저장 중 오류 발생:', error);
throw error;
}
} \ No newline at end of file
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts
index 0cedcade..5b5bb1a8 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts
@@ -125,7 +125,7 @@ export async function POST(request: NextRequest) {
// SOAP 로깅 래퍼 함수 사용
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_CUSTOMER_MASTER',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts
index fb54dff3..0aedbf02 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts
@@ -67,7 +67,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_DEPARTMENT_CODE',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts
index 388c4dc4..ee664d48 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts
@@ -154,7 +154,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_EMPLOYEE_MASTER',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts
index 563696d3..ec1dad0c 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts
@@ -58,7 +58,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts
index 5544dfdb..9dcc4f82 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts
@@ -77,7 +77,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_EQUP_MASTER',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts
index 97c5f1c1..2c2ab0fc 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts
@@ -77,7 +77,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_MATERIAL_MASTER_PART',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts
index 7b1b85e8..71479698 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts
@@ -46,7 +46,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts
index 9d76adbb..f0915527 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts
@@ -76,7 +76,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG
'IF_MDZ_EVCP_MODEL_MASTER',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts
index 3051fd8f..4bd6a02c 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts
@@ -122,7 +122,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_ORGANIZATION_MASTER',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts
index c1563859..fd7fb027 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts
@@ -47,7 +47,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_PROJECT_MASTER',
body,
async () => {
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts
index 61269937..9c74b5c5 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts
@@ -122,7 +122,7 @@ export async function POST(request: NextRequest) {
return withSoapLogging(
'INBOUND',
- 'S-ERP',
+ 'MDG',
'IF_MDZ_EVCP_VENDOR_MASTER',
body,
async () => {