"""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()