summaryrefslogtreecommitdiff
path: root/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts
diff options
context:
space:
mode:
Diffstat (limited to 'app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts')
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts99
1 files changed, 63 insertions, 36 deletions
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts
index e257a28a..d59246c2 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts
@@ -172,6 +172,25 @@ export async function POST(request: NextRequest) {
}
// XML 데이터를 DB 삽입 가능한 형태로 변환
+/**
+ * VENDOR 마스터 데이터 변환 함수
+ *
+ * 데이터 처리 아키텍처:
+ * 1. 최상위 테이블 (VENDOR_MASTER_BP_HEADER)
+ * - VNDRCD가 unique 필드로 충돌 시 upsert 처리
+ *
+ * 2. 하위 테이블들 (ADDRESS, BP_TAXNUM, BP_VENGEN 등)
+ * - FK(VNDRCD)로 연결
+ * - 별도 필수 필드 없음 (스키마에서 notNull() 제거 예정)
+ * - 전체 데이터셋 기반 삭제 후 재삽입 처리
+ *
+ * 3. 중첩 하위 테이블들 (AD_EMAIL, AD_FAX, BP_COMPNY 등)
+ * - 동일하게 FK(VNDRCD)로 연결
+ * - 전체 데이터셋 기반 처리
+ *
+ * @param vendorHeaderData XML에서 파싱된 VENDOR 헤더 데이터
+ * @returns 처리된 VENDOR 데이터 구조
+ */
function transformVendorData(vendorHeaderData: VendorHeaderXML[]): ProcessedVendorData[] {
if (!vendorHeaderData || !Array.isArray(vendorHeaderData)) {
return [];
@@ -181,84 +200,80 @@ function transformVendorData(vendorHeaderData: VendorHeaderXML[]): ProcessedVend
const vndrcdKey = vendorHeader.VNDRCD || '';
const fkData = { VNDRCD: vndrcdKey };
- // 1단계: VENDOR_HEADER (루트)
+ // 1단계: VENDOR_HEADER (루트 - unique 필드: VNDRCD)
const vendorHeaderConverted = convertXMLToDBData<VendorHeaderData>(
vendorHeader as Record<string, string | undefined>,
- ['VNDRCD'],
fkData
);
- // 2단계: ADDRESS와 직속 하위들
+ // 2단계: 직속 하위 테이블들 (FK: VNDRCD)
const addresses = processNestedArray(
vendorHeader.ADDRESS,
- (addr) => convertXMLToDBData<AddressData>(addr as Record<string, string | undefined>, ['ADR_NO'], fkData),
+ (addr) => convertXMLToDBData<AddressData>(addr as Record<string, string | undefined>, fkData),
fkData
);
- // ADDRESS의 하위 테이블들 (3단계)
+ const bpTaxnums = processNestedArray(
+ vendorHeader.BP_TAXNUM,
+ (item) => convertXMLToDBData<BpTaxnumData>(item as Record<string, string | undefined>, fkData),
+ fkData
+ );
+
+ const bpVengens = processNestedArray(
+ vendorHeader.BP_VENGEN,
+ (vengen) => convertXMLToDBData<BpVengenData>(vengen as Record<string, string | undefined>, fkData),
+ fkData
+ );
+
+ // 3단계: ADDRESS의 하위 테이블들 (FK: VNDRCD)
const adEmails = vendorHeader.ADDRESS?.flatMap(addr =>
processNestedArray(addr.AD_EMAIL, (item) =>
- convertXMLToDBData<AdEmailData>(item as Record<string, string | undefined>, ['REPR_SER', 'VLD_ST_DT'], fkData), fkData)
+ convertXMLToDBData<AdEmailData>(item as Record<string, string | undefined>, fkData), fkData)
) || [];
const adFaxes = vendorHeader.ADDRESS?.flatMap(addr =>
processNestedArray(addr.AD_FAX, (item) =>
- convertXMLToDBData<AdFaxData>(item as Record<string, string | undefined>, ['REPR_SER', 'VLD_ST_DT'], fkData), fkData)
+ convertXMLToDBData<AdFaxData>(item as Record<string, string | undefined>, fkData), fkData)
) || [];
const adPostals = vendorHeader.ADDRESS?.flatMap(addr =>
processNestedArray(addr.AD_POSTAL, (item) =>
- convertXMLToDBData<AdPostalData>(item as Record<string, string | undefined>, ['INTL_ADR_VER_ID'], fkData), fkData)
+ convertXMLToDBData<AdPostalData>(item as Record<string, string | undefined>, fkData), fkData)
) || [];
const adTels = vendorHeader.ADDRESS?.flatMap(addr =>
processNestedArray(addr.AD_TEL, (item) =>
- convertXMLToDBData<AdTelData>(item as Record<string, string | undefined>, ['REPR_SER', 'VLD_ST_DT'], fkData), fkData)
+ convertXMLToDBData<AdTelData>(item as Record<string, string | undefined>, fkData), fkData)
) || [];
const adUrls = vendorHeader.ADDRESS?.flatMap(addr =>
processNestedArray(addr.AD_URL, (item) =>
- convertXMLToDBData<AdUrlData>(item as Record<string, string | undefined>, ['REPR_SER', 'VLD_ST_DT'], fkData), fkData)
+ convertXMLToDBData<AdUrlData>(item as Record<string, string | undefined>, fkData), fkData)
) || [];
- // 2단계: BP_TAXNUM
- const bpTaxnums = processNestedArray(
- vendorHeader.BP_TAXNUM,
- (item) => convertXMLToDBData<BpTaxnumData>(item as Record<string, string | undefined>, ['TX_NO_CTG'], fkData),
- fkData
- );
-
- // 2단계: BP_VENGEN과 하위들
- const bpVengens = processNestedArray(
- vendorHeader.BP_VENGEN,
- (vengen) => convertXMLToDBData<BpVengenData>(vengen as Record<string, string | undefined>, ['VNDRNO'], fkData),
- fkData
- );
-
- // BP_VENGEN의 하위 테이블들 (3단계)
+ // 3단계: BP_VENGEN의 하위 테이블들 (FK: VNDRCD)
const bpCompnies = vendorHeader.BP_VENGEN?.flatMap(vengen =>
processNestedArray(vengen.BP_COMPNY, (item) =>
- convertXMLToDBData<BpCompnyData>(item as Record<string, string | undefined>, ['CO_CD'], fkData), fkData)
+ convertXMLToDBData<BpCompnyData>(item as Record<string, string | undefined>, fkData), fkData)
) || [];
const bpPorgs = vendorHeader.BP_VENGEN?.flatMap(vengen =>
processNestedArray(vengen.BP_PORG, (item) =>
- convertXMLToDBData<BpPorgData>(item as Record<string, string | undefined>, ['PUR_ORG_CD'], fkData), fkData)
+ convertXMLToDBData<BpPorgData>(item as Record<string, string | undefined>, fkData), fkData)
) || [];
- // BP_COMPNY의 하위 테이블 (4단계)
+ // 4단계: 더 깊은 중첩 테이블들 (FK: VNDRCD)
const bpWhtaxes = vendorHeader.BP_VENGEN?.flatMap(vengen =>
vengen.BP_COMPNY?.flatMap(compny =>
processNestedArray(compny.BP_WHTAX, (item) =>
- convertXMLToDBData<BpWhtaxData>(item as Record<string, string | undefined>, ['SRCE_TX_TP'], fkData), fkData)
+ convertXMLToDBData<BpWhtaxData>(item as Record<string, string | undefined>, fkData), fkData)
) || []
) || [];
- // BP_PORG의 하위 테이블 (4단계)
const zvpfns = vendorHeader.BP_VENGEN?.flatMap(vengen =>
vengen.BP_PORG?.flatMap(porg =>
processNestedArray(porg.ZVPFN, (item) =>
- convertXMLToDBData<ZvpfnData>(item as Record<string, string | undefined>, ['PTNR_SKL', 'PTNR_CNT'], fkData), fkData)
+ convertXMLToDBData<ZvpfnData>(item as Record<string, string | undefined>, fkData), fkData)
) || []
) || [];
@@ -281,6 +296,17 @@ function transformVendorData(vendorHeaderData: VendorHeaderXML[]): ProcessedVend
}
// 데이터베이스 저장 함수
+/**
+ * 처리된 VENDOR 데이터를 데이터베이스에 저장
+ *
+ * 저장 전략:
+ * 1. 최상위 테이블: VNDRCD 기준 upsert (충돌 시 업데이트)
+ * 2. 하위 테이블들: FK(VNDRCD) 기준 전체 삭제 후 재삽입
+ * - 송신 XML이 전체 데이터셋을 포함하므로 부분 업데이트 불필요
+ * - 데이터 일관성과 단순성 확보
+ *
+ * @param processedVendors 변환된 VENDOR 데이터 배열
+ */
async function saveToDatabase(processedVendors: ProcessedVendorData[]) {
console.log(`데이터베이스 저장 시작: ${processedVendors.length}개 벤더 데이터`);
@@ -295,7 +321,7 @@ async function saveToDatabase(processedVendors: ProcessedVendorData[]) {
continue;
}
- // 1. BP_HEADER 테이블 Upsert (최상위 테이블)
+ // 1. BP_HEADER 테이블 Upsert (최상위 테이블 - unique 필드: VNDRCD)
await tx.insert(VENDOR_MASTER_BP_HEADER)
.values(vendorHeader)
.onConflictDoUpdate({
@@ -306,14 +332,15 @@ async function saveToDatabase(processedVendors: ProcessedVendorData[]) {
}
});
- // 2. 하위 테이블들 처리 - FK 기준으로 전체 삭제 후 재삽입
+ // 2. 하위 테이블들 처리 - FK(VNDRCD) 기준 전체 삭제 후 재삽입
+ // 전체 데이터셋 기반 처리로 데이터 일관성 확보
await Promise.all([
// 2단계 테이블들
replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS, addresses, 'VNDRCD', vendorHeader.VNDRCD),
replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_TAXNUM, bpTaxnums, 'VNDRCD', vendorHeader.VNDRCD),
replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN, bpVengens, 'VNDRCD', vendorHeader.VNDRCD),
- // 3-4단계 테이블들
+ // 3-4단계 테이블들 - 동일하게 FK(VNDRCD) 기준 처리
replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_EMAIL, adEmails, 'VNDRCD', vendorHeader.VNDRCD),
replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_FAX, adFaxes, 'VNDRCD', vendorHeader.VNDRCD),
replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_POSTAL, adPostals, 'VNDRCD', vendorHeader.VNDRCD),
@@ -327,10 +354,10 @@ async function saveToDatabase(processedVendors: ProcessedVendorData[]) {
}
});
- console.log(`✅ 데이터베이스 저장 완료: ${processedVendors.length}개 벤더`);
+ console.log(`데이터베이스 저장 완료: ${processedVendors.length}개 벤더`);
return true;
} catch (error) {
- console.error('❌ 데이터베이스 저장 중 오류 발생:', error);
+ console.error('데이터베이스 저장 중 오류 발생:', error);
throw error;
}
}