gaoluyang
2 天以前 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
"""Classify existing customer follow-up records from their explicit content prefix."""
import sys,json,csv
from pathlib import Path
from collections import Counter
P=Path(__file__).parent
sys.stdout.reconfigure(encoding='utf8')
sys.path.insert(0,str(P.parent/'iqc-import-20261012/deps'))
import pymysql
canon=lambda x:json.loads(json.dumps(x,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',autocommit=False,cursorclass=pymysql.cursors.DictCursor)
try:
 with c.cursor() as q:
  q.execute('SELECT DATABASE() db,NOW() now,GET_LOCK(%s,10) locked',('crm-follow-types',));state=q.fetchone();assert state['db']=='mom-hm' and state['locked']==1
  q.execute('SELECT * FROM crm_follow_up_record WHERE deleted=0 ORDER BY id FOR UPDATE');records=q.fetchall()
  q.execute("SELECT * FROM system_dict_data WHERE dict_type='crm_follow_up_type' ORDER BY id FOR UPDATE");dicts=q.fetchall()
  backup=P/('follow-types-backup-'+state['now'].strftime('%Y%m%d-%H%M%S')+'.json')
  backup.write_text(json.dumps({'state':state,'records':records,'dicts':dicts},ensure_ascii=False,default=str,indent=2),encoding='utf8')
  active=[d for d in dicts if d['deleted'] in (0,b'\x00') and d['status']==0]
  labels={d['label']:int(d['value']) for d in active}
  assert '资料梳理' in labels
  mapping={'档案梳理':'档案梳理','信息整理':'信息整理','跟进资料核对':'资料核对','资料跟进':'资料梳理'}
  prefixes={str(x['content'] or '').split(':',1)[0] for x in records if x['biz_type']==2}
  used={int(x['value']) for x in dicts if str(x['value']).isdigit()};added=[]
  for prefix,label in mapping.items():
   if prefix not in prefixes or label in labels:continue
   value=max(used or {0})+1;used.add(value);labels[label]=value
   q.execute('INSERT INTO system_dict_data (sort,label,value,dict_type,status,color_type,remark,creator,updater,create_time,update_time,deleted) VALUES (%s,%s,%s,%s,0,%s,%s,%s,%s,%s,%s,0)',(value,label,str(value),'crm_follow_up_type','info','依据客户跟进内容分类;不代表已进行电话或实地拜访','291','291',state['now'],state['now']))
   added.append({'id':q.lastrowid,'label':label,'value':value})
  plan=[];unmatched=[]
  for x in records:
   if x['biz_type']!=2:continue
   prefix=str(x['content'] or '').split(':',1)[0]
   if prefix not in mapping:unmatched.append({'id':x['id'],'type':x['type'],'reason':'内容未明确类型,保留现有值'});continue
   label=mapping[prefix];target=labels[label]
   changed=x['type']!=target
   if changed:
    q.execute('UPDATE crm_follow_up_record SET type=%s,updater=%s,update_time=%s WHERE id=%s AND deleted=0',(target,'291',state['now'],x['id']));assert q.rowcount==1
   plan.append({'id':x['id'],'customer_id':x['biz_id'],'old_type':x['type'],'type':target,'label':label,'changed':changed})
  q.execute('SELECT * FROM crm_follow_up_record WHERE deleted=0 ORDER BY id');after={x['id']:x for x in q.fetchall()};planned={x['id']:x for x in plan}
  assert set(after)=={x['id'] for x in records}
  for old in records:
   new=after[old['id']];p=planned.get(old['id']);diff={k for k in old if old[k]!=new[k]}
   assert diff<=({'type','updater','update_time'} if p and p['changed'] else set()),(old['id'],diff)
   if p:assert new['type']==p['type']
  q.execute("SELECT * FROM system_dict_data WHERE dict_type='crm_follow_up_type' ORDER BY id");after_dicts={x['id']:x for x in q.fetchall()}
  for d in dicts:assert after_dicts[d['id']]==d
  assert set(after_dicts)-{x['id'] for x in dicts}=={x['id'] for x in added}
  c.commit()
  # Verify committed rows without rewriting record content, creators, or business dates.
  q.execute('SELECT * FROM crm_follow_up_record WHERE deleted=0 ORDER BY id');assert q.fetchall()==list(after.values())
  summary={'committed':True,'verified':True,'at':str(state['now']),'added_types':added,'matched':len(plan),'changed':sum(x['changed'] for x in plan),'type_distribution':dict(Counter(x['label'] for x in plan)),'preserved_unmatched':unmatched,'backup':backup.name,'content_creator_and_original_dates_unchanged':True}
  (P/'follow-types-result.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2),encoding='utf8')
  (P/'follow-types-mapping.json').write_text(json.dumps(plan,ensure_ascii=False,indent=2),encoding='utf8')
  with (P/'客户跟进类型补充明细.csv').open('w',encoding='utf-8-sig',newline='') as f:
   w=csv.writer(f);w.writerow(['跟进ID','客户ID','原类型值','新类型值','类型名称','已修改'])
   for x in plan:w.writerow([x['id'],x['customer_id'],x['old_type'],x['type'],x['label'],x['changed']])
  print(json.dumps(summary,ensure_ascii=False))
except Exception:c.rollback();raise
finally:
 with c.cursor() as q:q.execute('SELECT RELEASE_LOCK(%s)',('crm-follow-types',))
 c.close()