import sys,json from pathlib import Path from read_links import P,connect,encode def save(name,data):(P/name).write_text(encode(data),encoding='utf8') def main(): assert not (P/'oqc-binding-result.json').exists(),'Already applied' plan=json.loads((P/'oqc-binding-plan.json').read_text(encoding='utf8')) c=connect(sys.stdin.readline().strip()) try: with c.cursor() as q: q.execute("SET time_zone='+08:00'");q.execute('SELECT DATABASE() db,NOW() now');state=q.fetchone();assert state['db']=='mom-hm' q.execute("SELECT GET_LOCK('codex-customer-chain-repair-20261015',0) locked");assert q.fetchone()['locked']==1 q.execute('SELECT * FROM mes_qc_oqc WHERE deleted=0 ORDER BY id FOR UPDATE');before=q.fetchall() backup='oqc-before-'+state['now'].strftime('%Y%m%d-%H%M%S')+'.json';save(backup,before) current={r['id']:r for r in before};expected={id:dict(r) for id,r in current.items()} q.execute('SELECT id,name FROM crm_customer WHERE deleted=0 FOR SHARE');customers={r['id']:r for r in q.fetchall()} q.execute('SELECT id,no,customer_id,order_time FROM erp_sale_order WHERE deleted=0 FOR SHARE');orders={r['id']:r for r in q.fetchall()} q.execute('SELECT id,order_id,product_id FROM erp_sale_order_items WHERE deleted=0 FOR SHARE');lines={r['id']:r for r in q.fetchall()} for change in plan['changes']: id=change['id'];assert encode(current[id])==encode(change['source_before']),f'QC changed: {id}' assert change['after'] in customers and customers[change['after']]['name']==change['customer_name'] for e in change['evidence']: if 'order_id' in e: o=orders[e['order_id']];l=lines[e['line_id']] assert o['customer_id']==change['after'] and o['no']==e['order_no'] and str(o['order_time'])==e['order_time'] assert l['order_id']==o['id'] and l['product_id']==e['product_id'] expected[id]['client_id']=change['after'] q.execute('UPDATE mes_qc_oqc SET client_id=%s,update_time=%s WHERE id=%s AND client_id IS NULL AND deleted=0', (change['after'],current[id]['update_time'],id));assert q.rowcount==1 q.execute('SELECT * FROM mes_qc_oqc WHERE deleted=0 ORDER BY id');after={r['id']:r for r in q.fetchall()} assert encode(after)==encode(expected),'Unexpected QC field change' assert all(r['client_id'] in customers for r in after.values()),'Missing or invalid customer remains' c.commit() result=dict(plan['summary'],committed_at=str(state['now']),backup=str(P/backup), changed_fields=['client_id'],unrelated_oqc_unchanged=len(before)-len(plan['changes']), all_active_oqc_customer_valid=len(after),source_doc_links_unchanged=True, qc_results_quantities_dates_statuses_unchanged=True) save('oqc-binding-result.json',result);print(encode(result)) except BaseException:c.rollback();raise finally:c.close() def verify(): plan=json.loads((P/'oqc-binding-plan.json').read_text(encoding='utf8')) result=json.loads((P/'oqc-binding-result.json').read_text(encoding='utf8')) before=json.loads(Path(result['backup']).read_text(encoding='utf8')) expected={r['id']:r for r in before} for r in plan['changes']:expected[r['id']]['client_id']=r['after'] c=connect(sys.stdin.readline().strip()) try: with c.cursor() as q: q.execute("SET time_zone='+08:00'");q.execute('SELECT NOW() now');now=q.fetchone()['now'] q.execute('SELECT * FROM mes_qc_oqc WHERE deleted=0 ORDER BY id');after={r['id']:r for r in q.fetchall()} assert encode(after)==encode(expected),'Unexpected field changes' q.execute('SELECT id,name FROM crm_customer WHERE deleted=0');customers={r['id']:r for r in q.fetchall()} assert all(r['client_id'] in customers for r in after.values()) for r in plan['changes']:assert after[r['id']]['client_id']==r['after'] and customers[r['after']]['name']==r['customer_name'] verified={'passed':True,'verified_at':str(now),'updated':len(plan['changes']),'all_active_oqc':len(after), 'missing_or_invalid_customers':0,'only_client_id_changed':True,'match_basis_retained':True} save('oqc-binding-verification.json',verified);print(encode(verified)) finally:c.close() if __name__=='__main__':verify() if '--verify' in sys.argv else main()