diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-01 10:14:51 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-01 10:14:51 +0000 |
| commit | 91d0d3f73002d033a2f7869f39c6b7c832c8588f (patch) | |
| tree | d7570c11b7e9eda8167d36ea08302ff428731c14 /app/api/(S-ERP)/(MDG)/utils.ts | |
| parent | 933589898bb26c1585b0585407e935f10e3be2ab (diff) | |
| parent | 1092e6cbcafd69e236f9a57e2f18987be5764bd5 (diff) | |
(merge) merged local commits
Diffstat (limited to 'app/api/(S-ERP)/(MDG)/utils.ts')
| -rw-r--r-- | app/api/(S-ERP)/(MDG)/utils.ts | 88 |
1 files changed, 71 insertions, 17 deletions
diff --git a/app/api/(S-ERP)/(MDG)/utils.ts b/app/api/(S-ERP)/(MDG)/utils.ts index bcb1dd45..437988dc 100644 --- a/app/api/(S-ERP)/(MDG)/utils.ts +++ b/app/api/(S-ERP)/(MDG)/utils.ts @@ -1,8 +1,8 @@ import { XMLParser } from "fast-xml-parser"; import { readFileSync } from "fs"; -import { NextRequest, NextResponse } from "next/server"; +import { NextResponse } from "next/server"; import { join } from "path"; -import { eq, desc } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import db from "@/db/db"; import { soapLogs, type LogDirection, type SoapLogInsert } from "@/db/schema/SOAP/soap"; @@ -19,6 +19,8 @@ export interface SoapBodyData { // WSDL 파일 제공 함수 export function serveWsdl(wsdlFileName: string) { try { + // public/wsdl 에서 WSDL 제공함을 가정 + // 이게 WSDL 구현 표준인데, 보안 감사에서 반대한다면 제거 const wsdlPath = join(process.cwd(), 'public', 'wsdl', wsdlFileName); const wsdlContent = readFileSync(wsdlPath, 'utf-8'); @@ -33,7 +35,8 @@ export function serveWsdl(wsdlFileName: string) { } } -// XML 파서 생성 (기본 설정) +// XML 파서 생성 +// SAP XI 가 자동생성해 보내는 XML을 처리할 수 있도록 설정함 export function createXMLParser(arrayTags: string[] = []) { return new XMLParser({ ignoreAttributes: false, @@ -51,7 +54,7 @@ export function extractRequestData( parsedData: Record<string, unknown>, requestKeyPattern: string ): SoapBodyData | null { - // SOAP 구조 체크 + // SOAP 구조 체크 (방어적) const soapPaths = [ ['soap:Envelope', 'soap:Body'], ['SOAP:Envelope', 'SOAP:Body'], @@ -60,8 +63,9 @@ export function extractRequestData( ]; for (const [envelope, body] of soapPaths) { - if (parsedData?.[envelope]?.[body]) { - const result = extractFromSoapBody(parsedData[envelope][body] as SoapBodyData, requestKeyPattern); + const envelopeData = parsedData?.[envelope] as Record<string, unknown> | undefined; + if (envelopeData?.[body]) { + const result = extractFromSoapBody(envelopeData[body] as SoapBodyData, requestKeyPattern); if (result) return result; } } @@ -126,9 +130,26 @@ function extractFromSoapBody(soapBody: SoapBodyData, requestKeyPattern: string): } // 범용 XML → DB 변환 함수 +/** + * XML 데이터를 DB 삽입 가능한 형태로 변환 + * + * 아키텍처 설계: + * - 하위 테이블들은 별도의 필수 필드가 없다고 가정 (스키마에서 notNull() 제거 예정) + * - FK는 항상 최상위 테이블의 unique 필드를 참조 + * - 송신된 XML은 항상 전체 데이터셋을 포함 + * - 최상위 테이블의 unique 필드가 충돌하면 전체 삭제 후 재삽입 처리 + * + * FK 처리 방식: + * - XML에 FK 필드가 이미 포함된 경우: XML 값 우선 사용 (예: MATL 인터페이스) + * - XML에 FK 필드가 없는 경우: 상위에서 전달받은 FK 값 사용 (예: VENDOR 인터페이스) + * - 이를 통해 다양한 SAP 인터페이스 패턴에 대응 + * + * @param xmlData XML에서 파싱된 데이터 + * @param fkData 상위 테이블에서 전달받은 FK 데이터 + * @returns DB 삽입 가능한 형태로 변환된 데이터 + */ export function convertXMLToDBData<T extends Record<string, unknown>>( xmlData: Record<string, string | undefined>, - requiredFields: (keyof T)[] = [], fkData?: Record<string, string> ): T { const result = {} as T; @@ -141,20 +162,35 @@ export function convertXMLToDBData<T extends Record<string, unknown>>( } } - // 필수 필드 처리 (FK 등) - for (const field of requiredFields) { - if (!result[field] && fkData) { - const fieldStr = String(field); - if (fkData[fieldStr]) { - (result as Record<string, unknown>)[field] = fkData[fieldStr]; + // FK 필드 처리 (XML 우선, 없으면 상위에서 전달받은 값 사용) + if (fkData) { + for (const [key, value] of Object.entries(fkData)) { + // XML에 해당 FK 필드가 없거나 비어있는 경우에만 상위 값 사용 + const existingValue = (result as Record<string, unknown>)[key]; + if (!existingValue || existingValue === null || existingValue === '') { + (result as Record<string, unknown>)[key] = value; } + // XML에 이미 FK 필드가 있고 값이 있는 경우는 XML 값을 그대로 사용 } } return result; } -// 중첩 배열 처리 함수 +// 중첩 배열 처리 함수 (개선된 버전) +/** + * 중첩된 배열 데이터를 처리하여 DB 삽입 가능한 형태로 변환 + * + * 처리 방식: + * - 하위 테이블 데이터는 FK만 설정하면 됨 + * - 별도의 필수 필드 생성 로직 불필요 + * - 전체 데이터셋 기반으로 삭제 후 재삽입 처리 + * + * @param items 처리할 배열 데이터 + * @param converter 변환 함수 + * @param fkData FK 데이터 + * @returns 변환된 배열 데이터 + */ export function processNestedArray<T, U>( items: T[] | undefined, converter: (item: T, fkData?: Record<string, string>) => U, @@ -207,9 +243,27 @@ export function createSuccessResponse(namespace: string): NextResponse { } // 하위 테이블 처리: FK 기준으로 전체 삭제 후 재삽입 -export async function replaceSubTableData<T>( - tx: any, - table: any, +/** + * 하위 테이블 데이터를 전체 삭제 후 재삽입하는 함수 + * + * 처리 전략: + * - 송신 XML이 전체 데이터셋을 포함한다는 가정하에 설계 + * - 부분 업데이트보다 전체 교체를 통해 데이터 일관성 확보 + * - FK 기준으로 해당 부모 레코드의 모든 하위 데이터 교체 + * + * 처리 순서: + * 1. FK 기준으로 기존 데이터 전체 삭제 + * 2. 새로운 데이터 전체 삽입 + * + * @param tx 트랜잭션 객체 + * @param table 대상 테이블 스키마 + * @param data 삽입할 데이터 배열 + * @param parentField FK 필드명 (일반적으로 'VNDRCD') + * @param parentValue FK 값 (상위 테이블의 unique 필드 값) + */ +export async function replaceSubTableData<T extends Record<string, unknown>>( + tx: Parameters<Parameters<typeof db.transaction>[0]>[0], + table: any, // Drizzle 테이블 객체 - 복잡한 제네릭 타입으로 인해 any 사용 data: T[], parentField: string, parentValue: string |
