"""Sales item need-production = yes; native draft MPS per item, idempotent links.""" import sys,json,csv,collections from pathlib import Path from decimal import Decimal P=Path(__file__).parent sys.path.insert(0,str(P.parent/'iqc-import-20261012/deps')) import pymysql s=json.loads((P/'before-sales-mps-snapshot.json').read_text(encoding='utf8')) def norm(v):return json.loads(json.dumps(v,default=str)) def dump(n,v):(P/n).write_text(json.dumps(v,ensure_ascii=False,indent=2,default=str),encoding='utf8') c=pymysql.connect(host='36.213.150.237',port=10000,user='root',password=sys.stdin.readline().strip(),database='mom-hm',charset='utf8mb4',cursorclass=pymysql.cursors.DictCursor,connect_timeout=10,read_timeout=60) log=[] try: with c.cursor() as q: q.execute('SELECT DATABASE() db');assert q.fetchone()['db']=='mom-hm' q.execute("SELECT GET_LOCK('sales-mps-20261012',10) ok");assert q.fetchone()['ok']==1 c.begin();before={} for t in ['erp_sale_order','erp_sale_order_items','mes_pro_mps','mes_pro_mps_merge_item','mes_md_item','mdm_item','mdm_unit_measure']: q.execute('SELECT ENGINE FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name=%s',(t,));assert q.fetchone()['ENGINE']=='InnoDB' q.execute('SELECT * FROM '+t+' WHERE deleted=0 ORDER BY id FOR UPDATE');before[t]=list(q.fetchall()) assert norm(before[t])==s[t],f'{t} changed; re-review required' dump('before-sales-mps-import.json',before) orders={o['id']:o for o in before['erp_sale_order']} assert all(o['status']==20 and o['production_status']==0 for o in orders.values()) mes=collections.defaultdict(list) for i in before['mes_md_item']:mes[i['mdm_item_id']].append(i) active=collections.defaultdict(list) for m in before['mes_pro_mps']: if m['status']!=3:active[m['sale_order_item_id']].append(m) values=[];reused=[];codes={m['code'] for m in before['mes_pro_mps']};unit_issues=[] for item in before['erp_sale_order_items']: assert item['order_id'] in orders and Decimal(item['count'])>0 assert len(mes[item['product_id']])==1 product=mes[item['product_id']][0];order=orders[item['order_id']] assert product['status']==0 if item['product_unit_id']!=product['unit_measure_id']:unit_issues.append(dict(sale_order_item_id=item['id'],sale_order_no=order['no'],product_code=product['code'],product_name=product['name'],specification=product['specification'],sale_unit_id=item['product_unit_id'],mes_unit_id=product['unit_measure_id'],quantity=str(item['count']),note='按原生MPS生成规则采用MES产品单位,数量为整个产品SKU数量;订单包装单位不同,后续下发前需核实主数据单位口径。')) existing=active[item['id']];assert len(existing)<=1 if existing: old=existing[0];assert old['product_id']==product['id'] and old['quantity']==item['count'];reused.append(old);continue code='MPS20261012-SO-'+str(item['id']).zfill(6);assert code not in codes;codes.add(code) values.append(dict(code=code,status=0,sale_order_id=order['id'],sale_order_no=order['no'],sale_order_item_id=item['id'],product_id=product['id'],quantity=item['count'],quantity_produced=Decimal(0),unit_measure_id=product['unit_measure_id'],client_id=order['customer_id'],request_date=order['order_time'],merge_flag=0,remark='由销售订单生成:'+order['no']+';对应明细'+str(item['id'])+';需要生产改为是;保留订单数量,待计划下发。')) if values: for v in values:v['creator']='1';v['updater']='1' cols=list(values[0]);sql='INSERT INTO mes_pro_mps (`'+'`,`'.join(cols)+'`) VALUES ('+','.join(['%s']*len(cols))+')' args=[tuple(v[k] for k in cols) for v in values];log.extend(q.mogrify(sql,a)+';' for a in args);q.executemany(sql,args) # Scope to the reviewed order IDs; do not change approval, prices, quantities or shipment data. ids=list(orders);marks=','.join(['%s']*len(ids)) sql="UPDATE erp_sale_order_items i JOIN mes_pro_mps m ON m.sale_order_item_id=i.id AND m.deleted=0 AND m.status<>3 SET i.need_production=1,i.mps_id=m.id,i.updater='1' WHERE i.deleted=0 AND i.order_id IN ("+marks+')' log.append(q.mogrify(sql,ids)+';');q.execute(sql,ids);changed=q.rowcount q.execute('SELECT * FROM mes_pro_mps WHERE deleted=0 ORDER BY id');after_mps=list(q.fetchall()) q.execute('SELECT * FROM erp_sale_order_items WHERE deleted=0 ORDER BY id');after_items=list(q.fetchall()) final={m['sale_order_item_id']:m for m in after_mps if m['status']!=3} assert len(final)==len(before['erp_sale_order_items']) and len(after_mps)==len(before['mes_pro_mps'])+len(values) for item,old in zip(after_items,before['erp_sale_order_items']): assert item['id']==old['id'] and item['need_production']==1 m=final[item['id']];o=orders[item['order_id']];prd=mes[item['product_id']][0] assert item['mps_id']==m['id'] and m['sale_order_id']==o['id'] and m['sale_order_no']==o['no'] and m['product_id']==prd['id'] and m['quantity']==item['count'] and m['unit_measure_id']==prd['unit_measure_id'] and m['client_id']==o['customer_id'] and m['request_date']==o['order_time'] if m['code'].startswith('MPS20261012-SO-'):assert m['status']==0 and m['quantity_produced']==0 and m['work_order_id'] is None and m['issue_date'] is None for k in old: if k not in ['need_production','mps_id','updater','update_time']:assert old[k]==item[k] q.execute('SELECT * FROM erp_sale_order WHERE deleted=0 ORDER BY id');assert list(q.fetchall())==before['erp_sale_order'] for t in ['mes_pro_mps_merge_item','mes_md_item','mdm_item','mdm_unit_measure']: q.execute('SELECT * FROM '+t+' WHERE deleted=0 ORDER BY id');assert list(q.fetchall())==before[t] result={'database':'mom-hm','sale_orders':len(orders),'sales_items_need_production_yes':len(after_items),'new_draft_mps':len(values),'existing_mps_reused':len(reused),'sales_items_updated':changed,'unit_difference_items':len(unit_issues),'production_status':'未完成','mps_status':'草稿','duplicate_active_mps':0,'quantities_and_product_mapping_verified':True,'sales_financial_and_approval_fields_preserved':True,'verified':True} c.commit();dump('sales-mps-import-result.json',result);dump('sales-mps-generated.json',after_mps);dump('sales-mps-unit-differences.json',unit_issues) (P/'sales-mps-executed.sql').write_text('START TRANSACTION;\n'+'\n'.join(log)+'\nCOMMIT;\n',encoding='utf8') units={u['id']:u['name'].strip() for u in before['mdm_unit_measure']};prodids={p['id']:p for p in before['mes_md_item']} with (P/'销售订单生产主计划对应.csv').open('w',encoding='utf-8-sig',newline='') as f: w=csv.writer(f);w.writerow(['销售订单号','订单明细ID','主计划ID','主计划编码','产品编码','产品名称','规格','计划数量','计划单位','状态','需求日期','客户ID']) for m in after_mps: if m['sale_order_item_id'] not in final:continue prd=prodids[m['product_id']];w.writerow([m['sale_order_no'],m['sale_order_item_id'],m['id'],m['code'],prd['code'],prd['name'],prd['specification'],m['quantity'],units.get(m['unit_measure_id']),'草稿' if m['status']==0 else m['status'],m['request_date'],m['client_id']]) with (P/'销售生产单位口径待核实.csv').open('w',encoding='utf-8-sig',newline='') as f: w=csv.writer(f);w.writerow(['销售订单号','明细ID','产品编码','产品名称','规格','销售单位','MES及MPS单位','计划数量','说明']) for a in unit_issues:w.writerow([a['sale_order_no'],a['sale_order_item_id'],a['product_code'],a['product_name'],a['specification'],units.get(a['sale_unit_id']),units.get(a['mes_unit_id']),a['quantity'],a['note']]) print(json.dumps(result,ensure_ascii=False));q.execute("SELECT RELEASE_LOCK('sales-mps-20261012')") except:c.rollback();raise finally:c.close()