"use server"; import { sql } from "drizzle-orm"; import db from "@/db/db"; import { MATERIAL_MASTER_PART_MATL, MATERIAL_MASTER_PART_MATL_DESC, MATERIAL_GROUP_MASTER } from "@/db/schema/MDG/mdg"; export interface SyncResult { success: boolean; message: string; data?: { totalProcessed: number; newRecords: number; updatedRecords: number; skippedRecords: number; }; } /** * 기존 MDG 데이터로부터 MATERIAL_GROUP_MASTER 테이블을 동기화하는 함수 * * 로직: * 1. MATERIAL_MASTER_PART_MATL에서 MATKL(자재그룹코드)가 있는 레코드 조회 * 2. 각 MATKL에 대해 MATERIAL_MASTER_PART_MATL_DESC에서 SPRAS='E'인 MAKTX 찾기 * 3. MATERIAL_GROUP_MASTER에 UPSERT (MATKL 기준으로 중복 제거) */ export async function syncMaterialGroupMaster(): Promise { try { console.log("📊 자재그룹 마스터 동기화 시작..."); const result = await db.transaction(async (tx) => { // 1단계: MATKL별로 첫 번째 MATNR 선택 (DISTINCT ON 사용) const matklWithMatnr = await tx.execute(sql` SELECT DISTINCT ON (${MATERIAL_MASTER_PART_MATL.MATKL}) ${MATERIAL_MASTER_PART_MATL.MATKL} as "MATKL", ${MATERIAL_MASTER_PART_MATL.MATNR} as "MATNR" FROM ${MATERIAL_MASTER_PART_MATL} WHERE ${MATERIAL_MASTER_PART_MATL.MATKL} IS NOT NULL AND ${MATERIAL_MASTER_PART_MATL.MATKL} != '' ORDER BY ${MATERIAL_MASTER_PART_MATL.MATKL}, ${MATERIAL_MASTER_PART_MATL.MATNR} `); console.log(`🔍 발견된 고유 자재그룹코드: ${matklWithMatnr.rowCount}개`); if (matklWithMatnr.rowCount === 0) { return { totalProcessed: 0, newRecords: 0, updatedRecords: 0, skippedRecords: 0 }; } // 2단계: 선택된 MATNR들로 DESC 테이블에서 영어 설명 조회 const matnrList = matklWithMatnr.rows.map(item => item.MATNR as string); const materialGroupData = await tx .select({ MATKL: MATERIAL_MASTER_PART_MATL.MATKL, MAKTX: MATERIAL_MASTER_PART_MATL_DESC.MAKTX }) .from(MATERIAL_MASTER_PART_MATL) .innerJoin( MATERIAL_MASTER_PART_MATL_DESC, sql`${MATERIAL_MASTER_PART_MATL.MATNR} = ${MATERIAL_MASTER_PART_MATL_DESC.MATNR}` ) .where(sql` ${MATERIAL_MASTER_PART_MATL.MATNR} IN (${sql.join(matnrList.map(matnr => sql`${matnr}`), sql`, `)}) AND ${MATERIAL_MASTER_PART_MATL_DESC.SPRAS} = 'E' AND ${MATERIAL_MASTER_PART_MATL_DESC.MAKTX} IS NOT NULL AND ${MATERIAL_MASTER_PART_MATL_DESC.MAKTX} != '' `); console.log(`✅ 유효한 자재그룹 데이터: ${materialGroupData.length}개`); const validData = materialGroupData; if (validData.length === 0) { return { totalProcessed: 0, newRecords: 0, updatedRecords: 0, skippedRecords: matklWithMatnr.rowCount || 0 }; } // 3단계: 기존 MATERIAL_GROUP_MASTER의 모든 MATKL 조회 const existingRecords = await tx .select({ MATKL: MATERIAL_GROUP_MASTER.MATKL }) .from(MATERIAL_GROUP_MASTER); const existingMatkls = new Set(existingRecords.map(r => r.MATKL)); // 4단계: 신규/업데이트 데이터 분류 (null 값 제외하고 타입 안전하게) const filteredData = validData.filter((item): item is { MATKL: string; MAKTX: string } => !!item.MATKL && !!item.MAKTX ); const newRecords = filteredData.filter(item => !existingMatkls.has(item.MATKL)); const updateRecords = filteredData.filter(item => existingMatkls.has(item.MATKL)); let insertedCount = 0; let updatedCount = 0; // 5단계: 신규 레코드 삽입 (배치 처리) if (newRecords.length > 0) { await tx .insert(MATERIAL_GROUP_MASTER) .values(newRecords); insertedCount = newRecords.length; console.log(`➕ 신규 삽입: ${insertedCount}개`); } // 6단계: 기존 레코드 업데이트 (배치 처리) if (updateRecords.length > 0) { // PostgreSQL의 UPDATE ... FROM 구문 사용 await tx.execute(sql` UPDATE mdg."MATERIAL_GROUP_MASTER" SET "MAKTX" = temp_data.maktx, "updated_at" = NOW() FROM ( VALUES ${sql.join( updateRecords.map(item => sql`(${item.MATKL}, ${item.MAKTX})` ), sql`, ` )} ) AS temp_data(matkl, maktx) WHERE "MATKL" = temp_data.matkl `); updatedCount = updateRecords.length; console.log(`🔄 업데이트: ${updatedCount}개`); } return { totalProcessed: validData.length, newRecords: insertedCount, updatedRecords: updatedCount, skippedRecords: (matklWithMatnr.rowCount || 0) - validData.length }; }); console.log("✅ 자재그룹 마스터 동기화 완료"); return { success: true, message: `동기화 완료: 신규 ${result.newRecords}개, 업데이트 ${result.updatedRecords}개, 건너뜀 ${result.skippedRecords}개`, data: result }; } catch (error) { console.error("❌ 자재그룹 마스터 동기화 오류:", error); return { success: false, message: `동기화 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}` }; } } /** * 동기화 상태 확인 함수 */ export async function getMaterialGroupSyncStatus(): Promise<{ success: boolean; data?: { totalMDGRecords: number; totalMasterRecords: number; lastSyncDate?: string; }; }> { try { const [mdgCount, masterCount] = await Promise.all([ // MDG에서 유효한 MATKL 개수 db .select({ count: sql`count(distinct ${MATERIAL_MASTER_PART_MATL.MATKL})` }) .from(MATERIAL_MASTER_PART_MATL) .where(sql` ${MATERIAL_MASTER_PART_MATL.MATKL} IS NOT NULL AND ${MATERIAL_MASTER_PART_MATL.MATKL} != '' `), // MATERIAL_GROUP_MASTER 개수 db .select({ count: sql`count(*)` }) .from(MATERIAL_GROUP_MASTER) ]); // 최근 업데이트 시간 조회 const lastUpdated = await db .select({ lastUpdate: sql`max(${MATERIAL_GROUP_MASTER.updatedAt})` }) .from(MATERIAL_GROUP_MASTER); return { success: true, data: { totalMDGRecords: mdgCount[0]?.count || 0, totalMasterRecords: masterCount[0]?.count || 0, lastSyncDate: lastUpdated[0]?.lastUpdate || undefined } }; } catch (error) { console.error("동기화 상태 확인 오류:", error); return { success: false }; } }