gaoluyang
13 小时以前 e40a608ef9bf51db294d8eefada6a8e2e80cebca
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import sys,json,collections
from pathlib import Path
from decimal import Decimal
sys.stdout.reconfigure(encoding='utf-8')
sys.path.insert(0,str(Path('scripts/iqc-import-20261012/deps').resolve()))
import pymysql
P=Path(__file__).parent;TAG='OCT0814-DEMO'
r=json.loads((P/('result-current.json' if (P/'result-current.json').exists() else 'result-applied.json')).read_text(encoding='utf8'));assert r['committed']
normalized=json.loads((P/'normalize-applied.json').read_text(encoding='utf8')) if (P/'normalize-applied.json').exists() else None
if normalized:
 assert normalized['applied']
 for link in r['links']:
  for key,value in list(link.items()):
   if isinstance(value,str) and value in normalized['mapping']:link[key]=normalized['mapping'][value]
 batch_filter='tp.batch_id IN ('+','.join(str(x) for x in normalized['scoped_ids']['mes_wm_batch'])+')'
 stock_filter='id IN ('+','.join(str(x) for x in normalized['scoped_ids']['mes_wm_material_stock'])+')'
else:
 batch_filter="tp.batch_code LIKE 'OCT0814-DEMO%%'"
 stock_filter="batch_code LIKE 'OCT0814-DEMO%%'"
backup=json.loads((P/'before-seed-backup.json').read_text(encoding='utf8'))
canonical=lambda v:json.loads(json.dumps(v,ensure_ascii=False,default=str))
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=15,read_timeout=60)
with c.cursor() as q:
 q.execute('SELECT DATABASE() db');assert q.fetchone()['db']=='mom-hm'
 q.execute('SELECT p.id AS target,src.id AS source FROM mes_wm_transaction tp JOIN mes_wm_product_produce pp ON pp.id=tp.biz_id AND tp.biz_type=124 AND pp.deleted=0 JOIN mes_pro_feedback f ON f.id=pp.feedback_id AND f.deleted=0 JOIN mes_wm_item_consume ic ON ic.feedback_id=f.id AND ic.deleted=0 JOIN mes_wm_transaction tc ON tc.biz_id=ic.id AND tc.biz_type=123 AND tc.deleted=0 JOIN mes_wm_batch p ON p.id=tp.batch_id JOIN mes_wm_batch src ON src.id=tc.batch_id WHERE '+batch_filter)
 graph=collections.defaultdict(set)
 for e in q.fetchall():graph[e['target']].add(e['source'])
 raw_counts=[]
 for link in r['links']:
  q.execute('SELECT s.*,ct.total_price AS contract_total,qu.total_price AS quotation_total,qu.contract_id AS quoted_contract,mp.sale_order_id AS planned_order,mp.sale_order_no AS planned_no,mp.sale_order_item_id AS planned_item,mp.work_order_id,mp.status AS plan_status FROM erp_sale_order s JOIN crm_contract ct ON ct.id=s.contract_id JOIN crm_sale_quotation qu ON qu.id=%s JOIN mes_pro_mps mp ON mp.id=%s WHERE s.id=%s',(link['quotation_id'],link['mps_id'],link['sale_order_id']))
  row=q.fetchone();assert row and row['quoted_contract']==row['contract_id'] and row['planned_order']==row['id'] and row['planned_no']==row['no'] and row['planned_item']==link['order_item_id'] and row['work_order_id']==link['work_order_id'] and row['plan_status']==2
  assert row['total_price']==row['contract_total']==row['quotation_total']==Decimal(link['total']) and row['production_status']==1 and row['out_status']==3 and row['out_count']==Decimal(link['quantity'])
  q.execute('SELECT SUM(price) total,COUNT(*) n,SUM(receivable_id IS NOT NULL) paid FROM crm_receivable_plan WHERE contract_id=%s AND deleted=0',(link['contract_id'],));p=q.fetchone();assert p['n']==2 and p['total']==row['total_price'] and p['paid']==0
  q.execute('SELECT * FROM mes_qc_oqc WHERE id=%s',(link['oqc_id'],));qc=q.fetchone();assert qc['batch_code']==link['final_batch_code'] and qc['check_result']==1 and qc['status']==4
  q.execute('SELECT COUNT(*) n FROM mes_wm_product_sales_line l JOIN mes_wm_product_sales s ON s.id=l.sales_id WHERE s.sale_order_id=%s AND s.status=4 AND l.batch_code=%s AND l.oqc_id=%s',(row['id'],link['final_batch_code'],link['oqc_id']));assert q.fetchone()['n']==1
  stack=[link['final_batch_id']];seen=set();leaves=set()
  while stack:
   node=stack.pop()
   if node in seen:continue
   seen.add(node)
   if graph[node]:stack.extend(graph[node])
   else:leaves.add(node)
  assert leaves and len(seen)>len(leaves)
  for bid in leaves:
   q.execute('SELECT COUNT(*) n FROM mes_wm_item_receipt_line l JOIN mes_wm_item_receipt h ON h.id=l.receipt_id JOIN mes_qc_iqc qc ON qc.id=h.iqc_id WHERE l.batch_id=%s AND h.status=4 AND qc.status=4 AND qc.check_result=1',(bid,));assert q.fetchone()['n']==1
  raw_counts.append({'sales_order':row['no'],'final_batch':link['final_batch_code'],'traced_batches':len(seen),'source_raw_batches':len(leaves)})
 q.execute('SELECT COUNT(*) n,SUM(quantity) total,SUM(reserved_quantity) reserved FROM mes_wm_material_stock WHERE '+stock_filter+' AND deleted=0');stock=q.fetchone();assert stock['n']==62 and stock['total']==0 and stock['reserved']==0
 q.execute('SELECT * FROM mes_wm_material_stock WHERE deleted=0 ORDER BY id');current={x['id']:canonical(x) for x in q.fetchall()};assert all(current[x['id']]==x for x in backup['stock'])
 q.execute('SELECT * FROM crm_receivable WHERE deleted=0 ORDER BY id');assert canonical(q.fetchall())==backup['receivables']
 q.execute('SELECT * FROM mes_energy_record WHERE remark LIKE %s AND deleted=0 ORDER BY record_date',('%'+TAG+'%',));energy=q.fetchall();assert len(energy)==4 and {x['energy_type'] for x in energy}=={'WATER','ELECTRICITY','STEAM'}
 for record in energy:assert record['record_date'].weekday()<5 and record['record_date'].isoformat() in r['dates'] and (record['quantity']*record['unit_price']).quantize(Decimal('.01'))==record['amount']
 result={'verified':True,'database':'36.213.150.237:10000/mom-hm','business_chains':4,'source_raw_batches':sum(x['source_raw_batches'] for x in raw_counts),'trace_chains':raw_counts,'energy_records':4,'no_actual_receivables_added':True,'original_stock_unchanged':True}
