/* eslint-disable @typescript-eslint/no-explicit-any */ import { NextRequest } from "next/server"; import db from "@/db/db"; import { DEPARTMENT_CODE_CMCTB_DEPT_MDG, DEPARTMENT_CODE_CMCTB_DEPT_MDG_COMPNM, DEPARTMENT_CODE_CMCTB_DEPT_MDG_CORPNM, DEPARTMENT_CODE_CMCTB_DEPT_MDG_DEPTNM } from "@/db/schema/MDG/mdg"; import { ToXMLFields, serveWsdl, createXMLParser, extractRequestData, convertXMLToDBData, processNestedArray, createErrorResponse, createSuccessResponse, withSoapLogging } from "@/lib/soap/utils"; import { bulkUpsert, bulkReplaceSubTableData } from "@/lib/soap/batch-utils"; // 스키마에서 직접 타입 추론 type DeptData = typeof DEPARTMENT_CODE_CMCTB_DEPT_MDG.$inferInsert; type CompnmData = typeof DEPARTMENT_CODE_CMCTB_DEPT_MDG_COMPNM.$inferInsert; type CorpnmData = typeof DEPARTMENT_CODE_CMCTB_DEPT_MDG_CORPNM.$inferInsert; type DeptnmData = typeof DEPARTMENT_CODE_CMCTB_DEPT_MDG_DEPTNM.$inferInsert; // XML에서 받는 데이터 구조 type DeptXML = ToXMLFields> & { DEPTNM?: DeptnmXML[]; COMPNM?: CompnmXML[]; CORPNM?: CorpnmXML[]; }; type DeptnmXML = ToXMLFields>; type CompnmXML = ToXMLFields>; type CorpnmXML = ToXMLFields>; // 처리된 데이터 구조 interface ProcessedDepartmentData { dept: DeptData; deptnms: DeptnmData[]; compnms: CompnmData[]; corpnms: CorpnmData[]; } export async function GET(request: NextRequest) { const url = new URL(request.url); if (url.searchParams.has('wsdl')) { return serveWsdl('IF_MDZ_EVCP_DEPARTMENT_CODE.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_DEPARTMENT_CODE.wsdl'); } const body = await request.text(); return withSoapLogging( 'INBOUND', 'MDG', 'IF_MDZ_EVCP_DEPARTMENT_CODE', body, async () => { console.log('Request Body 일부:', body.substring(0, 200) + (body.length > 200 ? '...' : '')); const parser = createXMLParser(['CMCTB_DEPT_MDG', 'DEPTNM', 'COMPNM', 'CORPNM']); const parsedData = parser.parse(body); console.log('XML root keys:', Object.keys(parsedData)); const requestData = extractRequestData(parsedData, 'IF_MDZ_EVCP_DEPARTMENT_CODEReq'); 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_DEPARTMENT_CODEReq or CMCTB_DEPT_MDG data'); } console.log('Validating request data structure:', `CMCTB_DEPT_MDG: ${requestData.CMCTB_DEPT_MDG ? 'found' : 'not found'}` ); if (requestData.CMCTB_DEPT_MDG && Array.isArray(requestData.CMCTB_DEPT_MDG) && requestData.CMCTB_DEPT_MDG.length > 0) { console.log('First CMCTB_DEPT_MDG sample:', JSON.stringify(requestData.CMCTB_DEPT_MDG[0], null, 2)); } // XML 데이터를 DB 삽입 가능한 형태로 변환 const processedDepts = transformDepartmentData(requestData.CMCTB_DEPT_MDG as DeptXML[] || []); // 필수 필드 검증 for (const deptData of processedDepts) { if (!deptData.dept.DEPTCD) { throw new Error('Missing required field: DEPTCD in department'); } if (!deptData.dept.CORPCD) { throw new Error('Missing required field: CORPCD in department'); } } // 데이터베이스 저장 await saveToDatabase(processedDepts); console.log(`Processed ${processedDepts.length} departments`); return createSuccessResponse('http://60.101.108.100/api/IF_MDZ_EVCP_DEPARTMENT_CODE/'); } ).catch(error => { return createErrorResponse(error); }); } // XML 데이터를 DB 삽입 가능한 형태로 변환 /** * DEPARTMENT 코드 데이터 변환 함수 * * 데이터 처리 아키텍처: * 1. 최상위 테이블 (DEPARTMENT_CODE_CMCTB_DEPT_MDG) * - DEPTCD가 unique 필드로 충돌 시 upsert 처리 * * 2. 하위 테이블들 (DEPTNM, COMPNM, CORPNM) * - FK(DEPTCD)로 연결 * - 별도 필수 필드 없음 (스키마에서 notNull() 제거 예정) * - 전체 데이터셋 기반 삭제 후 재삽입 처리 * * @param deptData XML에서 파싱된 DEPARTMENT 데이터 * @returns 처리된 DEPARTMENT 데이터 구조 */ function transformDepartmentData(deptData: DeptXML[]): ProcessedDepartmentData[] { if (!deptData || !Array.isArray(deptData)) { return []; } return deptData.map(dept => { const deptcdKey = dept.DEPTCD || ''; const fkData = { DEPTCD: deptcdKey }; // 1단계: DEPT (루트 - unique 필드: DEPTCD) const deptRecord = convertXMLToDBData( dept as Record, fkData ); // 2단계: 하위 테이블들 (FK: DEPTCD) const deptnms = processNestedArray( dept.DEPTNM, (deptnm) => convertXMLToDBData(deptnm as Record, fkData), fkData ); const compnms = processNestedArray( dept.COMPNM, (compnm) => convertXMLToDBData(compnm as Record, fkData), fkData ); const corpnms = processNestedArray( dept.CORPNM, (corpnm) => convertXMLToDBData(corpnm as Record, fkData), fkData ); return { dept: deptRecord, deptnms, compnms, corpnms }; }); } // 데이터베이스 저장 함수 /** * 처리된 DEPARTMENT 데이터를 데이터베이스에 저장 * * 저장 전략: * 1. 최상위 테이블: DEPTCD 기준 upsert (충돌 시 업데이트) * 2. 하위 테이블들: FK(DEPTCD) 기준 전체 삭제 후 재삽입 * - 송신 XML이 전체 데이터셋을 포함하므로 부분 업데이트 불필요 * - 데이터 일관성과 단순성 확보 * * @param processedDepts 변환된 DEPARTMENT 데이터 배열 */ async function saveToDatabase(processedDepts: ProcessedDepartmentData[]) { console.log(`데이터베이스(배치) 저장 시작: ${processedDepts.length}개 부서 데이터`); try { await db.transaction(async (tx) => { const deptRows = processedDepts .map((d) => d.dept) .filter((d): d is DeptData => !!d.DEPTCD); const deptcds = deptRows.map((d) => d.DEPTCD as string); const deptnms = processedDepts.flatMap((d) => d.deptnms); const compnms = processedDepts.flatMap((d) => d.compnms); const corpnms = processedDepts.flatMap((d) => d.corpnms); await bulkUpsert(tx, DEPARTMENT_CODE_CMCTB_DEPT_MDG, deptRows, 'DEPTCD'); await Promise.all([ bulkReplaceSubTableData(tx, DEPARTMENT_CODE_CMCTB_DEPT_MDG_DEPTNM, deptnms, DEPARTMENT_CODE_CMCTB_DEPT_MDG_DEPTNM.DEPTCD, deptcds), bulkReplaceSubTableData(tx, DEPARTMENT_CODE_CMCTB_DEPT_MDG_COMPNM, compnms, DEPARTMENT_CODE_CMCTB_DEPT_MDG_COMPNM.DEPTCD, deptcds), bulkReplaceSubTableData(tx, DEPARTMENT_CODE_CMCTB_DEPT_MDG_CORPNM, corpnms, DEPARTMENT_CODE_CMCTB_DEPT_MDG_CORPNM.DEPTCD, deptcds), ]); }); console.log(`데이터베이스(배치) 저장 완료: ${processedDepts.length}개 부서`); return true; } catch (error) { console.error('데이터베이스 저장 중 오류 발생:', error); throw error; } }