gaoluyang
15 小时以前 e71d71bf43c2307c89d3592adf08c1b32fcd0907
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""Raise the four October supplemental chains to 200, atomically across all modules."""
import sys,json,re,collections
from pathlib import Path
from decimal import Decimal,ROUND_HALF_UP
sys.stdout.reconfigure(encoding='utf-8')
sys.path.insert(0,str(Path('scripts/iqc-import-20261012/deps').resolve()))
import pymysql
P=Path(__file__).parent;D=Decimal;apply='--apply' in sys.argv
normalize=json.loads((P/'normalize-applied.json').read_text(encoding='utf8'))
manifest=json.loads((P/'result-applied.json').read_text(encoding='utf8'))
for link in manifest['links']:
 for k,v in list(link.items()):
  if isinstance(v,str) and v in normalize['mapping']:link[k]=normalize['mapping'][v]
schema=json.loads((P/'schema-snapshot.json').read_text(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,autocommit=False,read_timeout=90)
def dump(name,obj):(P/name).write_text(json.dumps(obj,ensure_ascii=False,indent=2,default=str),encoding='utf8')
def money(v):return D(v).quantize(D('.01'),rounding=ROUND_HALF_UP)
rows={};changes=[];log=[];summaries=[]
with c.cursor() as q:
 try:
  q.execute('SELECT GET_LOCK(%s,10)',('october-chain-production-floor',));assert list(q.fetchone().values())==[1]
  for t,ids in normalize['scoped_ids'].items():
   q.execute('SELECT * FROM `'+t+'` WHERE id IN ('+','.join(['%s']*len(ids))+') FOR UPDATE',tuple(ids));rows[t]=q.fetchall();assert len(rows[t])==len(ids)
  dump('before-production-floor-'+('apply' if apply else 'preview')+'.json',rows)
  byid={t:{x['id']:x for x in rr} for t,rr in rows.items()}
  day_factors={};new_totals={}
  for link in manifest['links']:
   mp=byid['mes_pro_mps'][link['mps_id']];wo=byid['mes_pro_work_order'][link['work_order_id']];order=byid['erp_sale_order'][link['sale_order_id']];oi=byid['erp_sale_order_items'][link['order_item_id']]
   old=mp['quantity'];assert old>0 and mp['quantity_produced']==wo['quantity']==wo['quantity_produced']==oi['count']==oi['out_count']==order['total_count']==order['out_count']==old
   assert old==D(link['quantity']),('Dataset already changed',link['sale_order_no'],old)
   target=max(old,D(200));factor=target/old;day_factors[link['date']]=factor
   net=money(oi['product_price']*target);tax=money(net*oi['tax_percent']/100);total=net+tax
   new_totals[link['date']]=(net,tax,total)
   summaries.append({'sales_order':order['no'],'product':link['product'],'unit_id':link['unit_id'],'old_quantity':old,'new_quantity':target,'old_total':order['total_price'],'new_total':total})
   link['quantity']=target;link['total']=total
  # Scale only transaction quantities, never prices, percentages, sample readings or template limits.
  quantity_tables={'crm_business_product','crm_sale_quotation_product','crm_contract_product','erp_sale_order','erp_sale_order_items','mes_pro_mps','mes_pro_work_order','mes_pro_work_order_bom','mes_pro_task','mes_pro_feedback','mes_pro_task_issue','mes_wm_arrival_notice_line','mes_qc_iqc','mes_qc_ipqc','mes_qc_oqc','mes_wm_item_receipt_line','mes_wm_item_receipt_detail','mes_wm_product_issue_line','mes_wm_product_issue_detail','mes_wm_item_consume_line','mes_wm_item_consume_detail','mes_wm_product_produce_line','mes_wm_product_produce_detail','mes_wm_product_receipt_line','mes_wm_product_receipt_detail','mes_wm_sales_notice_line','mes_wm_product_sales_line','mes_wm_product_sales_detail','mes_wm_stock_reserve','mes_wm_transaction','mes_wm_material_stock'}
  for t,rr in rows.items():
   decimals={x['Field']:int(re.search(r'decimal\(\d+,(\d+)\)',x['Type']).group(1)) for x in schema[t]['columns'] if x['Type'].startswith('decimal')}
   for row in rr:
    day=row['create_time'].date().isoformat();factor=day_factors.get(day,D(1));v={}
    if factor==1:continue
    if t in quantity_tables:
     for k,old in row.items():
      isquantity=k=='quantity' or k.endswith('_quantity') or k in ['count','total_count','out_count','quantity_produced','quantity_scheduled','quantity_changed']
      if not isquantity or k.startswith(('min_','max_')) or old is None or old==0:continue
      assert isinstance(old,(D,int)),(t,k)
      value=D(old)*factor
      value=value.quantize(D(1).scaleb(-decimals[k]),rounding=ROUND_HALF_UP) if k in decimals else int(value)
      if value!=old:v[k]=value
    net,tax,total=new_totals[day]
    if t=='crm_business':v.update(total_product_price=net,total_price=total)
    elif t=='crm_sale_quotation':v.update(total_product_price=net,tax_price=tax,total_price=total)
    elif t=='crm_contract':v.update(total_product_price=total,total_price=total)
    elif t=='erp_sale_order':v.update(total_product_price=net,total_tax_price=tax,total_price=total)
    elif t in ['crm_business_product','crm_sale_quotation_product','erp_sale_order_items']:
     v['total_price']=net
     if 'tax_price' in row:v['tax_price']=tax
    elif t=='crm_contract_product':
     v['total_price']=total;assert money(row['contract_price']*v['count'])==total
    elif t=='crm_receivable_plan':
     assert row['receivable_id'] is None and row['period'] in [1,2]
     first=money(total*D('.3'));v['price']=first if row['period']==1 else total-first
    if not v:continue
    sql='UPDATE `'+t+'` SET '+','.join('`'+k+'`=%s' for k in v)+',update_time=update_time WHERE id=%s'
    args=tuple(v.values())+(row['id'],);q.execute(sql,args);assert q.rowcount==1
    log.append(q.mogrify(sql,args)+';');changes.append({'table':t,'id':row['id'],'before':{k:row[k] for k in v},'after':v});byid[t][row['id']]={**row,**v}
  for link in manifest['links']:
   target=link['quantity'];day=link['date'];net,tax,total=new_totals[day]
   for t,key,field in [('mes_pro_mps','mps_id','quantity_produced'),('mes_pro_work_order','work_order_id','quantity_produced'),('erp_sale_order','sale_order_id','out_count'),('erp_sale_order_items','order_item_id','out_count')]:assert byid[t][link[key]][field]==target
   for tid in link['tasks']:
    task=byid['mes_pro_task'][tid];assert task['quantity']==task['produced_quantity']==task['qualify_quantity']==target
   for fid in link['feedbacks']:
    fb=byid['mes_pro_feedback'][fid];assert fb['scheduled_quantity']==fb['feedback_quantity']==fb['qualified_quantity']==target
   assert sum(byid['crm_receivable_plan'][pid]['price'] for pid in link['receivable_plans'])==total
   assert byid['mes_qc_oqc'][link['oqc_id']]['out_quantity']==byid['mes_qc_oqc'][link['oqc_id']]['qualified_quantity']==target
  balances=collections.defaultdict(lambda:D(0))
  for row in byid['mes_wm_transaction'].values():balances[row['material_stock_id']]+=row['quantity']
  assert len(balances)==62 and all(x==0 for x in balances.values()),'Inventory flows must balance'
  for row in byid['mes_wm_material_stock'].values():assert row['quantity']==row['reserved_quantity']==0
  # All fields outside the explicit change set must remain unchanged.
  expected_changes={(x['table'],x['id']):x['after'] for x in changes}
  for t,rr in rows.items():
   for row in rr:
    q.execute('SELECT * FROM `'+t+'` WHERE id=%s',(row['id'],));assert q.fetchone()=={**row,**expected_changes.get((t,row['id']),{})},(t,row['id'])
  result={'applied':apply,'minimum_quantity':200,'chains':summaries,'changed_rows':len(changes),'changes':changes,'inventory_balanced':True,'unit_prices_unchanged':True}
  dump('production-floor-'+('applied' if apply else 'preview')+'.json',result)
  (P/('production-floor-applied.sql' if apply else 'production-floor-preview.sql')).write_text('\n'.join(log),encoding='utf8')
  if apply:
   c.commit();dump('result-current.json',manifest)
  else:c.rollback()
  print(json.dumps({k:result[k] for k in ['applied','minimum_quantity','chains','changed_rows','inventory_balanced','unit_prices_unchanged']},ensure_ascii=False,default=str))
 except:
  c.rollback();raise
 finally:
  q.execute('SELECT RELEASE_LOCK(%s)',('october-chain-production-floor',))
c.close()