import sys,json,csv,collections
|
from decimal import Decimal
|
from pathlib import Path
|
sys.path.insert(0,str(Path('scripts/iqc-import-20261012/deps').resolve()))
|
import pymysql
|
apply='--apply' in sys.argv
|
conn=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=15,read_timeout=60,autocommit=False)
|
out=Path('scripts/mps-link-data-20261014');out.mkdir(exist_ok=True)
|
def save(name,data): (out/name).write_text(json.dumps(data,ensure_ascii=False,indent=2,default=str),encoding='utf8')
|
with conn.cursor() as q:
|
q.execute('SELECT p.*,u.name AS plan_unit_name FROM mes_pro_mps p LEFT JOIN mes_md_unit_measure u ON u.id=p.unit_measure_id AND u.deleted=0 WHERE p.deleted=0 ORDER BY p.id')
|
plans=q.fetchall()
|
q.execute('SELECT i.*,o.no,o.customer_id,o.order_time,u.name AS sale_unit_name FROM erp_sale_order_items i JOIN erp_sale_order o ON o.id=i.order_id AND o.deleted=0 LEFT JOIN mdm_unit_measure u ON u.id=i.product_unit_id AND u.deleted=0 WHERE i.deleted=0')
|
items=q.fetchall()
|
q.execute('SELECT id,code,mdm_item_id,name FROM mes_md_item WHERE deleted=0')
|
products={x['id']:x for x in q.fetchall()}
|
q.execute('SELECT id,code FROM mdm_item WHERE deleted=0')
|
mdm_by_code=collections.defaultdict(list)
|
for x in q.fetchall():mdm_by_code[x['code']].append(x['id'])
|
save('strict-audit-before.json',{'plans':plans,'items':items,'products':products})
|
rows=[];matches=[]
|
for plan in plans:
|
product=products.get(plan['product_id'],{})
|
mdm_id=product.get('mdm_item_id')
|
if not mdm_id and len(mdm_by_code[product.get('code')])==1:mdm_id=mdm_by_code[product['code']][0]
|
choices=[x for x in items if mdm_id and x['product_id']==mdm_id]
|
qty=[x for x in choices if Decimal(str(x['count']))==Decimal(str(plan['quantity']))]
|
unit=[x for x in qty if plan['plan_unit_name'] and x['sale_unit_name'] and plan['plan_unit_name'].strip()==x['sale_unit_name'].strip()]
|
month=[x for x in unit if plan['request_date'] and x['order_time'] and plan['request_date'].strftime('%Y-%m')==x['order_time'].strftime('%Y-%m')]
|
available=[x for x in month if not x['mps_id'] or x['mps_id']==plan['id']]
|
reason=('产品无对应销售明细' if not choices else '数量不一致' if not qty else '单位不一致或缺失' if not unit else '月份不一致或缺失' if not month else '销售明细已关联其他主计划' if not available else '多条符合条件的销售明细' if len(available)>1 else '唯一候选')
|
if plan['sale_order_id'] or plan['sale_order_no'] or plan['sale_order_item_id']:reason='已有来源关联,保持原样'
|
row={'主计划ID':plan['id'],'主计划编码':plan['code'],'产品编码':product.get('code',''),'产品名称':product.get('name',''),'计划数量':plan['quantity'],'计划单位':plan['plan_unit_name'],'需求日期':plan['request_date'],'同产品候选数':len(choices),'同数量候选数':len(qty),'同单位候选数':len(unit),'同月份候选数':len(month),'候选销售单号':';'.join(sorted(set(x['no'] for x in available))),'结果':reason}
|
rows.append(row)
|
if reason=='唯一候选':matches.append((plan,available[0],row))
|
reverse=collections.Counter(x[1]['id'] for x in matches)
|
for plan,item,row in matches:
|
if reverse[item['id']]>1:row['结果']='同一销售明细匹配多个主计划,暂不关联'
|
matches=[x for x in matches if reverse[x[1]['id']]==1]
|
save('confirmed-candidates.json',[{'mps_id':p['id'],'mps_code':p['code'],'order_id':i['order_id'],'order_no':i['no'],'item_id':i['id']} for p,i,r in matches])
|
if apply and matches:
|
try:
|
with conn.cursor() as q:
|
backups=[]
|
for plan,item,row in matches:
|
q.execute('SELECT * FROM mes_pro_mps WHERE id=%s AND deleted=0 FOR UPDATE',(plan['id'],));old_plan=q.fetchone()
|
q.execute('SELECT * FROM erp_sale_order_items WHERE id=%s AND deleted=0 FOR UPDATE',(item['id'],));old_item=q.fetchone()
|
assert old_plan and old_item and not any(old_plan[x] for x in ['sale_order_id','sale_order_no','sale_order_item_id'])
|
assert not old_item['mps_id'] or old_item['mps_id']==plan['id']
|
backups.append({'plan':old_plan,'item':old_item})
|
save('updated-records-backup.json',backups)
|
for plan,item,row in matches:
|
q.execute('UPDATE mes_pro_mps SET sale_order_id=%s,sale_order_no=%s,sale_order_item_id=%s,update_time=update_time WHERE id=%s AND deleted=0',(item['order_id'],item['no'],item['id'],plan['id']));assert q.rowcount==1
|
if not item['mps_id']:
|
q.execute('UPDATE erp_sale_order_items SET mps_id=%s,update_time=update_time WHERE id=%s AND deleted=0 AND (mps_id IS NULL OR mps_id=0)',(plan['id'],item['id']));assert q.rowcount==1
|
row['结果']='已关联'
|
for backup in backups:
|
q.execute('SELECT * FROM mes_pro_mps WHERE id=%s',(backup['plan']['id'],));now_plan=q.fetchone()
|
q.execute('SELECT * FROM erp_sale_order_items WHERE id=%s',(backup['item']['id'],));now_item=q.fetchone()
|
changed=[k for k,v in backup['plan'].items() if k not in ['sale_order_id','sale_order_no','sale_order_item_id'] and now_plan[k]!=v]
|
assert not changed, 'Unexpected plan fields changed: '+','.join(changed)
|
assert all(now_item[k]==v for k,v in backup['item'].items() if k!='mps_id')
|
assert now_item['mps_id']==now_plan['id'] and now_plan['sale_order_item_id']==now_item['id'] and now_plan['sale_order_id']==now_item['order_id']
|
conn.commit()
|
except Exception:
|
conn.rollback();raise
|
else:conn.rollback()
|
with (out/'生产主计划销售订单严格匹配结果.csv').open('w',encoding='utf-8-sig',newline='') as f:
|
w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
|
summary={'total_plans':len(plans),'unique_strict_matches':len(matches),'applied':len(matches) if apply else 0,'results':dict(collections.Counter(x['结果'] for x in rows))}
|
save('strict-match-summary.json',summary)
|
print(json.dumps(summary,ensure_ascii=False))
|
conn.close()
|