diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/material-groups/page.tsx | 24 | ||||
| -rw-r--r-- | app/api/(S-ERP)/(MDG)/IF_MDZ_EVCP_MATERIAL_MASTER_PART/route.ts | 50 |
2 files changed, 59 insertions, 15 deletions
diff --git a/app/[lng]/evcp/(evcp)/material-groups/page.tsx b/app/[lng]/evcp/(evcp)/material-groups/page.tsx index 3acd11b9..468e3412 100644 --- a/app/[lng]/evcp/(evcp)/material-groups/page.tsx +++ b/app/[lng]/evcp/(evcp)/material-groups/page.tsx @@ -13,6 +13,7 @@ import { Shell } from "@/components/shell" import { getMaterialGroups } from "@/lib/material-groups/services" import { MaterialGroupTable } from "@/lib/material-groups/table/material-group-table" import { InformationButton } from "@/components/information/information-button" +import { MaterialGroupSyncButton } from "@/components/material-groups/sync-button" import { searchParamsCache } from "@/lib/material-groups/validations" interface MaterialGroupPageProps { @@ -39,18 +40,19 @@ export default async function MaterialGroupPage(props: MaterialGroupPageProps) { return ( <Shell className="gap-2"> <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <div className="flex items-center gap-2"> - <h2 className="text-2xl font-bold tracking-tight"> - 자재그룹 - </h2> - <InformationButton pagePath="evcp/material-groups" /> - </div> - <p className="text-muted-foreground"> - MDG로부터 수신된 자재그룹 정보 - </p> + <div> + <div className="flex items-center gap-2"> + <h2 className="text-2xl font-bold tracking-tight"> + 자재그룹 + </h2> + <InformationButton pagePath="evcp/material-groups" /> </div> + <p className="text-muted-foreground"> + MDG로부터 수신된 자재그룹 정보 + </p> + </div> + <div className="flex items-center gap-2"> + <MaterialGroupSyncButton /> </div> </div> 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 2c2ab0fc..80bc85d3 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 @@ -6,7 +6,8 @@ import { MATERIAL_MASTER_PART_MATL_PLNT, MATERIAL_MASTER_PART_MATL_UNIT, MATERIAL_MASTER_PART_MATL_CLASSASGN, - MATERIAL_MASTER_PART_MATL_CHARASGN + MATERIAL_MASTER_PART_MATL_CHARASGN, + MATERIAL_GROUP_MASTER } from "@/db/schema/MDG/mdg"; import { @@ -32,6 +33,7 @@ type MatlPlntData = typeof MATERIAL_MASTER_PART_MATL_PLNT.$inferInsert; type MatlUnitData = typeof MATERIAL_MASTER_PART_MATL_UNIT.$inferInsert; type MatlClassAsgnData = typeof MATERIAL_MASTER_PART_MATL_CLASSASGN.$inferInsert; type MatlCharAsgnData = typeof MATERIAL_MASTER_PART_MATL_CHARASGN.$inferInsert; +type MaterialGroupData = typeof MATERIAL_GROUP_MASTER.$inferInsert; // XML에서 받는 데이터 구조 (스키마와 동일한 구조, string 타입) type MatlXML = ToXMLFields<Omit<MatlData, 'id' | 'createdAt' | 'updatedAt'>> & { @@ -56,6 +58,7 @@ interface ProcessedMaterialData { units: MatlUnitData[]; classAssignments: MatlClassAsgnData[]; characteristicAssignments: MatlCharAsgnData[]; + materialGroup?: MaterialGroupData; // 자재그룹코드 데이터 (옵셔널) } export async function GET(request: NextRequest) { @@ -138,6 +141,12 @@ export async function POST(request: NextRequest) { * - 별도 필수 필드 없음 (스키마에서 notNull() 제거 예정) * - 전체 데이터셋 기반 삭제 후 재삽입 처리 * + * 3. 자재그룹코드 마스터 (MATERIAL_GROUP_MASTER) - 신규 추가 + * - MATKL(자재그룹코드), MAKTX(자재그룹명) 분리 저장 + * - MATKL이 unique 필드로 충돌 시 upsert 처리 + * - DESC 테이블에서 SPRAS='E'인 MAKTX만 매칭 + * - 중복 제거를 통한 성능 최적화 + * * XML 패턴: * - MATERIAL 인터페이스는 XML에 MATNR이 이미 포함된 패턴 * - 하위 테이블에도 MATNR 필드가 있어서 XML 값 우선 사용됨 @@ -191,13 +200,29 @@ function transformMatlData(matlData: MatlXML[]): ProcessedMaterialData[] { fkData ); + // 3단계: 자재그룹코드 마스터 데이터 생성 (MATKL 존재 시) + let materialGroup: MaterialGroupData | undefined; + + if (material.MATKL) { + // DESC 테이블에서 SPRAS='E'인 MAKTX 찾기 + const englishDescription = descriptions.find(desc => desc.SPRAS === 'E'); + + if (englishDescription && englishDescription.MAKTX) { + materialGroup = { + MATKL: material.MATKL, + MAKTX: englishDescription.MAKTX + }; + } + } + return { material, descriptions, plants, units, classAssignments, - characteristicAssignments + characteristicAssignments, + materialGroup }; }); } @@ -208,7 +233,10 @@ function transformMatlData(matlData: MatlXML[]): ProcessedMaterialData[] { * * 저장 전략: * 1. 최상위 테이블: MATNR 기준 upsert (충돌 시 업데이트) - * 2. 하위 테이블들: FK(MATNR) 기준 전체 삭제 후 재삽입 + * 2. 자재그룹코드 마스터: MATKL 기준 upsert (충돌 시 업데이트) + * - 중복된 MATKL 제거하여 성능 최적화 + * - 영어(SPRAS='E') DESC만 처리 + * 3. 하위 테이블들: FK(MATNR) 기준 전체 삭제 후 재삽입 * - 송신 XML이 전체 데이터셋을 포함하므로 부분 업데이트 불필요 * - 데이터 일관성과 단순성 확보 * @@ -231,11 +259,25 @@ async function saveToDatabase(processedMaterials: ProcessedMaterialData[]) { const units = processedMaterials.flatMap((m) => m.units); const classAssignments = processedMaterials.flatMap((m) => m.classAssignments); const characteristicAssignments = processedMaterials.flatMap((m) => m.characteristicAssignments); + + // 3) 자재그룹코드 데이터 수집 (중복 제거) + const materialGroups = processedMaterials + .filter((m) => m.materialGroup) + .map((m) => m.materialGroup!) + .filter((group, index, self) => + self.findIndex(g => g.MATKL === group.MATKL) === index // MATKL 기준 중복 제거 + ); // 3) 부모 테이블 UPSERT (배치) await bulkUpsert(tx, MATERIAL_MASTER_PART_MATL, materialRows, 'MATNR'); - // 4) 하위 테이블 교체 (배치) + // 4) 자재그룹코드 마스터 UPSERT (별도 처리) + if (materialGroups.length > 0) { + console.log(`자재그룹코드 마스터 저장: ${materialGroups.length}개 그룹`); + await bulkUpsert(tx, MATERIAL_GROUP_MASTER, materialGroups, 'MATKL'); + } + + // 5) 하위 테이블 교체 (배치) 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), |
