summaryrefslogtreecommitdiff
path: root/app/api/(S-ERP)
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-07-31 09:34:29 +0000
committerjoonhoekim <26rote@gmail.com>2025-07-31 09:34:29 +0000
commit0728ce2e0c085b8f1e8699bcdbe3d2000208bc74 (patch)
tree3b6299d082314d55065e16a1cf09a0abf2118088 /app/api/(S-ERP)
parent10f90dc68dec42e9a64e081cc0dce6a484447290 (diff)
(김준회) MDG 쿼리 배치처리 도입 (네트워크 왕복 오버헤드 해결 목적), 마이그레이션간 DELETE 제거, 환경변수 정리
Diffstat (limited to 'app/api/(S-ERP)')
-rw-r--r--app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts2
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts90
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts54
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts122
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts44
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts62
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts67
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts36
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts59
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts117
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts36
-rw-r--r--app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_VENDOR_MASTER/route.ts90
12 files changed, 375 insertions, 404 deletions
diff --git a/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts
index 3b7636f9..31b61ffc 100644
--- a/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts
+++ b/app/api/(S-ERP)/(ECC)/IF_ECC_EVCP_PR_INFORMATION/route.ts
@@ -13,7 +13,7 @@ import {
createSoapResponse,
replaceSubTableData,
withSoapLogging,
-} from '@/lib/soap/mdg/utils';
+} from '@/lib/soap/utils';
import {
PR_INFORMATION_T_BID_HEADER,
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts
index 9d08527b..0cedcade 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_CUSTOMER_MASTER/route.ts
@@ -25,9 +25,12 @@ import {
processNestedArray,
createErrorResponse,
createSuccessResponse,
- replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
// 스키마에서 직접 타입 추론
type BpHeaderData = typeof CUSTOMER_MASTER_BP_HEADER.$inferInsert;
@@ -316,54 +319,53 @@ function transformCustomerData(bpHeaderData: BpHeaderXML[]): ProcessedCustomerDa
* @param processedCustomers 변환된 CUSTOMER 데이터 배열
*/
async function saveToDatabase(processedCustomers: ProcessedCustomerData[]) {
- console.log(`데이터베이스 저장 시작: ${processedCustomers.length}개 고객 데이터`);
-
+ console.log(`데이터베이스(배치) 저장 시작: ${processedCustomers.length}개 고객 데이터`);
try {
await db.transaction(async (tx) => {
- for (const customerData of processedCustomers) {
- const { bpHeader, addresses, adEmails, adFaxes, adPostals, adTels, adUrls,
- bpCusgens, zvatregs, ztaxinds, zcompanies, zsales, zcpfns, bpTaxnums } = customerData;
-
- if (!bpHeader.BP_HEADER) {
- console.warn('BP_HEADER가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ // 1) 부모 테이블 데이터 준비
+ const bpHeaderRows = processedCustomers
+ .map((c) => c.bpHeader)
+ .filter((h): h is BpHeaderData => !!h.BP_HEADER);
- // 1. BP_HEADER 테이블 Upsert (최상위 테이블 - unique 필드: BP_HEADER)
- await tx.insert(CUSTOMER_MASTER_BP_HEADER)
- .values(bpHeader)
- .onConflictDoUpdate({
- target: CUSTOMER_MASTER_BP_HEADER.BP_HEADER,
- set: {
- ...bpHeader,
- updatedAt: new Date(),
- }
- });
+ const bpHeaderKeys = bpHeaderRows.map((h) => h.BP_HEADER as string);
- // 2. 하위 테이블들 처리 - FK(BP_HEADER) 기준 전체 삭제 후 재삽입
- // 전체 데이터셋 기반 처리로 데이터 일관성 확보
- await Promise.all([
- // 2단계 테이블들
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS, addresses, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN, bpCusgens, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_TAXNUM, bpTaxnums, 'BP_HEADER', bpHeader.BP_HEADER),
-
- // 3-4단계 테이블들 - 동일하게 FK(BP_HEADER) 기준 처리
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_EMAIL, adEmails, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_FAX, adFaxes, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_POSTAL, adPostals, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_TEL, adTels, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_URL, adUrls, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZVATREG, zvatregs, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZTAXIND, ztaxinds, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZCOMPANY, zcompanies, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZSALES, zsales, 'BP_HEADER', bpHeader.BP_HEADER),
- replaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZSALES_ZCPFN, zcpfns, 'BP_HEADER', bpHeader.BP_HEADER),
- ]);
- }
+ // 2) 하위 테이블 데이터 평탄화
+ const addresses = processedCustomers.flatMap((c) => c.addresses);
+ const adEmails = processedCustomers.flatMap((c) => c.adEmails);
+ const adFaxes = processedCustomers.flatMap((c) => c.adFaxes);
+ const adPostals = processedCustomers.flatMap((c) => c.adPostals);
+ const adTels = processedCustomers.flatMap((c) => c.adTels);
+ const adUrls = processedCustomers.flatMap((c) => c.adUrls);
+ const bpCusgens = processedCustomers.flatMap((c) => c.bpCusgens);
+ const zvatregs = processedCustomers.flatMap((c) => c.zvatregs);
+ const ztaxinds = processedCustomers.flatMap((c) => c.ztaxinds);
+ const zcompanies = processedCustomers.flatMap((c) => c.zcompanies);
+ const zsales = processedCustomers.flatMap((c) => c.zsales);
+ const zcpfns = processedCustomers.flatMap((c) => c.zcpfns);
+ const bpTaxnums = processedCustomers.flatMap((c) => c.bpTaxnums);
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, CUSTOMER_MASTER_BP_HEADER, bpHeaderRows, 'BP_HEADER');
+
+ // 4) 하위 테이블 교체 (배치)
+ await Promise.all([
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS, addresses, CUSTOMER_MASTER_BP_HEADER_ADDRESS.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_EMAIL, adEmails, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_EMAIL.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_FAX, adFaxes, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_FAX.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_POSTAL, adPostals, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_POSTAL.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_TEL, adTels, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_TEL.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_URL, adUrls, CUSTOMER_MASTER_BP_HEADER_ADDRESS_AD_URL.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN, bpCusgens, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZVATREG, zvatregs, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZVATREG.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZTAXIND, ztaxinds, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZTAXIND.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZCOMPANY, zcompanies, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZCOMPANY.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZSALES, zsales, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZSALES.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZSALES_ZCPFN, zcpfns, CUSTOMER_MASTER_BP_HEADER_BP_CUSGEN_ZSALES_ZCPFN.BP_HEADER, bpHeaderKeys),
+ bulkReplaceSubTableData(tx, CUSTOMER_MASTER_BP_HEADER_BP_TAXNUM, bpTaxnums, CUSTOMER_MASTER_BP_HEADER_BP_TAXNUM.BP_HEADER, bpHeaderKeys),
+ ]);
});
- console.log(`데이터베이스 저장 완료: ${processedCustomers.length}개 고객`);
+ console.log(`데이터베이스(배치) 저장 완료: ${processedCustomers.length}개 고객`);
return true;
} catch (error) {
console.error('데이터베이스 저장 중 오류 발생:', error);
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts
index 28757fb5..fb54dff3 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_DEPARTMENT_CODE/route.ts
@@ -16,9 +16,12 @@ import {
processNestedArray,
createErrorResponse,
createSuccessResponse,
- replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
// 스키마에서 직접 타입 추론
type DeptData = typeof DEPARTMENT_CODE_CMCTB_DEPT_MDG.$inferInsert;
@@ -187,40 +190,29 @@ function transformDepartmentData(deptData: DeptXML[]): ProcessedDepartmentData[]
* @param processedDepts 변환된 DEPARTMENT 데이터 배열
*/
async function saveToDatabase(processedDepts: ProcessedDepartmentData[]) {
- console.log(`데이터베이스 저장 시작: ${processedDepts.length}개 부서 데이터`);
-
+ console.log(`데이터베이스(배치) 저장 시작: ${processedDepts.length}개 부서 데이터`);
try {
await db.transaction(async (tx) => {
- for (const deptData of processedDepts) {
- const { dept, deptnms, compnms, corpnms } = deptData;
-
- if (!dept.DEPTCD) {
- console.warn('부서코드(DEPTCD)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ const deptRows = processedDepts
+ .map((d) => d.dept)
+ .filter((d): d is DeptData => !!d.DEPTCD);
- // 1. Department 테이블 Upsert (최상위 테이블 - unique 필드: DEPTCD)
- await tx.insert(DEPARTMENT_CODE_CMCTB_DEPT_MDG)
- .values(dept)
- .onConflictDoUpdate({
- target: DEPARTMENT_CODE_CMCTB_DEPT_MDG.DEPTCD,
- set: {
- ...dept,
- updatedAt: new Date(),
- }
- });
-
- // 2. 하위 테이블들 처리 - FK(DEPTCD) 기준 전체 삭제 후 재삽입
- // 전체 데이터셋 기반 처리로 데이터 일관성 확보
- await Promise.all([
- replaceSubTableData(tx, DEPARTMENT_CODE_CMCTB_DEPT_MDG_DEPTNM, deptnms, 'DEPTCD', dept.DEPTCD),
- replaceSubTableData(tx, DEPARTMENT_CODE_CMCTB_DEPT_MDG_COMPNM, compnms, 'DEPTCD', dept.DEPTCD),
- replaceSubTableData(tx, DEPARTMENT_CODE_CMCTB_DEPT_MDG_CORPNM, corpnms, 'DEPTCD', dept.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}개 부서`);
+ console.log(`데이터베이스(배치) 저장 완료: ${processedDepts.length}개 부서`);
return true;
} catch (error) {
console.error('데이터베이스 저장 중 오류 발생:', error);
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts
index fc6bc71f..388c4dc4 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_MASTER/route.ts
@@ -41,7 +41,12 @@ import {
createSuccessResponse,
replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
+
// 스키마에서 직접 타입 추론
type EmpMdgData = typeof EMPLOYEE_MASTER_CMCTB_EMP_MDG.$inferInsert;
@@ -310,65 +315,76 @@ function transformEmployeeData(empData: EmpMdgXML[]): ProcessedEmployeeData[] {
* @param processedEmployees 변환된 EMPLOYEE 데이터 배열
*/
async function saveToDatabase(processedEmployees: ProcessedEmployeeData[]) {
- console.log(`데이터베이스 저장 시작: ${processedEmployees.length}개 사원 데이터`);
+ console.log(`데이터베이스(배치) 저장 시작: ${processedEmployees.length}개 사원 데이터`);
try {
await db.transaction(async (tx) => {
- for (const employeeData of processedEmployees) {
- const { employee, banm, binm, compnm, corpnm, countrynm, deptcode, deptcodePccdnm,
- deptnm, dhjobgdnm, gjobdutynm, gjobgrdnm, gjobgrdtype, gjobnm, gnnm,
- jobdutynm, jobgrdnm, jobnm, ktlnm, oktlnm, orgbicdnm, orgcompnm,
- orgcorpnm, orgdeptnm, orgpdepnm, pdeptnm } = employeeData;
-
- if (!employee.EMPID) {
- console.warn('사원번호(EMPID)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ // 1) 부모 테이블 데이터 준비
+ const employeeRows = processedEmployees
+ .map((e) => e.employee)
+ .filter((e): e is EmpMdgData => !!e.EMPID);
- // 1. CMCTB_EMP_MDG 테이블 Upsert (최상위 테이블 - unique 필드: EMPID)
- await tx.insert(EMPLOYEE_MASTER_CMCTB_EMP_MDG)
- .values(employee)
- .onConflictDoUpdate({
- target: EMPLOYEE_MASTER_CMCTB_EMP_MDG.EMPID,
- set: {
- ...employee,
- updatedAt: new Date(),
- }
- });
+ const empids = employeeRows.map((e) => e.EMPID as string);
- // 2. 하위 테이블들 처리 - FK(EMPID) 기준 전체 삭제 후 재삽입
- // 25개 테이블을 병렬 처리로 성능 최적화
- await Promise.all([
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_BANM, banm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_BINM, binm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_COMPNM, compnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_CORPNM, corpnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_COUNTRYNM, countrynm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_DEPTCODE, deptcode, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_DEPTCODE_PCCDNM, deptcodePccdnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_DEPTNM, deptnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_DHJOBGDNM, dhjobgdnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GJOBDUTYNM, gjobdutynm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GJOBGRDNM, gjobgrdnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GJOBGRDTYPE, gjobgrdtype, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GJOBNM, gjobnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GNNM, gnnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_JOBDUTYNM, jobdutynm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_JOBGRDNM, jobgrdnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_JOBNM, jobnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_KTLNM, ktlnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_OKTLNM, oktlnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGBICDNM, orgbicdnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGCOMPNM, orgcompnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGCORPNM, orgcorpnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGDEPTNM, orgdeptnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGPDEPNM, orgpdepnm, 'EMPID', employee.EMPID),
- replaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_PDEPTNM, pdeptnm, 'EMPID', employee.EMPID)
- ]);
- }
+ // 2) 하위 테이블 데이터 평탄화
+ const banm = processedEmployees.flatMap((e) => e.banm);
+ const binm = processedEmployees.flatMap((e) => e.binm);
+ const compnm = processedEmployees.flatMap((e) => e.compnm);
+ const corpnm = processedEmployees.flatMap((e) => e.corpnm);
+ const countrynm = processedEmployees.flatMap((e) => e.countrynm);
+ const deptcode = processedEmployees.flatMap((e) => e.deptcode);
+ const deptcodePccdnm = processedEmployees.flatMap((e) => e.deptcodePccdnm);
+ const deptnm = processedEmployees.flatMap((e) => e.deptnm);
+ const dhjobgdnm = processedEmployees.flatMap((e) => e.dhjobgdnm);
+ const gjobdutynm = processedEmployees.flatMap((e) => e.gjobdutynm);
+ const gjobgrdnm = processedEmployees.flatMap((e) => e.gjobgrdnm);
+ const gjobgrdtype = processedEmployees.flatMap((e) => e.gjobgrdtype);
+ const gjobnm = processedEmployees.flatMap((e) => e.gjobnm);
+ const gnnm = processedEmployees.flatMap((e) => e.gnnm);
+ const jobdutynm = processedEmployees.flatMap((e) => e.jobdutynm);
+ const jobgrdnm = processedEmployees.flatMap((e) => e.jobgrdnm);
+ const jobnm = processedEmployees.flatMap((e) => e.jobnm);
+ const ktlnm = processedEmployees.flatMap((e) => e.ktlnm);
+ const oktlnm = processedEmployees.flatMap((e) => e.oktlnm);
+ const orgbicdnm = processedEmployees.flatMap((e) => e.orgbicdnm);
+ const orgcompnm = processedEmployees.flatMap((e) => e.orgcompnm);
+ const orgcorpnm = processedEmployees.flatMap((e) => e.orgcorpnm);
+ const orgdeptnm = processedEmployees.flatMap((e) => e.orgdeptnm);
+ const orgpdepnm = processedEmployees.flatMap((e) => e.orgpdepnm);
+ const pdeptnm = processedEmployees.flatMap((e) => e.pdeptnm);
+
+ // 3) 데이터베이스 저장
+ await bulkUpsert(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG, employeeRows, 'EMPID');
+
+ // 4) 하위 테이블 데이터 저장
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_BANM, banm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_BINM, binm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_COMPNM, compnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_CORPNM, corpnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_COUNTRYNM, countrynm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_DEPTCODE, deptcode, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_DEPTCODE_PCCDNM, deptcodePccdnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_DEPTNM, deptnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_DHJOBGDNM, dhjobgdnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GJOBDUTYNM, gjobdutynm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GJOBGRDNM, gjobgrdnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GJOBGRDTYPE, gjobgrdtype, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GJOBNM, gjobnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_GNNM, gnnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_JOBDUTYNM, jobdutynm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_JOBGRDNM, jobgrdnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_JOBNM, jobnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_KTLNM, ktlnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_OKTLNM, oktlnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGBICDNM, orgbicdnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGCOMPNM, orgcompnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGCORPNM, orgcorpnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGDEPTNM, orgdeptnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_ORGPDEPNM, orgpdepnm, 'EMPID', empids);
+ await bulkReplaceSubTableData(tx, EMPLOYEE_MASTER_CMCTB_EMP_MDG_PDEPTNM, pdeptnm, 'EMPID', empids);
});
- console.log(`데이터베이스 저장 완료: ${processedEmployees.length}개 사원`);
+ console.log(`데이터베이스(배치) 저장 완료: ${processedEmployees.length}개 사원`);
return true;
} catch (error) {
console.error('데이터베이스 저장 중 오류 발생:', error);
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts
index 22f151b3..563696d3 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EMPLOYEE_REFERENCE_MASTER/route.ts
@@ -16,7 +16,11 @@ import {
createSuccessResponse,
replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
// 스키마에서 직접 타입 추론
type EmpRefData = typeof EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF.$inferInsert;
@@ -160,33 +164,25 @@ function transformEmpRefData(empRefData: EmpRefXML[]): ProcessedEmployeeReferenc
* @param processedEmpRefs 변환된 EMPLOYEE_REFERENCE 데이터 배열
*/
async function saveToDatabase(processedEmpRefs: ProcessedEmployeeReferenceData[]) {
- console.log(`데이터베이스 저장 시작: ${processedEmpRefs.length}개 직원 참조 데이터`);
+ console.log(`데이터베이스(배치) 저장 시작: ${processedEmpRefs.length}개 직원 참조 데이터`);
try {
await db.transaction(async (tx) => {
- for (const empRefData of processedEmpRefs) {
- const { empRef, names } = empRefData;
-
- if (!empRef.GRPCD) {
- console.warn('그룹코드(GRPCD)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ // 1) 부모 테이블 데이터 준비
+ const empRefRows = processedEmpRefs
+ .map((e) => e.empRef)
+ .filter((e): e is EmpRefData => !!e.GRPCD);
- // 1. Employee Reference 테이블 Upsert (최상위 테이블 - unique 필드: GRPCD)
- await tx.insert(EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF)
- .values(empRef)
- .onConflictDoUpdate({
- target: EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF.GRPCD,
- set: {
- ...empRef,
- updatedAt: new Date(),
- }
- });
-
- // 2. 하위 테이블 처리 - FK(GRPCD) 기준 전체 삭제 후 재삽입
- // 전체 데이터셋 기반 처리로 데이터 일관성 확보
- await replaceSubTableData(tx, EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF_NAME, names, 'GRPCD', empRef.GRPCD);
- }
+ const grpcds = empRefRows.map((e) => e.GRPCD as string);
+
+ // 2) 하위 테이블 데이터 평탄화
+ const names = processedEmpRefs.flatMap((e) => e.names);
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF, empRefRows, 'GRPCD');
+
+ // 4) 하위 테이블 교체 (배치)
+ await bulkReplaceSubTableData(tx, EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF_NAME, names, EMPLOYEE_REFERENCE_MASTER_CMCTB_EMP_REF_MDG_IF_NAME.GRPCD, grpcds);
});
console.log(`데이터베이스 저장 완료: ${processedEmpRefs.length}개 직원 참조`);
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts
index cd1005e7..5544dfdb 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_EQUP_MASTER/route.ts
@@ -18,9 +18,12 @@ import {
processNestedArray,
createErrorResponse,
createSuccessResponse,
- replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
// 스키마에서 직접 타입 추론 (Insert와 XML을 통합)
type MatlData = typeof EQUP_MASTER_MATL.$inferInsert;
@@ -212,42 +215,37 @@ function transformMatlData(matlData: MatlXML[]): ProcessedMaterialData[] {
* @param processedMaterials 변환된 EQUP 데이터 배열
*/
async function saveToDatabase(processedMaterials: ProcessedMaterialData[]) {
- console.log(`데이터베이스 저장 시작: ${processedMaterials.length}개 장비 데이터`);
-
+ console.log(`데이터베이스(배치) 저장 시작: ${processedMaterials.length}개 장비 데이터`);
try {
await db.transaction(async (tx) => {
- for (const materialData of processedMaterials) {
- const { material, descriptions, plants, units, classAssignments, characteristicAssignments } = materialData;
-
- if (!material.MATNR) {
- console.warn('자재번호(MATNR)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ // 1) 부모 테이블 데이터 준비
+ const materialRows = processedMaterials
+ .map((m) => m.material)
+ .filter((m): m is MatlData => !!m.MATNR);
- // 1. MATL 테이블 Upsert (최상위 테이블 - unique 필드: MATNR)
- await tx.insert(EQUP_MASTER_MATL)
- .values(material)
- .onConflictDoUpdate({
- target: EQUP_MASTER_MATL.MATNR,
- set: {
- ...material,
- updatedAt: new Date(),
- }
- });
+ const matnrs = materialRows.map((m) => m.MATNR as string);
- // 2. 하위 테이블들 처리 - FK(MATNR) 기준 전체 삭제 후 재삽입
- // 전체 데이터셋 기반 처리로 데이터 일관성 확보
- await Promise.all([
- replaceSubTableData(tx, EQUP_MASTER_MATL_DESC, descriptions, 'MATNR', material.MATNR),
- replaceSubTableData(tx, EQUP_MASTER_MATL_PLNT, plants, 'MATNR', material.MATNR),
- replaceSubTableData(tx, EQUP_MASTER_MATL_UNIT, units, 'MATNR', material.MATNR),
- replaceSubTableData(tx, EQUP_MASTER_MATL_CLASSASGN, classAssignments, 'MATNR', material.MATNR),
- replaceSubTableData(tx, EQUP_MASTER_MATL_CHARASGN, characteristicAssignments, 'MATNR', material.MATNR)
- ]);
- }
+ // 2) 하위 테이블 데이터 평탄화
+ const descriptions = processedMaterials.flatMap((m) => m.descriptions);
+ const plants = processedMaterials.flatMap((m) => m.plants);
+ const units = processedMaterials.flatMap((m) => m.units);
+ const classAssignments = processedMaterials.flatMap((m) => m.classAssignments);
+ const characteristicAssignments = processedMaterials.flatMap((m) => m.characteristicAssignments);
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, EQUP_MASTER_MATL, materialRows, 'MATNR');
+
+ // 4) 하위 테이블 교체 (배치)
+ await Promise.all([
+ bulkReplaceSubTableData(tx, EQUP_MASTER_MATL_DESC, descriptions, EQUP_MASTER_MATL_DESC.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, EQUP_MASTER_MATL_PLNT, plants, EQUP_MASTER_MATL_PLNT.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, EQUP_MASTER_MATL_UNIT, units, EQUP_MASTER_MATL_UNIT.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, EQUP_MASTER_MATL_CLASSASGN, classAssignments, EQUP_MASTER_MATL_CLASSASGN.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, EQUP_MASTER_MATL_CHARASGN, characteristicAssignments, EQUP_MASTER_MATL_CHARASGN.MATNR, matnrs),
+ ]);
});
- console.log(`데이터베이스 저장 완료: ${processedMaterials.length}개 장비`);
+ console.log(`데이터베이스(배치) 저장 완료: ${processedMaterials.length}개 장비`);
return true;
} catch (error) {
console.error('데이터베이스 저장 중 오류 발생:', error);
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts
index 21063ff7..49aff036 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts
@@ -11,7 +11,6 @@ import {
import {
ToXMLFields,
- SoapBodyData,
serveWsdl,
createXMLParser,
extractRequestData,
@@ -19,9 +18,16 @@ import {
processNestedArray,
createErrorResponse,
createSuccessResponse,
- replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
// 스키마에서 직접 타입 추론 (Insert와 XML을 통합)
type MatlData = typeof MATERIAL_MASTER_PART_MATL.$inferInsert;
@@ -213,42 +219,37 @@ function transformMatlData(matlData: MatlXML[]): ProcessedMaterialData[] {
* @param processedMaterials 변환된 MATERIAL 데이터 배열
*/
async function saveToDatabase(processedMaterials: ProcessedMaterialData[]) {
- console.log(`데이터베이스 저장 시작: ${processedMaterials.length}개 자재 데이터`);
-
+ console.log(`데이터베이스(배치) 저장 시작: ${processedMaterials.length}개 자재`);
try {
await db.transaction(async (tx) => {
- for (const materialData of processedMaterials) {
- const { material, descriptions, plants, units, classAssignments, characteristicAssignments } = materialData;
-
- if (!material.MATNR) {
- console.warn('자재번호(MATNR)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ // 1) 부모 테이블 데이터 준비
+ const materialRows = processedMaterials
+ .map((m) => m.material)
+ .filter((m): m is MatlData => !!m.MATNR);
- // 1. MATL 테이블 Upsert (최상위 테이블 - unique 필드: MATNR)
- await tx.insert(MATERIAL_MASTER_PART_MATL)
- .values(material)
- .onConflictDoUpdate({
- target: MATERIAL_MASTER_PART_MATL.MATNR,
- set: {
- ...material,
- updatedAt: new Date(),
- }
- });
+ const matnrs = materialRows.map((m) => m.MATNR as string);
- // 2. 하위 테이블들 처리 - FK(MATNR) 기준 전체 삭제 후 재삽입
- // 전체 데이터셋 기반 처리로 데이터 일관성 확보
- await Promise.all([
- replaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_DESC, descriptions, 'MATNR', material.MATNR),
- replaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_PLNT, plants, 'MATNR', material.MATNR),
- replaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_UNIT, units, 'MATNR', material.MATNR),
- replaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_CLASSASGN, classAssignments, 'MATNR', material.MATNR),
- replaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_CHARASGN, characteristicAssignments, 'MATNR', material.MATNR)
- ]);
- }
+ // 2) 하위 테이블 데이터 평탄화
+ const descriptions = processedMaterials.flatMap((m) => m.descriptions);
+ const plants = processedMaterials.flatMap((m) => m.plants);
+ const units = processedMaterials.flatMap((m) => m.units);
+ const classAssignments = processedMaterials.flatMap((m) => m.classAssignments);
+ const characteristicAssignments = processedMaterials.flatMap((m) => m.characteristicAssignments);
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, MATERIAL_MASTER_PART_MATL, materialRows, 'MATNR');
+
+ // 4) 하위 테이블 교체 (배치)
+ await Promise.all([
+ bulkReplaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_DESC, descriptions, MATERIAL_MASTER_PART_MATL_DESC.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_PLNT, plants, MATERIAL_MASTER_PART_MATL_PLNT.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_UNIT, units, MATERIAL_MASTER_PART_MATL_UNIT.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_CLASSASGN, classAssignments, MATERIAL_MASTER_PART_MATL_CLASSASGN.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, MATERIAL_MASTER_PART_MATL_CHARASGN, characteristicAssignments, MATERIAL_MASTER_PART_MATL_CHARASGN.MATNR, matnrs),
+ ]);
});
- console.log(`데이터베이스 저장 완료: ${processedMaterials.length}개 자재`);
+ console.log(`데이터베이스(배치) 저장 완료: ${processedMaterials.length}개 자재`);
return true;
} catch (error) {
console.error('데이터베이스 저장 중 오류 발생:', error);
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts
index 428cd298..e2c97b2e 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART_RETURN/route.ts
@@ -10,7 +10,11 @@ import {
createSuccessResponse,
ToXMLFields,
withSoapLogging,
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
// 스키마에서 직접 타입 추론
type CMCTBMatBseData = typeof MATERIAL_MASTER_PART_RETURN_CMCTB_MAT_BSE.$inferInsert;
@@ -116,29 +120,21 @@ function transformMaterialData(materialData: CMCTBMatBseXML[]): ProcessedMateria
// 데이터베이스 저장 함수
async function saveToDatabase(processedMaterials: ProcessedMaterialData[]) {
- console.log(`데이터베이스 저장 시작: ${processedMaterials.length}개 자재 데이터`);
+ console.log(`데이터베이스(배치) 저장 시작: ${processedMaterials.length}개 자재 데이터`);
try {
await db.transaction(async (tx) => {
- for (const materialData of processedMaterials) {
- const { materialData: material } = materialData;
-
- if (!material.MAT_CD) {
- console.warn('자재코드(MAT_CD)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ // 1) 부모 테이블 데이터 준비
+ const materialRows = processedMaterials
+ .map((m) => m.materialData)
+ .filter((m): m is CMCTBMatBseData => !!m.MAT_CD);
- // MATERIAL_MASTER_PART_RETURN_CMCTB_MAT_BSE 테이블 Upsert
- await tx.insert(MATERIAL_MASTER_PART_RETURN_CMCTB_MAT_BSE)
- .values(material)
- .onConflictDoUpdate({
- target: MATERIAL_MASTER_PART_RETURN_CMCTB_MAT_BSE.MAT_CD,
- set: {
- ...material,
- updatedAt: new Date(),
- }
- });
- }
+ const matcds = materialRows.map((m) => m.MAT_CD as string);
+
+ // 2) 하위 테이블 데이터 평탄화 (하위 테이블 없음)
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, MATERIAL_MASTER_PART_RETURN_CMCTB_MAT_BSE, materialRows, 'MAT_CD');
});
console.log(`✅ 데이터베이스 저장 완료: ${processedMaterials.length}개 자료`);
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts
index 204dffa3..9d76adbb 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MODEL_MASTER/route.ts
@@ -17,9 +17,12 @@ import {
processNestedArray,
createErrorResponse,
createSuccessResponse,
- replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
// 스키마에서 직접 타입 추론 (Insert와 XML을 통합)
type MatlData = typeof MODEL_MASTER_MATL.$inferInsert;
@@ -211,39 +214,35 @@ function transformMatlData(matlData: MatlXML[]): ProcessedMaterialData[] {
* @param processedMaterials 변환된 MODEL 데이터 배열
*/
async function saveToDatabase(processedMaterials: ProcessedMaterialData[]) {
- console.log(`데이터베이스 저장 시작: ${processedMaterials.length}개 모델 데이터`);
+ console.log(`데이터베이스(배치) 저장 시작: ${processedMaterials.length}개 모델 데이터`);
try {
await db.transaction(async (tx) => {
- for (const materialData of processedMaterials) {
- const { material, descriptions, plants, units, classAssignments, characteristicAssignments } = materialData;
-
- if (!material.MATNR) {
- console.warn('자재번호(MATNR)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ // 1) 부모 테이블 데이터 준비
+ const materialRows = processedMaterials
+ .map((m) => m.material)
+ .filter((m): m is MatlData => !!m.MATNR);
- // 1. MATL 테이블 Upsert (최상위 테이블 - unique 필드: MATNR)
- await tx.insert(MODEL_MASTER_MATL)
- .values(material)
- .onConflictDoUpdate({
- target: MODEL_MASTER_MATL.MATNR,
- set: {
- ...material,
- updatedAt: new Date(),
- }
- });
+ const matnrs = materialRows.map((m) => m.MATNR as string);
- // 2. 하위 테이블들 처리 - FK(MATNR) 기준 전체 삭제 후 재삽입
- // 전체 데이터셋 기반 처리로 데이터 일관성 확보
- await Promise.all([
- replaceSubTableData(tx, MODEL_MASTER_MATL_DESC, descriptions, 'MATNR', material.MATNR),
- replaceSubTableData(tx, MODEL_MASTER_MATL_PLNT, plants, 'MATNR', material.MATNR),
- replaceSubTableData(tx, MODEL_MASTER_MATL_UNIT, units, 'MATNR', material.MATNR),
- replaceSubTableData(tx, MODEL_MASTER_MATL_CLASSASGN, classAssignments, 'MATNR', material.MATNR),
- replaceSubTableData(tx, MODEL_MASTER_MATL_CHARASGN, characteristicAssignments, 'MATNR', material.MATNR)
- ]);
- }
+ // 2) 하위 테이블 데이터 평탄화
+ const descriptions = processedMaterials.flatMap((m) => m.descriptions);
+ const plants = processedMaterials.flatMap((m) => m.plants);
+ const units = processedMaterials.flatMap((m) => m.units);
+ const classAssignments = processedMaterials.flatMap((m) => m.classAssignments);
+ const characteristicAssignments = processedMaterials.flatMap((m) => m.characteristicAssignments);
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, MODEL_MASTER_MATL, materialRows, 'MATNR');
+
+ // 4) 하위 테이블 교체 (배치)
+ await Promise.all([
+ bulkReplaceSubTableData(tx, MODEL_MASTER_MATL_DESC, descriptions, MODEL_MASTER_MATL_DESC.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, MODEL_MASTER_MATL_PLNT, plants, MODEL_MASTER_MATL_PLNT.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, MODEL_MASTER_MATL_UNIT, units, MODEL_MASTER_MATL_UNIT.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, MODEL_MASTER_MATL_CLASSASGN, classAssignments, MODEL_MASTER_MATL_CLASSASGN.MATNR, matnrs),
+ bulkReplaceSubTableData(tx, MODEL_MASTER_MATL_CHARASGN, characteristicAssignments, MODEL_MASTER_MATL_CHARASGN.MATNR, matnrs)
+ ]);
});
console.log(`데이터베이스 저장 완료: ${processedMaterials.length}개 모델`);
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts
index 987d4002..3051fd8f 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_ORGANIZATION_MASTER/route.ts
@@ -30,7 +30,12 @@ import {
createSuccessResponse,
replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
+import { coerce } from "zod/v4";
// 스키마에서 직접 타입 추론
type CctrData = typeof ORGANIZATION_MASTER_HRHMTB_CCTR.$inferInsert;
@@ -376,75 +381,49 @@ async function saveToDatabase(processedOrganizations: ProcessedOrganizationData)
try {
await db.transaction(async (tx) => {
- // CCTR 테이블 처리 (unique 필드: CCTR)
- for (const { cctr, texts } of processedOrganizations.cctrItems) {
- if (!cctr.CCTR) continue;
-
- await tx.insert(ORGANIZATION_MASTER_HRHMTB_CCTR)
- .values(cctr)
- .onConflictDoUpdate({
- target: ORGANIZATION_MASTER_HRHMTB_CCTR.CCTR,
- set: { ...cctr, updatedAt: new Date() }
- });
-
- await replaceSubTableData(tx, ORGANIZATION_MASTER_HRHMTB_CCTR_TEXT, texts, 'CCTR', cctr.CCTR);
- }
-
- // PCTR 테이블 처리 (unique 필드: PCTR)
- for (const { pctr, texts } of processedOrganizations.pctrItems) {
- if (!pctr.PCTR) continue;
-
- await tx.insert(ORGANIZATION_MASTER_HRHMTB_PCTR)
- .values(pctr)
- .onConflictDoUpdate({
- target: ORGANIZATION_MASTER_HRHMTB_PCTR.PCTR,
- set: { ...pctr, updatedAt: new Date() }
- });
-
- // PCTR의 TEXT는 CCTR_TEXT 테이블을 사용하므로 처리하지 않음
- }
- // 나머지 단일 테이블들 처리
- const tableProcessors = [
- { items: processedOrganizations.zbukrsItems, table: ORGANIZATION_MASTER_HRHMTB_ZBUKRS, key: 'ZBUKRS' },
- { items: processedOrganizations.zekgrpItems, table: ORGANIZATION_MASTER_HRHMTB_ZEKGRP, key: 'ZEKGRP' },
- { items: processedOrganizations.zekorgItems, table: ORGANIZATION_MASTER_HRHMTB_ZEKORG, key: 'ZEKORG' },
- { items: processedOrganizations.zlgortItems, table: ORGANIZATION_MASTER_HRHMTB_ZLGORT, key: 'ZLGORT' },
- { items: processedOrganizations.zspartItems, table: ORGANIZATION_MASTER_HRHMTB_ZSPART, key: 'ZSPART' },
- { items: processedOrganizations.zvkburItems, table: ORGANIZATION_MASTER_HRHMTB_ZVKBUR, key: 'ZVKBUR' },
- { items: processedOrganizations.zvkgrpItems, table: ORGANIZATION_MASTER_HRHMTB_ZVKGRP, key: 'ZVKGRP' },
- { items: processedOrganizations.zvkorgItems, table: ORGANIZATION_MASTER_HRHMTB_ZVKORG, key: 'ZVKORG' },
- { items: processedOrganizations.zvstelItems, table: ORGANIZATION_MASTER_HRHMTB_ZVSTEL, key: 'ZVSTEL' },
- { items: processedOrganizations.zvtwegItems, table: ORGANIZATION_MASTER_HRHMTB_ZVTWEG, key: 'ZVTWEG' },
- { items: processedOrganizations.zwerksItems, table: ORGANIZATION_MASTER_HRHMTB_ZWERKS, key: 'ZWERKS' }
- ];
-
- for (const { items, table, key } of tableProcessors) {
- for (const item of items) {
- if (!(item as any)[key]) continue;
-
- await tx.insert(table)
- .values(item)
- .onConflictDoUpdate({
- target: (table as any)[key],
- set: { ...item, updatedAt: new Date() }
- });
- }
- }
-
- // ZGSBER 테이블 처리 (TEXT 포함)
- for (const { zgsber, texts } of processedOrganizations.zgsberItems) {
- if (!zgsber.ZGSBER) continue;
-
- await tx.insert(ORGANIZATION_MASTER_HRHMTB_ZGSBER)
- .values(zgsber)
- .onConflictDoUpdate({
- target: ORGANIZATION_MASTER_HRHMTB_ZGSBER.ZGSBER,
- set: { ...zgsber, updatedAt: new Date() }
- });
-
- await replaceSubTableData(tx, ORGANIZATION_MASTER_HRHMTB_ZGSBER_TEXT, texts, 'ZGSBER', zgsber.ZGSBER);
- }
+ // 1) 부모 테이블 데이터 준비 (root)
+ const cctrRows = processedOrganizations.cctrItems.map((c) => c.cctr).filter((c): c is CctrData => !!c.CCTR);
+ const pctrRows = processedOrganizations.pctrItems;
+ const zbukrsRows = processedOrganizations.zbukrsItems;
+ const zekgrpRows = processedOrganizations.zekgrpItems;
+ const zekorgRows = processedOrganizations.zekorgItems;
+ const zgsberRows = processedOrganizations.zgsberItems.map((zgsber) => zgsber.zgsber).filter((zgsber): zgsber is ZgsberData => !!zgsber.ZGSBER);
+ const zlgortRows = processedOrganizations.zlgortItems;
+ const zspartRows = processedOrganizations.zspartItems;
+ const zvkburRows = processedOrganizations.zvkburItems;
+ const zvkgrpRows = processedOrganizations.zvkgrpItems;
+ const zvkorgRows = processedOrganizations.zvkorgItems;
+ const zvstelRows = processedOrganizations.zvstelItems;
+ const zvtwegRows = processedOrganizations.zvtwegItems;
+ const zwerksRows = processedOrganizations.zwerksItems;
+
+ const cctrIds = cctrRows.map((cctr) => cctr.CCTR as string);
+ const zgsberIds = zgsberRows.map((zgsber) => zgsber.ZGSBER as string);
+
+ // 2) 하위 테이블 데이터 평탄화 (2건)
+ const cctrTexts = processedOrganizations.cctrItems.flatMap((cctr) => cctr.texts);
+ const zgsberTexts = processedOrganizations.zgsberItems.flatMap((zgsber) => zgsber.texts);
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_CCTR, cctrRows, 'CCTR');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_PCTR, pctrRows, 'PCTR');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZBUKRS, zbukrsRows, 'ZBUKRS');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZEKGRP, zekgrpRows, 'ZEKGRP');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZEKORG, zekorgRows, 'ZEKORG');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZGSBER, zgsberRows, 'ZGSBER');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZLGORT, zlgortRows, 'ZLGORT');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZSPART, zspartRows, 'ZSPART');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVKBUR, zvkburRows, 'ZVKBUR');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVKGRP, zvkgrpRows, 'ZVKGRP');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVKORG, zvkorgRows, 'ZVKORG');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVSTEL, zvstelRows, 'ZVSTEL');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZVTWEG, zvtwegRows, 'ZVTWEG');
+ await bulkUpsert(tx, ORGANIZATION_MASTER_HRHMTB_ZWERKS, zwerksRows, 'ZWERKS');
+
+ // 4) 하위 테이블 교체 (배치) (2건)
+ await bulkReplaceSubTableData(tx, ORGANIZATION_MASTER_HRHMTB_CCTR_TEXT, cctrTexts, ORGANIZATION_MASTER_HRHMTB_CCTR_TEXT.CCTR, cctrIds);
+ await bulkReplaceSubTableData(tx, ORGANIZATION_MASTER_HRHMTB_ZGSBER_TEXT, zgsberTexts, ORGANIZATION_MASTER_HRHMTB_ZGSBER_TEXT.ZGSBER, zgsberIds);
});
console.log('조직 마스터 데이터 처리 완료.');
diff --git a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts
index 93071c69..c1563859 100644
--- a/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts
+++ b/app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_PROJECT_MASTER/route.ts
@@ -13,7 +13,9 @@ import {
createErrorResponse,
createSuccessResponse,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import { bulkUpsert } from "@/lib/soap/batch-utils"; // 서브테이블 없음
+
// 스키마에서 직접 타입 추론
type ProjectData = typeof PROJECT_MASTER_CMCTB_PROJ_MAST.$inferInsert;
@@ -102,8 +104,7 @@ function transformProjectData(projectData: ProjectXML[]): ProcessedProjectData[]
return projectData.map(proj => {
// Project 데이터 변환
const project = convertXMLToDBData<ProjectData>(
- proj as Record<string, string | undefined>,
- ['PROJ_NO']
+ proj as Record<string, string | undefined>
);
// 필수 필드 보정
@@ -119,32 +120,19 @@ function transformProjectData(projectData: ProjectXML[]): ProcessedProjectData[]
// 데이터베이스 저장 함수
async function saveToDatabase(processedProjects: ProcessedProjectData[]) {
- console.log(`데이터베이스 저장 함수가 호출됨. ${processedProjects.length}개의 프로젝트 데이터 수신.`);
-
+ console.log(`데이터베이스(배치) 저장 시작: ${processedProjects.length}개 프로젝트`);
try {
await db.transaction(async (tx) => {
- for (const projectData of processedProjects) {
- const { project } = projectData;
-
- if (!project.PROJ_NO) {
- console.warn('프로젝트번호(PROJ_NO)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ const projectRows = processedProjects
+ .map((p) => p.project)
+ .filter((p): p is ProjectData => !!p.PROJ_NO);
- // Project 테이블 Upsert
- await tx.insert(PROJECT_MASTER_CMCTB_PROJ_MAST)
- .values(project)
- .onConflictDoUpdate({
- target: PROJECT_MASTER_CMCTB_PROJ_MAST.PROJ_NO,
- set: {
- ...project,
- updatedAt: new Date(),
- }
- });
- }
+ if (!projectRows.length) return;
+
+ await bulkUpsert(tx, PROJECT_MASTER_CMCTB_PROJ_MAST, projectRows, 'PROJ_NO');
});
- console.log(`${processedProjects.length}개의 프로젝트 데이터 처리 완료.`);
+ console.log(`데이터베이스(배치) 저장 완료: ${processedProjects.length}개 프로젝트`);
return true;
} catch (error) {
console.error('데이터베이스 저장 중 오류 발생:', error);
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 75f8cd62..61269937 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
@@ -24,9 +24,12 @@ import {
processNestedArray,
createErrorResponse,
createSuccessResponse,
- replaceSubTableData,
withSoapLogging
-} from "@/lib/soap/mdg/utils";
+} from "@/lib/soap/utils";
+import {
+ bulkUpsert,
+ bulkReplaceSubTableData
+} from "@/lib/soap/batch-utils";
// 스키마에서 직접 타입 추론
type VendorHeaderData = typeof VENDOR_MASTER_BP_HEADER.$inferInsert;
@@ -308,53 +311,54 @@ function transformVendorData(vendorHeaderData: VendorHeaderXML[]): ProcessedVend
* @param processedVendors 변환된 VENDOR 데이터 배열
*/
async function saveToDatabase(processedVendors: ProcessedVendorData[]) {
- console.log(`데이터베이스 저장 시작: ${processedVendors.length}개 벤더 데이터`);
-
+ console.log(`데이터베이스(배치) 저장 시작: ${processedVendors.length}개 벤더 데이터`);
try {
await db.transaction(async (tx) => {
- for (const vendorData of processedVendors) {
- const { vendorHeader, addresses, adEmails, adFaxes, adPostals, adTels, adUrls,
- bpTaxnums, bpVengens, bpCompnies, bpWhtaxes, bpPorgs, zvpfns } = vendorData;
-
- if (!vendorHeader.VNDRCD) {
- console.warn('벤더코드(VNDRCD)가 없는 항목 발견, 건너뜁니다.');
- continue;
- }
+ // 1) 부모 테이블 데이터 준비
+ const vendorHeaderRows = processedVendors
+ .map((v) => v.vendorHeader)
+ .filter((v): v is VendorHeaderData => !!v.VNDRCD);
- // 1. BP_HEADER 테이블 Upsert (최상위 테이블 - unique 필드: VNDRCD)
- await tx.insert(VENDOR_MASTER_BP_HEADER)
- .values(vendorHeader)
- .onConflictDoUpdate({
- target: VENDOR_MASTER_BP_HEADER.VNDRCD,
- set: {
- ...vendorHeader,
- updatedAt: new Date(),
- }
- });
+ const vndrCds = vendorHeaderRows.map((v) => v.VNDRCD as string);
- // 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단계 테이블들 - 동일하게 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),
- replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_TEL, adTels, 'VNDRCD', vendorHeader.VNDRCD),
- replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_URL, adUrls, 'VNDRCD', vendorHeader.VNDRCD),
- replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_COMPNY, bpCompnies, 'VNDRCD', vendorHeader.VNDRCD),
- replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_COMPNY_BP_WHTAX, bpWhtaxes, 'VNDRCD', vendorHeader.VNDRCD),
- replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_PORG, bpPorgs, 'VNDRCD', vendorHeader.VNDRCD),
- replaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_PORG_ZVPFN, zvpfns, 'VNDRCD', vendorHeader.VNDRCD),
- ]);
- }
+ // 2) 하위 테이블 데이터 평탄화
+ const addresses = processedVendors.flatMap((v) => v.addresses);
+ const adEmails = processedVendors.flatMap((v) => v.adEmails);
+ const adFaxes = processedVendors.flatMap((v) => v.adFaxes);
+ const adPostals = processedVendors.flatMap((v) => v.adPostals);
+ const adTels = processedVendors.flatMap((v) => v.adTels);
+ const adUrls = processedVendors.flatMap((v) => v.adUrls);
+ const bpTaxnums = processedVendors.flatMap((v) => v.bpTaxnums);
+ const bpVengens = processedVendors.flatMap((v) => v.bpVengens);
+ const bpCompnies = processedVendors.flatMap((v) => v.bpCompnies);
+ const bpWhtaxes = processedVendors.flatMap((v) => v.bpWhtaxes);
+ const bpPorgs = processedVendors.flatMap((v) => v.bpPorgs);
+ const zvpfns = processedVendors.flatMap((v) => v.zvpfns);
+
+ // 3) 부모 테이블 UPSERT (배치)
+ await bulkUpsert(tx, VENDOR_MASTER_BP_HEADER, vendorHeaderRows, 'VNDRCD');
+
+ // 4) 하위 테이블 교체 (배치)
+ // 정의서에서 하위 테이블 키를 알려주지 않았고, 정시템도 모른다고 하므로 최상위 테이블 PK 기준 전체 삭제 후 삽입
+ await Promise.all([
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS, addresses, VENDOR_MASTER_BP_HEADER_ADDRESS.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_TAXNUM, bpTaxnums, VENDOR_MASTER_BP_HEADER_BP_TAXNUM.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN, bpVengens, VENDOR_MASTER_BP_HEADER_BP_VENGEN.VNDRCD, vndrCds),
+
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_EMAIL, adEmails, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_EMAIL.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_FAX, adFaxes, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_FAX.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_POSTAL, adPostals, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_POSTAL.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_TEL, adTels, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_TEL.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_URL, adUrls, VENDOR_MASTER_BP_HEADER_ADDRESS_AD_URL.VNDRCD, vndrCds),
+
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_COMPNY, bpCompnies, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_COMPNY.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_COMPNY_BP_WHTAX, bpWhtaxes, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_COMPNY_BP_WHTAX.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_PORG, bpPorgs, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_PORG.VNDRCD, vndrCds),
+ bulkReplaceSubTableData(tx, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_PORG_ZVPFN, zvpfns, VENDOR_MASTER_BP_HEADER_BP_VENGEN_BP_PORG_ZVPFN.VNDRCD, vndrCds),
+ ]);
});
- console.log(`데이터베이스 저장 완료: ${processedVendors.length}개 벤더`);
+ console.log(`데이터베이스(배치) 저장 완료: ${processedVendors.length}개 벤더`);
return true;
} catch (error) {
console.error('데이터베이스 저장 중 오류 발생:', error);