import sys,json,csv
|
from pathlib import Path
|
from datetime import datetime
|
from collections import Counter
|
P=Path(__file__).parent
|
sys.path.insert(0,str(P.parent/'iqc-import-20261012/deps'))
|
import pymysql
|
s=json.loads((P/'before-log-business-actors.json').read_text(encoding='utf8'))
|
old_records=json.loads((P/'operation-log-supplement-records.json').read_text(encoding='utf8'))
|
record_by_trace={r['trace_id']:r for r in old_records}
|
roles={'mes_qc_iqc':('inspector_user_id','检验人员'),
|
'mes_qc_oqc':('inspector_user_id','检验人员'),
|
'crm_contract':('owner_user_id','合同负责人'),
|
'erp_sale_order':('sale_user_id','销售员')}
|
encode=lambda r:json.dumps(r,ensure_ascii=False,default=str,sort_keys=True)
|
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)
|
result_rows=[];changes=[];unresolved=[]
|
try:
|
with c.cursor() as q:
|
q.execute("SELECT GET_LOCK('codex-business-metadata-log-v1',0) locked")
|
assert q.fetchone()['locked']==1
|
q.execute('SELECT NOW() now');now=q.fetchone()['now']
|
q.execute('SELECT id,nickname FROM system_users WHERE deleted=0')
|
users={str(r['id']):r for r in q.fetchall()}
|
q.execute("SELECT * FROM system_operate_log WHERE trace_id LIKE 'BF-META-V1:%' FOR UPDATE")
|
logs=q.fetchall();by_trace={r['trace_id']:r for r in logs}
|
assert len(logs)==len(s['logs'])==len(old_records)==113
|
assert all(encode(by_trace[r['trace_id']])==encode(r) for r in s['logs']), 'Supplemented logs changed since snapshot'
|
q.execute('SELECT * FROM system_operate_log ORDER BY id');before=q.fetchall()
|
(P/'before-operation-log-actor-correction.json').write_text(encode(before),encoding='utf8')
|
for log in logs:
|
trace=log['trace_id'];old=record_by_trace[trace];extra=json.loads(log['extra'])
|
table=extra['source_table'];biz_id=log['biz_id']
|
actor_field,role=roles.get(table,('creator','建档人员(业务表未设专用人员字段)'))
|
time_field='inspect_date' if table in ['mes_qc_iqc','mes_qc_oqc'] else 'create_time'
|
q.execute('SELECT `'+actor_field+'` actor, `'+time_field+'` event_time FROM `'+table+'` WHERE id=%s AND deleted=0 FOR SHARE',(biz_id,))
|
source=q.fetchone()
|
if not source or str(source['actor']) not in users or source['event_time'] is None:
|
unresolved.append({'trace_id':trace,'source_table':table,'biz_id':biz_id,'reason':'业务人员或时间无法确认'})
|
continue
|
user=users[str(source['actor'])];name=user['nickname'].strip();event=source['event_time']
|
if not isinstance(event,datetime):event=datetime.combine(event,datetime.min.time())
|
assert event<=now
|
is_qc=time_field=='inspect_date'
|
when=event.strftime('%Y-%m-%d') if is_qc else str(event)
|
subtype='历史数据补录(检验记录)' if is_qc else '历史数据补录(业务归属)'
|
extra.update({'reconstructed':True,'evidence':'business_record_role_and_date' if is_qc else 'business_record_actor_field',
|
'actor_source_field':actor_field,'business_actor_user_id':user['id'],'business_actor_role':role,
|
'event_time_source_field':time_field,'event_time_precision':'day' if is_qc else 'second',
|
'corrected_at':str(now),'attribution':'依据业务人员字段归属,不证明原始请求操作者'})
|
action=(f'【历史数据补录/业务记录归属】{log["type"]}记录“{old["source_name"]}”(ID:{biz_id});'
|
f'{role}:{name}({user["id"]}),依据字段:{actor_field};'
|
f'{"检测日期" if is_qc else "建档时间"}:{when}。'
|
+('原数据仅有日期,日志零点为日期展示值,不代表实际操作时刻。' if is_qc else '')
|
+f'实际补录时间:{extra["recorded_at"]},归属修正时间:{now}。仅依据业务记录补录,非原始操作请求日志。')
|
assert len(action)<=2000 and len(encode(extra))<=2000
|
q.execute('UPDATE system_operate_log SET user_id=%s,sub_type=%s,action=%s,extra=%s,create_time=%s,update_time=%s WHERE id=%s AND trace_id=%s',
|
(user['id'],subtype,action,encode(extra),event,now,log['id'],trace))
|
assert q.rowcount==1
|
updated=dict(old,user_id=user['id'],sub_type=subtype,action=action,extra=encode(extra),create_time=str(event),update_time=str(now),source_user_name=name,business_role=role,actor_source_field=actor_field,event_time_precision=extra['event_time_precision'])
|
result_rows.append(updated)
|
changes.append({'log_id':log['id'],'business_table':table,'business_id':biz_id,'before_user_id':log['user_id'],'after_user_id':user['id'],'actor':name,'role':role,'actor_source_field':actor_field,'before_time':str(log['create_time']),'after_time':str(event),'date_precision':extra['event_time_precision']})
|
assert not unresolved,encode(unresolved)
|
q.execute('SELECT * FROM system_operate_log ORDER BY id');after={r['id']:r for r in q.fetchall()}
|
target_ids={r['id'] for r in logs}
|
assert all(encode(r)==encode(after[r['id']]) for r in before if r['id'] not in target_ids), 'Original logs changed'
|
assert all(after[x['log_id']]['user_id']==x['after_user_id'] and str(after[x['log_id']]['create_time'])==x['after_time'] for x in changes)
|
c.commit()
|
result={'updated_supplemented_logs':len(changes),'changed_actor_count':sum(r['before_user_id']!=r['after_user_id'] for r in changes),
|
'changed_time_count':sum(r['before_time']!=r['after_time'] for r in changes),'by_business_actor':dict(Counter(r['actor'] for r in changes)),
|
'by_role':dict(Counter(r['role'] for r in changes)),'unresolved':unresolved,'original_logs_unchanged':len(before)-len(logs),'corrected_at':str(now)}
|
(P/'operation-log-actor-correction-result.json').write_text(encode(result),encoding='utf8')
|
(P/'operation-log-actor-corrections.json').write_text(encode(changes),encoding='utf8')
|
(P/'operation-log-supplement-records.json').write_text(encode(result_rows),encoding='utf8')
|
with (P/'操作日志补录清单.csv').open('w',encoding='utf-8-sig',newline='') as f:
|
w=csv.writer(f);w.writerow(['模块','业务ID','业务编号或名称','业务人员','人员ID','人员角色','人员来源字段','日志显示时间','时间精度','实际补录时间','日志类型','来源表','追踪编号'])
|
for r in result_rows:w.writerow([r['type'],r['biz_id'],r['source_name'],r['source_user_name'],r['user_id'],r['business_role'],r['actor_source_field'],r['create_time'],r['event_time_precision'],r['recorded_at'],r['sub_type'],r['source_table'],r['trace_id']])
|
print(encode(result))
|
finally:c.close()
|