/* eslint-disable @typescript-eslint/no-explicit-any */ import { NextRequest } from "next/server"; import db from "@/db/db"; import { EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF, EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF_NAME } 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"; // 스키마에서 직접 타입 추론 type EmpRefData = typeof EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF.$inferInsert; type EmpRefNameData = typeof EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF_NAME.$inferInsert; // XML에서 받는 데이터 구조 type EmpRefXML = ToXMLFields> & { NAME?: NameXML[]; }; type NameXML = ToXMLFields>; // 처리된 데이터 구조 interface ProcessedEmployeeReferenceData { empRef: EmpRefData; names: EmpRefNameData[]; } export async function GET(request: NextRequest) { const url = new URL(request.url); if (url.searchParams.has('wsdl')) { return serveWsdl('IF_MDZ_EVCP_EMPLOYEE_REFERENCE_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_EMPLOYEE_REFERENCE_MASTER.wsdl'); } const body = await request.text(); return withSoapLogging( 'INBOUND', 'MDG', 'IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER', body, async () => { console.log('Request Body 일부:', body.substring(0, 200) + (body.length > 200 ? '...' : '')); const parser = createXMLParser(['CMCTB_EMP_REF_MDG_IF', 'NAME']); const parsedData = parser.parse(body); console.log('XML root keys:', Object.keys(parsedData)); const requestData = extractRequestData(parsedData, 'IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTERReq'); 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_EMPLOYEE_REFERENCE_MASTERReq or CMCTB_EMP_REF_MDG_IF data'); } console.log('Validating request data structure:', `CMCTB_EMP_REF_MDG_IF: ${requestData.CMCTB_EMP_REF_MDG_IF ? 'found' : 'not found'}` ); if (requestData.CMCTB_EMP_REF_MDG_IF && Array.isArray(requestData.CMCTB_EMP_REF_MDG_IF) && requestData.CMCTB_EMP_REF_MDG_IF.length > 0) { console.log('First CMCTB_EMP_REF_MDG_IF sample:', JSON.stringify(requestData.CMCTB_EMP_REF_MDG_IF[0], null, 2)); } // XML 데이터를 DB 삽입 가능한 형태로 변환 const processedEmpRefs = transformEmpRefData(requestData.CMCTB_EMP_REF_MDG_IF as EmpRefXML[] || []); // 필수 필드 검증 for (const empRefData of processedEmpRefs) { if (!empRefData.empRef.GRPCD) { throw new Error('Missing required field: GRPCD in employee reference'); } } // 데이터베이스 저장 await saveToDatabase(processedEmpRefs); console.log(`Processed ${processedEmpRefs.length} employee references`); return createSuccessResponse('http://60.101.108.100/api/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/'); } ).catch(error => { return createErrorResponse(error); }); } // XML 데이터를 DB 삽입 가능한 형태로 변환 /** * EMPLOYEE_REFERENCE 마스터 데이터 변환 함수 * * 데이터 처리 아키텍처: * 1. 최상위 테이블 (EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF) * - GRPCD가 unique 필드로 충돌 시 upsert 처리 * * 2. 하위 테이블들 (NAME) * - FK(GRPCD)로 연결 * - 별도 필수 필드 없음 (스키마에서 notNull() 제거 예정) * - 전체 데이터셋 기반 삭제 후 재삽입 처리 * * @param empRefData XML에서 파싱된 EMPLOYEE_REFERENCE 데이터 * @returns 처리된 EMPLOYEE_REFERENCE 데이터 구조 */ function transformEmpRefData(empRefData: EmpRefXML[]): ProcessedEmployeeReferenceData[] { if (!empRefData || !Array.isArray(empRefData)) { return []; } return empRefData.map(empRef => { const grpcdKey = empRef.GRPCD || ''; const fkData = { GRPCD: grpcdKey }; // 1단계: EMP_REF (루트 - unique 필드: GRPCD) const empRefRecord = convertXMLToDBData( empRef as Record, fkData ); // 2단계: 하위 테이블들 (FK: GRPCD) const names = processNestedArray( empRef.NAME, (name) => convertXMLToDBData(name as Record, fkData), fkData ); return { empRef: empRefRecord, names }; }); } // 데이터베이스 저장 함수 /** * 처리된 EMPLOYEE_REFERENCE 데이터를 데이터베이스에 저장 * * 저장 전략: * 1. 최상위 테이블: GRPCD 기준 upsert (충돌 시 업데이트) * 2. 하위 테이블들: FK(GRPCD) 기준 전체 삭제 후 재삽입 * - 송신 XML이 전체 데이터셋을 포함하므로 부분 업데이트 불필요 * - 데이터 일관성과 단순성 확보 * * @param processedEmpRefs 변환된 EMPLOYEE_REFERENCE 데이터 배열 */ async function saveToDatabase(processedEmpRefs: ProcessedEmployeeReferenceData[]) { console.log(`데이터베이스(배치) 저장 시작: ${processedEmpRefs.length}개 직원 참조 데이터`); try { await db.transaction(async (tx) => { // 1) 부모 테이블 데이터 준비 const empRefRows = processedEmpRefs .map((e) => e.empRef) .filter((e): e is EmpRefData => !!e.GRPCD); const grpcds = empRefRows.map((e) => e.GRPCD as string); // 2) 하위 테이블 데이터 평탄화 const names = processedEmpRefs.flatMap((e) => e.names); // 3) 부모 테이블 UPSERT (배치) await bulkUpsert(tx, EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF, empRefRows, 'GRPCD'); // 4) 하위 테이블 교체 (배치) await bulkReplaceSubTableData(tx, EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF_NAME, names, EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF_NAME.GRPCD, grpcds); }); console.log(`데이터베이스 저장 완료: ${processedEmpRefs.length}개 직원 참조`); return true; } catch (error) { console.error('데이터베이스 저장 중 오류 발생:', error); throw error; } }