diff options
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.ts | 99 |
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; } } |