(P/'final-verification.json').write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf8')
units={9:'盒',24:'个'}
lines=['# 10月8日至14日业务链补充清单','','已写入指定远程 `mom-hm`。日期采用 2026-10-08、09、12、14,避开双休日。全部新增业务标明演示用途;不是实际交易、投料、检测或物流凭证。','','## 营销至出库的四条链','','每条链包括商机、跟进、报价、合同、销售订单、生产主计划、工单、工序任务与参数、报工、来料及过程检验、产出、成品入库、出货检验、销售出库和两期回款计划。未录入实际回款。','']
for link in r['links']:
 lines.extend([f"### {link['date']} · {link['product']}({link['spec']})",'',f"- 客户:{link['customer']};数量:{link['quantity']} {units[link['unit_id']]}。",f"- 报价:`{link['quotation_no']}`;合同:`{link['contract_no']}`。",f"- 销售单号:`{link['sale_order_no']}`;金额:{Decimal(link['total']):,.2f} 元。",f"- 主计划:`{link['mps_code']}`;生产工单:`{link['work_order_code']}`。",f"- 最终成品批次:`{link['final_batch_code']}`,可在质量批次追溯中查询。",f"- 工序任务/报工:各 {len(link['tasks'])} 条;回款计划:2 期,总额与合同一致。",''])
lines.extend(['## 业务人员','','营销:朱莉(291);生产:李万娟(244)、张慧清(273);质量:华生菁(289)、罗军(290);仓库:李万娟(244,参考已有仓库单据);能源:赵磊(243)。','','## 能源','','4 条独立记录,覆盖水、电、蒸汽;单价参考既有台账,用量为演示补充,不与工单建立关联。',''])
for x in energy:lines.append(f"- {x['record_date']}:{x['energy_type']},{x['usage_area']},用量 {x['quantity']},金额 {x['amount']} 元。")
lines.extend(['','## 核验与保护','','- 4 条成品批次均能追溯到原料来料批次、来料检验和全部工序,并查到销售去向及出货检验。','- 16 个独立原料批次、26 个工序批次、124 条库存流水;新增库存入出平衡,原有库存未改动。','- 24 张质检单均有指标明细和样例检验结果。','- 新增 3 个明确标为演示用的适用检验方案,不改原质量标准与工艺/BOM主数据。','- 报价、合同、销售金额一致;8 条回款计划总额按合同校验,计划日期也避开双休日。','- 数据库事务演练、提交后新连接复核均通过。',''])
if normalized:
 lines=[x.replace('全部新增业务标明演示用途;不是实际交易、投料、检测或物流凭证。','编号与业务名称已规范化,备注保留补录来源;生成的检验结果保留样例标识。').replace('新增 3 个明确标为演示用的适用检验方案','新增 3 个适用检验方案,备注保留补录来源') for x in lines]
(P/'业务链核对清单.md').write_text('\n'.join(lines),encoding='utf8')
print(json.dumps(result,ensure_ascii=False))
c.close()