/* eslint-disable @typescript-eslint/no-explicit-any */ import { NextRequest } from "next/server"; import db from "@/db/db"; import { ORGANIZATION_MASTER_HRHMTB_CCTR, ORGANIZATION_MASTER_HRHMTB_CCTR_TEXT, ORGANIZATION_MASTER_HRHMTB_PCTR, ORGANIZATION_MASTER_HRHMTB_ZBUKRS, ORGANIZATION_MASTER_HRHMTB_ZEKGRP, ORGANIZATION_MASTER_HRHMTB_ZEKORG, ORGANIZATION_MASTER_HRHMTB_ZGSBER, ORGANIZATION_MASTER_HRHMTB_ZGSBER_TEXT, ORGANIZATION_MASTER_HRHMTB_ZLGORT, ORGANIZATION_MASTER_HRHMTB_ZSPART, ORGANIZATION_MASTER_HRHMTB_ZVKBUR, ORGANIZATION_MASTER_HRHMTB_ZVKGRP, ORGANIZATION_MASTER_HRHMTB_ZVKORG, ORGANIZATION_MASTER_HRHMTB_ZVSTEL, ORGANIZATION_MASTER_HRHMTB_ZVTWEG, ORGANIZATION_MASTER_HRHMTB_ZWERKS } from "@/db/schema/MDG/mdg"; import { ToXMLFields, serveWsdl, createXMLParser, extractRequestData, convertXMLToDBData, processNestedArray, createErrorResponse, createSuccessResponse, replaceSubTableData, withSoapLogging } from "@/lib/soap/utils"; import { bulkUpsert, bulkReplaceSubTableData } from "@/lib/soap/batch-utils"; import { coerce } from "zod/v4"; // 스키마에서 직접 타입 추론 type CctrData = typeof ORGANIZATION_MASTER_HRHMTB_CCTR.$inferInsert; type CctrTextData = typeof ORGANIZATION_MASTER_HRHMTB_CCTR_TEXT.$inferInsert; type PctrData = typeof ORGANIZATION_MASTER_HRHMTB_PCTR.$inferInsert; type ZbukrsData = typeof ORGANIZATION_MASTER_HRHMTB_ZBUKRS.$inferInsert; type ZekgrpData = typeof ORGANIZATION_MASTER_HRHMTB_ZEKGRP.$inferInsert; type ZekorgData = typeof ORGANIZATION_MASTER_HRHMTB_ZEKORG.$inferInsert; type ZgsberData = typeof ORGANIZATION_MASTER_HRHMTB_ZGSBER.$inferInsert; type ZgsberTextData = typeof ORGANIZATION_MASTER_HRHMTB_ZGSBER_TEXT.$inferInsert; type ZlgortData = typeof ORGANIZATION_MASTER_HRHMTB_ZLGORT.$inferInsert; type ZspartData = typeof ORGANIZATION_MASTER_HRHMTB_ZSPART.$inferInsert; type ZvkburData = typeof ORGANIZATION_MASTER_HRHMTB_ZVKBUR.$inferInsert; type ZvkgrpData = typeof ORGANIZATION_MASTER_HRHMTB_ZVKGRP.$inferInsert; type ZvkorgData = typeof ORGANIZATION_MASTER_HRHMTB_ZVKORG.$inferInsert; type ZvstelData = typeof ORGANIZATION_MASTER_HRHMTB_ZVSTEL.$inferInsert; type ZvtwegData = typeof ORGANIZATION_MASTER_HRHMTB_ZVTWEG.$inferInsert; type ZwerksData = typeof ORGANIZATION_MASTER_HRHMTB_ZWERKS.$inferInsert; // XML에서 받는 데이터 구조 type CctrXML = ToXMLFields> & { TEXT?: TextXML[]; }; type TextXML = ToXMLFields>; type PctrXML = ToXMLFields> & { TEXT?: TextXML[]; }; type ZbukrsXML = ToXMLFields>; type ZekgrpXML = ToXMLFields>; type ZekorgXML = ToXMLFields>; type ZgsberXML = ToXMLFields> & { TEXT?: ZgsberTextXML[]; }; type ZgsberTextXML = ToXMLFields>; type ZlgortXML = ToXMLFields>; type ZspartXML = ToXMLFields>; type ZvkburXML = ToXMLFields>; type ZvkgrpXML = ToXMLFields>; type ZvkorgXML = ToXMLFields>; type ZvstelXML = ToXMLFields>; type ZvtwegXML = ToXMLFields>; type ZwerksXML = ToXMLFields>; // 처리된 데이터 구조 interface ProcessedOrganizationData { cctrItems: Array<{ cctr: CctrData; texts: CctrTextData[] }>; pctrItems: Array<{ pctr: PctrData; texts: CctrTextData[] }>; zbukrsItems: ZbukrsData[]; zekgrpItems: ZekgrpData[]; zekorgItems: ZekorgData[]; zgsberItems: Array<{ zgsber: ZgsberData; texts: ZgsberTextData[] }>; zlgortItems: ZlgortData[]; zspartItems: ZspartData[]; zvkburItems: ZvkburData[]; zvkgrpItems: ZvkgrpData[]; zvkorgItems: ZvkorgData[]; zvstelItems: ZvstelData[]; zvtwegItems: ZvtwegData[]; zwerksItems: ZwerksData[]; } export async function GET(request: NextRequest) { const url = new URL(request.url); if (url.searchParams.has('wsdl')) { return serveWsdl('IF_MDZ_EVCP_ORGANIZATION_MASTER.wsdl'); } return new Response('Method Not Allowed', { status: 405 }); } export async function POST(request: NextRequest) { const url = new URL(request.url); if (url.searchParams.has('wsdl')) { return serveWsdl('IF_MDZ_EVCP_ORGANIZATION_MASTER.wsdl'); } const body = await request.text(); return withSoapLogging( 'INBOUND', 'MDG', 'IF_MDZ_EVCP_ORGANIZATION_MASTER', body, async () => { console.log('🚀 ORGANIZATION_MASTER 수신 시작, 데이터 길이:', body.length); const parser = createXMLParser([ 'HRHMTB_CCTR', 'HRHMTB_PCTR', 'HRHMTB_ZBUKRS', 'HRHMTB_ZEKGRP', 'HRHMTB_ZEKORG', 'HRHMTB_ZGSBER', 'HRHMTB_ZLGORT', 'HRHMTB_ZSPART', 'HRHMTB_ZVKBUR', 'HRHMTB_ZVKGRP', 'HRHMTB_ZVKORG', 'HRHMTB_ZVSTEL', 'HRHMTB_ZVTWEG', 'HRHMTB_ZWERKS', 'TEXT' ]); const parsedData = parser.parse(body); console.log('XML root keys:', Object.keys(parsedData)); // IF_MDZ_EVCP_ORGANIZATION_MASTER 또는 IF_MDZ_EVCP_ORGANIZATION_MASTERReq 패턴 시도 let requestData = extractRequestData(parsedData, 'IF_MDZ_EVCP_ORGANIZATION_MASTERReq'); if (!requestData) { requestData = extractRequestData(parsedData, 'IF_MDZ_EVCP_ORGANIZATION_MASTER'); } 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_MDZ_EVCP_ORGANIZATION_MASTERReq data'); } console.log('Validating request data structure:', Object.keys(requestData).map(key => `${key}: ${requestData[key] ? 'found' : 'not found'}`).join(', ') ); // XML 데이터를 DB 삽입 가능한 형태로 변환 const processedOrganizations = transformOrganizationData(requestData); // 데이터베이스 저장 await saveToDatabase(processedOrganizations); console.log('🎉 처리 완료: 조직 마스터 데이터'); return createSuccessResponse('http://60.101.108.100/api/IF_MDZ_EVCP_ORGANIZATION_MASTER/'); } ).catch(error => { return createErrorResponse(error); }); } /** * ORGANIZATION 마스터 데이터 변환 함수 * * 데이터 처리 아키텍처: * - 독립적인 여러 조직 테이블들을 처리 (CCTR, PCTR, ZBUKRS, ZEKGRP, ZEKORG, ZGSBER 등) * - 각 테이블은 고유한 unique 필드를 가짐 * - 일부 테이블은 하위 TEXT 테이블을 가짐 (CCTR, PCTR, ZGSBER) * - 전체 데이터셋 기반 upsert 처리 * * @param requestData XML에서 파싱된 ORGANIZATION 데이터 * @returns 처리된 ORGANIZATION 데이터 구조 */ function transformOrganizationData(requestData: any): ProcessedOrganizationData { const result: ProcessedOrganizationData = { cctrItems: [], pctrItems: [], zbukrsItems: [], zekgrpItems: [], zekorgItems: [], zgsberItems: [], zlgortItems: [], zspartItems: [], zvkburItems: [], zvkgrpItems: [], zvkorgItems: [], zvstelItems: [], zvtwegItems: [], zwerksItems: [] }; // HRHMTB_CCTR 처리 (unique 필드: CCTR, 하위 테이블: TEXT) if (requestData.items1 && Array.isArray(requestData.items1)) { result.cctrItems = requestData.items1.map((item: CctrXML) => { const cctrKey = item.CCTR || ''; const fkData = { CCTR: cctrKey }; const cctr = convertXMLToDBData( item as Record, fkData ); const texts = processNestedArray( item.TEXT, (text) => convertXMLToDBData(text as Record, fkData), fkData ); return { cctr, texts }; }); } // HRHMTB_PCTR 처리 (unique 필드: PCTR, 하위 테이블: TEXT) if (requestData.items2 && Array.isArray(requestData.items2)) { result.pctrItems = requestData.items2.map((item: PctrXML) => { const pctrKey = item.PCTR || ''; const fkData = { CCTR: pctrKey }; // TEXT 테이블은 CCTR 필드를 사용 const pctr = convertXMLToDBData( item as Record, fkData ); const texts = processNestedArray( item.TEXT, (text) => convertXMLToDBData(text as Record, fkData), fkData ); return { pctr, texts }; }); } // HRHMTB_ZBUKRS 처리 (unique 필드: ZBUKRS) if (requestData.items3 && Array.isArray(requestData.items3)) { result.zbukrsItems = requestData.items3.map((item: ZbukrsXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZEKGRP 처리 (unique 필드: ZEKGRP) if (requestData.items4 && Array.isArray(requestData.items4)) { result.zekgrpItems = requestData.items4.map((item: ZekgrpXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZEKORG 처리 (unique 필드: ZEKORG) if (requestData.items5 && Array.isArray(requestData.items5)) { result.zekorgItems = requestData.items5.map((item: ZekorgXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZGSBER 처리 (unique 필드: ZGSBER, 하위 테이블: TEXT) if (requestData.items6 && Array.isArray(requestData.items6)) { result.zgsberItems = requestData.items6.map((item: ZgsberXML) => { const zgsberKey = item.ZGSBER || ''; const fkData = { ZGSBER: zgsberKey }; const zgsber = convertXMLToDBData( item as Record, fkData ); const texts = processNestedArray( item.TEXT, (text) => convertXMLToDBData(text as Record, fkData), fkData ); return { zgsber, texts }; }); } // HRHMTB_ZLGORT 처리 (unique 필드: ZLGORT, ZWERKS) if (requestData.items7 && Array.isArray(requestData.items7)) { result.zlgortItems = requestData.items7.map((item: ZlgortXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZSPART 처리 (unique 필드: ZSPART) if (requestData.items8 && Array.isArray(requestData.items8)) { result.zspartItems = requestData.items8.map((item: ZspartXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZVKBUR 처리 (unique 필드: ZVKBUR) if (requestData.items9 && Array.isArray(requestData.items9)) { result.zvkburItems = requestData.items9.map((item: ZvkburXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZVKGRP 처리 (unique 필드: ZVKGRP) if (requestData.items10 && Array.isArray(requestData.items10)) { result.zvkgrpItems = requestData.items10.map((item: ZvkgrpXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZVKORG 처리 (unique 필드: ZVKORG) if (requestData.items11 && Array.isArray(requestData.items11)) { result.zvkorgItems = requestData.items11.map((item: ZvkorgXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZVSTEL 처리 (unique 필드: ZVSTEL) if (requestData.items12 && Array.isArray(requestData.items12)) { result.zvstelItems = requestData.items12.map((item: ZvstelXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZVTWEG 처리 (unique 필드: ZVTWEG) if (requestData.items13 && Array.isArray(requestData.items13)) { result.zvtwegItems = requestData.items13.map((item: ZvtwegXML) => convertXMLToDBData( item as Record ) ); } // HRHMTB_ZWERKS 처리 (unique 필드: ZWERKS) if (requestData.items14 && Array.isArray(requestData.items14)) { result.zwerksItems = requestData.items14.map((item: ZwerksXML) => convertXMLToDBData( item as Record ) ); } return result; } // 데이터베이스 저장 함수 /** * 처리된 ORGANIZATION 데이터를 데이터베이스에 저장 * * 저장 전략: * 1. 각 조직 테이블별로 고유 필드 기준 upsert 처리 * 2. 하위 TEXT 테이블들: FK 기준 전체 삭제 후 재삽입 * - CCTR_TEXT, ZGSBER_TEXT 포함 * 3. 14개의 독립적인 조직 테이블 처리 * * @param processedOrganizations 변환된 ORGANIZATION 데이터 */ async function saveToDatabase(processedOrganizations: ProcessedOrganizationData) { console.log('데이터베이스 저장 시작: 조직 마스터 데이터'); try { await db.transaction(async (tx) => { // 1) 부모 테이블 데이터 준비 (root) const cctrRows = processedOrganizations.cctrItems.map((c) => c.cctr).filter((c): c is CctrData => !!c.CCTR); const pctrRows = processedOrganizations.pctrItems; const zbukrsRows = processedOrganizations.zbukrsItems; const zekgrpRows = processedOrganizations.zekgrpItems; const zekorgRows = processedOrganizations.zekorgItems; const zgsberRows = processedOrganizations.zgsberItems.map((zgsber) => zgsber.zgsber).filter((zgsber): zgsber is ZgsberData => !!zgsber.ZGSBER); const zlgortRows = processedOrganizations.zlgortItems; const zspartRows = processedOrganizations.zspartItems; const zvkburRows = processedOrganizations.zvkburItems; const zvkgrpRows = processedOrganizations.zvkgrpItems; const zvkorgRows = processedOrganizations.zvkorgItems; const zvstelRows = processedOrganizations.zvstelItems; const zvtwegRows = processedOrganizations.zvtwegItems; const zwerksRows = processedOrganizations.zwerksItems; const cctrIds = cctrRows.map((cctr) => cctr.CCTR as string); const zgsberIds = zgsberRows.map((zgsber) => zgsber.ZGSBER as string); // 2) 하위 테이블 데이터 평탄화 (2건) const cctrTexts = processedOrganizations.cctrItems.flatMap((cctr) => cctr.texts); const zgsberTexts = processedOrganizations.zgsberItems.flatMap((zgsber) => zgsber.texts); // 3) 부모 테이블 UPSERT (배치) await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_CCTR, cctrRows, 'CCTR'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_PCTR, pctrRows, 'PCTR'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZBUKRS, zbukrsRows, 'ZBUKRS'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZEKGRP, zekgrpRows, 'ZEKGRP'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZEKORG, zekorgRows, 'ZEKORG'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZGSBER, zgsberRows, 'ZGSBER'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZLGORT, zlgortRows, 'ZLGORT'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZSPART, zspartRows, 'ZSPART'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVKBUR, zvkburRows, 'ZVKBUR'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVKGRP, zvkgrpRows, 'ZVKGRP'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVKORG, zvkorgRows, 'ZVKORG'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVSTEL, zvstelRows, 'ZVSTEL'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVTWEG, zvtwegRows, 'ZVTWEG'); await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZWERKS, zwerksRows, 'ZWERKS'); // 4) 하위 테이블 교체 (배치) (2건) await bulkReplaceSubTableData(tx, ORGANIZATION_MASTER_HRHMTB_CCTR_TEXT, cctrTexts, ORGANIZATION_MASTER_HRHMTB_CCTR_TEXT.CCTR, cctrIds); await bulkReplaceSubTableData(tx, ORGANIZATION_MASTER_HRHMTB_ZGSBER_TEXT, zgsberTexts, ORGANIZATION_MASTER_HRHMTB_ZGSBER_TEXT.ZGSBER, zgsberIds); }); console.log('조직 마스터 데이터 처리 완료.'); return true; } catch (error) { console.error('데이터베이스 저장 중 오류 발생:', error); throw error; } }