gaoluyang
3 天以前 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
"""Link non-Beijing-Tongrentang quotations to existing contract opportunities."""
import sys,json
from pathlib import Path
from datetime import datetime
from decimal import Decimal
P=Path(__file__).parent
sys.stdout.reconfigure(encoding='utf8')
sys.path.insert(0,str(P.parent/'iqc-import-20261012/deps'))
import pymysql
assert not (P/'result.json').exists(),'Already processed; verify instead of repeating.'
minimum=datetime(2026,9,17)
c=pymysql.connect(host='36.213.150.237',port=10000,user='root',password=sys.stdin.readline().strip(),database='mom-hm',charset='utf8mb4',autocommit=False,cursorclass=pymysql.cursors.DictCursor)
result={'quotes':[],'new_business_product_ids':[],'new_permission_ids':[],'business_headers_created':0,'excluded_customer_keyword':'北京同仁堂'}
try:
 with c.cursor() as q:
  q.execute("SET time_zone='+08:00'")
  q.execute('SELECT DATABASE() db,NOW() now,GET_LOCK(%s,10) locked',('quotation-opportunities-20261014',));state=q.fetchone();assert state['db']=='mom-hm' and state['locked']==1;now=state['now']
  tabs=['crm_customer','crm_business','crm_business_product','crm_sale_quotation','crm_sale_quotation_product','crm_contract','crm_contract_product','erp_sale_order','erp_sale_order_items','crm_permission','crm_invoice','crm_receivable','crm_receivable_plan']
  before={};data={};schemas={}
  for t in tabs:
   q.execute('SELECT * FROM '+t+' WHERE deleted=0 ORDER BY id FOR UPDATE');rs=q.fetchall();before[t]=rs;data[t]={x['id']:x for x in rs}
   q.execute('SHOW COLUMNS FROM '+t);schemas[t]={x['Field'] for x in q.fetchall()}
  q.execute('SELECT status FROM system_users WHERE id=291 AND deleted=0');assert q.fetchone()['status']==0
  backup=P/('locked-before-'+now.strftime('%Y%m%d-%H%M%S')+'.json')
  backup.write_text(json.dumps({'state':state,'tables':before},ensure_ascii=False,default=str,indent=2),encoding='utf8')
  allowed={};expected={}
  def insert(t,v,day):
   v={**v,'creator':'291','updater':'291','create_time':day,'update_time':day,'deleted':0};assert set(v)<=schemas[t]
   q.execute('INSERT INTO '+t+' ('+','.join('`'+k+'`' for k in v)+') VALUES ('+','.join(['%s']*len(v))+')',list(v.values()));return q.lastrowid
  def update(t,id,v,day):
   v={**v,'updater':'291','update_time':day};assert set(v)<=schemas[t]
   q.execute('UPDATE '+t+' SET '+','.join('`'+k+'`=%s' for k in v)+' WHERE deleted=0 AND id=%s',[*v.values(),id]);assert q.rowcount==1
   allowed.setdefault((t,id),set()).update(v);expected.setdefault((t,id),{}).update(v)
  quotes=[x for x in before['crm_sale_quotation'] if '北京同仁堂' not in data['crm_customer'][x['customer_id']]['name']]
  seen=set()
  for qt in quotes:
   ct=data['crm_contract'][qt['contract_id']];assert ct['customer_id']==qt['customer_id'] and ct['total_price']==qt['total_price']
   b=data['crm_business'].get(ct['business_id']);assert b and b['customer_id']==qt['customer_id'] and b['total_price']==qt['total_price']
   assert qt['business_id'] in (None,b['id']) and b['id'] not in seen;seen.add(b['id'])
   order=data['erp_sale_order'][ct['order_id']];assert order['contract_id']==ct['id'] and order['customer_id']==qt['customer_id']
   assert b['order_id'] in (None,order['id']) and b['order_no'] in (None,'',order['no'])
   day=max(qt['quotation_time'],qt['create_time'],minimum);assert day<=now
   update('crm_sale_quotation',qt['id'],{'business_id':b['id'],'quotation_time':day,'create_time':day},max(qt['update_time'],day))
   quote_lines=[x for x in before['crm_sale_quotation_product'] if x['quotation_id']==qt['id']]
   contract_lines=[x for x in before['crm_contract_product'] if x['contract_id']==ct['id']];assert contract_lines
   assert sorted((x['item_id'],x['item_unit_id'],x['count']) for x in quote_lines)==sorted((x['item_id'],x['item_unit_id'],x['count']) for x in contract_lines)
   for l in quote_lines:
    if l['create_time']!=day:update('crm_sale_quotation_product',l['id'],{'create_time':day},max(l['update_time'],day))
   existing_products=[x for x in before['crm_business_product'] if x['business_id']==b['id']]
   assert not existing_products,'Existing business product rows require separate reconciliation'
   total=sum(x['total_price'] for x in contract_lines);assert total==ct['total_product_price']==ct['total_price'] and ct['discount_percent']==0
   for l in contract_lines:
    id=insert('crm_business_product',{'business_id':b['id'],'item_id':l['item_id'],'item_unit_id':l['item_unit_id'],'item_price':l['item_price'],'business_price':l['contract_price'],'count':l['count'],'total_price':l['total_price']},day);result['new_business_product_ids'].append(id)
   update('crm_business',b['id'],{'order_id':order['id'],'order_no':order['no'],'total_product_price':total,'discount_percent':Decimal(0)},now)
   permissions=[x for x in before['crm_permission'] if x['biz_type']==4 and x['biz_id']==b['id'] and x['user_id']==b['owner_user_id']]
   if not permissions:result['new_permission_ids'].append(insert('crm_permission',{'biz_type':4,'biz_id':b['id'],'user_id':b['owner_user_id'],'level':1},day))
   else:assert any(x['level']==1 for x in permissions),'Business owner permission is incomplete'
   result['quotes'].append({'id':qt['id'],'no':qt['no'],'customer':data['crm_customer'][qt['customer_id']]['name'],'business_id':b['id'],'contract_id':ct['id'],'order_id':order['id'],'date':str(day),'old_date':str(qt['quotation_time']),'date_changed':day!=qt['quotation_time'] or day!=qt['create_time'],'product_lines':len(contract_lines)})
  assert len(quotes)==16 and len(result['new_business_product_ids'])==34
  after={}
  for t,rows in before.items():
   q.execute('SELECT * FROM '+t+' WHERE deleted=0 ORDER BY id');rs=q.fetchall();after[t]=rs;byid={x['id']:x for x in rs}
   for old in rows:
    new=byid[old['id']];diff={k for k in old if old[k]!=new[k]};assert diff<=allowed.get((t,old['id']),set()),(t,old['id'],diff)
    for k,v in expected.get((t,old['id']),{}).items():assert new[k]==v
   newids=set(result['new_business_product_ids']) if t=='crm_business_product' else set(result['new_permission_ids']) if t=='crm_permission' else set()
   assert set(byid)-{x['id'] for x in rows}==newids
  for x in result['quotes']:
   qt=next(r for r in after['crm_sale_quotation'] if r['id']==x['id']);b=next(r for r in after['crm_business'] if r['id']==x['business_id'])
   assert minimum<=qt['quotation_time']==qt['create_time']<=now and qt['business_id']==b['id'] and qt['customer_id']==b['customer_id']
   assert sum(r['total_price'] for r in after['crm_business_product'] if r['business_id']==b['id'])==b['total_product_price']==b['total_price']
  c.commit()
  for t,rs in after.items():
   q.execute('SELECT * FROM '+t+' WHERE deleted=0 ORDER BY id');assert q.fetchall()==rs
  result.update(committed=True,verified=True,at=str(now),quotes_linked=len(quotes),quote_dates_changed=sum(x['date_changed'] for x in result['quotes']),business_products_added=len(result['new_business_product_ids']),backup=backup.name,beijing_tongrentang_data_unchanged=True,contract_invoice_receipt_order_amounts_dates_statuses_unchanged=True)
  (P/'result.json').write_text(json.dumps(result,ensure_ascii=False,indent=2),encoding='utf8')
  print(json.dumps({k:v for k,v in result.items() if k not in ['quotes','new_business_product_ids','new_permission_ids']},ensure_ascii=False))
except Exception:c.rollback();raise
finally:
 with c.cursor() as q:q.execute('SELECT RELEASE_LOCK(%s)',('quotation-opportunities-20261014',))
 c.close()