import csv, json, xml.etree.ElementTree as ET from decimal import Decimal as D from pathlib import Path ROOT = Path('scripts/stock-import-202609/material-repair') def read(name): root = ET.parse(ROOT.parent / name).getroot() return {s.tag: [{x.tag: x.text for x in row} for row in s] for s in root} before = read('remote-duplicate-references-snapshot.xml') after = read('remote-material-repair-final-snapshot.xml') plan = json.loads((ROOT / 'plan.json').read_text(encoding='utf-8')) merge = {str(x['old_id']): x for x in plan['merges']} materials = {x['id']: x for x in after['materials']} mes = {x['mdm_item_id']: x for x in after['mesMaterials'] if x['deleted'] == '0'} assert len(before['materials']) - len(materials) == 10 assert set(merge) <= {x['id'] for x in after['deletedMaterials']} for fix in plan['fixes']: if str(fix['id']) in merge: continue actual = materials[str(fix['id'])] assert (actual['name'], actual['specification']) == (fix['name'], fix['specification']) assert actual['category_id'] and actual['is_batch_managed'] == '1' numeric = {'quantity', 'frozen_quantity', 'reserved_quantity', 'in_transit_quantity'} for section, allowed in [('stock', {'item_id','item_type_id','updater','update_time'} | numeric), ('transactions', {'item_id','quantity','updater','update_time'}), ('batches', {'item_id','remark','updater','update_time'})]: current = {x['id']: x for x in after[section]} assert len(before[section]) == len(current) for old in before[section]: new = current[old['id']] mapping = merge.get(old['item_id']) target = str(mapping['new_id']) if mapping else old['item_id'] factor = D(mapping['factor']) if mapping else D('1') assert new['item_id'] == target for field in old: if field not in allowed: assert old[field] == new[field], (section, old['id'], field) for field in numeric & old.keys(): if old[field] is None: assert new[field] is None else: assert D(new[field]) == D(old[field]) * factor, (section,old['id'],field) if section == 'batches' and mapping: assert mapping['old_code'] in new['remark'] and mapping['provenance'] in new['remark'] stock = after['stock'] canonical = {x['item_id'] for x in stock if x['creator'] == 'stock-import-202609'} assert len(canonical) == 837 for item_id in canonical: m, i = materials[item_id], mes[item_id] for a, b in [('name','name'), ('specification','specification'), ('unit_measure_id','unit_measure_id'), ('category_id','item_type_id'), ('is_batch_managed','batch_flag'), ('status','status')]: assert m[a] == i[b], (item_id,a) assert i['batch_flag'] == '1' type_plan = json.loads((ROOT / 'verified-type-plan.json').read_text(encoding='utf-8')) for row in type_plan: m, i = materials[row['id']], mes[row['id']] assert m['item_type'] == row['target_type'] assert m['category_id'] == i['item_type_id'] == row['target_category_id'] batch = {x['id']: x for x in after['batches']} for s in stock: assert s['item_id'] in materials and s['item_id'] == batch[s['batch_id']]['item_id'] assert s['item_type_id'] == materials[s['item_id']]['category_id'] assert before['units'] == after['units'] assert before['warehouses'] == after['warehouses'] assert before['locations'] == after['locations'] assert before['areas'] == after['areas'] assert not after['duplicateReferences'] old_specs = json.loads((ROOT / 'existing-spec-plan.json').read_text(encoding='utf-8')) for f in old_specs['fixes']: assert materials[f['id']]['specification'] == mes[f['id']]['specification'] == f['specification'] msg = '核验通过:582条新增物料字段及分类已整理,201条原表规格已提取,39条已有物料缺失规格已补齐;10条重复物料归并,837个库存物料分类和批次管理与MES一致,574条MES记录补建;855条库存、843个批次及755条流水记录保留,单位换算后实物数量守恒;仓库、库区、库位和单位主数据均未改变。\n' (ROOT / 'verification.txt').write_text(msg, encoding='utf-8') with (ROOT / '整理后MES物料清单.csv').open('w', encoding='utf-8-sig', newline='') as f: w=csv.writer(f);w.writerow(['物料ID','编码','名称','规格','物料类型','分类ID','单位ID','批次管理','MES已同步']) for item_id in sorted(canonical,key=int): m=materials[item_id];w.writerow([item_id,m['code'],m['name'],m['specification'],{'1':'原料','2':'半成品','3':'成品','4':'辅料'}[m['item_type']],m['category_id'],m['unit_measure_id'],'启用','是']) print(msg)