summaryrefslogtreecommitdiff
path: root/app/api/(S-ERP)/(MDG)/utils.ts
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-07-01 10:14:51 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-07-01 10:14:51 +0000
commit91d0d3f73002d033a2f7869f39c6b7c832c8588f (patch)
treed7570c11b7e9eda8167d36ea08302ff428731c14 /app/api/(S-ERP)/(MDG)/utils.ts
parent933589898bb26c1585b0585407e935f10e3be2ab (diff)
parent1092e6cbcafd69e236f9a57e2f18987be5764bd5 (diff)
(merge) merged local commits
Diffstat (limited to 'app/api/(S-ERP)/(MDG)/utils.ts')
-rw-r--r--app/api/(S-ERP)/(MDG)/utils.ts88
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