gaoluyang
14 小时以前 e40a608ef9bf51db294d8eefada6a8e2e80cebca
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""Import reviewed native MES process masters and disabled routes, no backflush."""
import sys,json,datetime,unicodedata,re,csv
from pathlib import Path
P=Path(__file__).parent
sys.path.insert(0,str(P.parent/'iqc-import-20261012/deps'))
import pymysql
plan=json.loads((P/'route-plan.json').read_text(encoding='utf8'))
snapshot=json.loads((P/'remote-snapshot.json').read_text(encoding='utf8'))
assert len(plan['processes'])==46 and len(plan['routes'])==28
assert all(not st['backflush_flag'] and (not st['starred'] or st['check_flag']) for r in plan['routes'] for st in r['steps'])
assert all(sum(st['key_flag'] for st in r['steps'])==1 for r in plan['routes'])
def dump(n,v): (P/n).write_text(json.dumps(v,ensure_ascii=False,indent=2,default=str),encoding='utf8')
def normal(n):return re.sub(r'\s+','',unicodedata.normalize('NFKC',n))
def same(old,new):
    for k,v in new.items():
        actual=old[k]
        if isinstance(actual,bytes):actual=int.from_bytes(actual,'big')
        if actual!=v:return False
    return 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,connect_timeout=10,read_timeout=60)
log=[];newprocess=[];newroutes=[];newcontents=0;newsteps=0
try:
    with c.cursor() as q:
        q.execute('SELECT DATABASE() db');assert q.fetchone()['db']=='mom-hm'
        q.execute("SELECT GET_LOCK('stp-routes-20261012',10) ok");assert q.fetchone()['ok']==1
        c.begin();before={}
        tables=['mes_pro_process','mes_pro_process_content','mes_pro_route','mes_pro_route_process','mes_pro_route_product','mes_pro_route_product_bom']
        for t in tables:
            q.execute('SELECT ENGINE FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name=%s',(t,));assert q.fetchone()['ENGINE']=='InnoDB'
            q.execute('SELECT * FROM '+t+' WHERE deleted=0 ORDER BY id FOR UPDATE');before[t]=list(q.fetchall())
        dump('before-route-import.json',before)
        for t in tables:assert [(x['id'],x.get('code')) for x in before[t]]==[(x['id'],x.get('code')) for x in sorted(snapshot[t],key=lambda x:x['id'])], 'Remote data changed; review again'
        def insert(t,values):
            cols=list(values);sql='INSERT INTO '+t+' (`'+'`,`'.join(cols)+"`,creator,updater) VALUES ("+','.join(['%s']*len(cols))+",'1','1')"
            args=tuple(values[k] for k in cols);log.append(q.mogrify(sql,args)+';');q.execute(sql,args);return q.lastrowid
        def insert_many(t,values):
            if not values:return
            cols=list(values[0]);sql='INSERT INTO '+t+' (`'+'`,`'.join(cols)+"`,creator,updater) VALUES ("+','.join(['%s']*len(cols))+",'1','1')"
            args=[tuple(v[k] for k in cols) for v in values]
            log.extend(q.mogrify(sql,a)+';' for a in args);q.executemany(sql,args)
        processes={normal(x['name']):x for x in before['mes_pro_process']}
        codes={x['code']:x for x in before['mes_pro_process']}
        nameids={}
        for m in plan['processes']:
            existing=processes.get(normal(m['name']))
            if existing:
                m['id']=existing['id'];m['action']='复用现有工序'
            else:
                assert m['code'] not in codes,'Process code occupied'
                m['id']=insert('mes_pro_process',dict(code=m['code'],name=m['name'],attention=m['attention'],status=0,
                      remark='来源:生产工艺规程工序去重;参数按原文产品和版本执行。'))
                newprocess.append(m['id']);m['action']='新增工序'
                contentrows=[]
                for idx,ev in enumerate(m['contents'],1):
                    prefix='【'+ev['document_code']+';段落'+str(ev['paragraph'])+'】'
                    raw=ev['text'];parts=[raw[i:i+500-len(prefix)] for i in range(0,len(raw),500-len(prefix))]
                    for part in parts:contentrows.append(dict(process_id=m['id'],sort=len(contentrows)+1,content=prefix+part,
                        remark='原文摘录;仅适用所列产品规程。共同工序的不同版本参数不合并。'))
                if not contentrows:contentrows.append(dict(process_id=m['id'],sort=1,content='来源流程图:'+m['name']+'。详细参数以对应原规程确认。',remark='来源:'+','.join(m['sources'])))
                insert_many('mes_pro_process_content',contentrows);newcontents+=len(contentrows)
            nameids[m['name']]=m['id']
        oldroutes={x['code']:x for x in before['mes_pro_route']}
        for r in plan['routes']:
            notes=';'.join(r['issues'])
            description='来源:'+r['document_code']+';保留原文工序顺序,全部非倒冲、手动领料;未启用,待核实产品/BOM和参数。'
            if r['document_id']==15:description+='红、黑枸杞分支独立制浆,在搅拌乳化处合流。'
            header=dict(code=r['code'],name=r['name'],description=description,status=1,remark=(notes or '按规程生成;系统唯一产出关键工序用于数量统计,原文*控制点逐项设置需要质检。'))
            # Long exception text stays complete in JSON/CSV; database remark gives a pointer.
            if len(header['remark'])>255:header['remark']=header['remark'][:220]+';完整事项见来源规程及生成清单。'
            assert len(header['name'])<=100 and len(header['description'])<=255
            old=oldroutes.get(r['code'])
            if old:assert same(old,header);r['id']=old['id'];r['action']='复用现有路线'
            else:r['id']=insert('mes_pro_route',header);newroutes.append(r['id']);r['action']='新增未启用路线'
            values=[]
            for i,st in enumerate(r['steps']):
                st['process_id']=nameids[st['name']]
                nextpid=nameids[r['steps'][i+1]['name']] if i+1<len(r['steps']) else None
                remark=st['remark']
                if r['document_id']==15 and st['name'] in ['红枸杞原浆制备','黑果枸杞原浆制备']:
                    remark+=';原料支线独立加工,顺序仅用于系统编排,合流于搅拌乳化剪切。'
                if r['document_id']==7 and st['name']=='混合':remark+=';时间冲突:图/监控表40min、正文30min,待核实。'
                if r['document_id']==10 and st['name'] in ['浸泡','淋干','高温杀菌','粉碎','过筛']:remark+=';此段仅见原文流程图,详细操作范围待核实。'
                if st['name'] in ['微波杀菌','水浴灭菌','均质','瞬时灭菌','热灌装'] and st['evidence']:
                    parameter=next((x['text'] for x in st['evidence'] if re.search(r'\d+(?:[.~~\-±]|℃|min|s|Mpa|ml)',x['text'])),None)
                    if parameter:
                        # Retain the original paragraph as source, without selecting a universal setpoint.
                        room=255-len(remark)-2
                        if room>20:remark+=';'+parameter[:room]
                assert len(remark)<=255
                value=dict(route_id=r['id'],process_id=st['process_id'],sort=st['sort'],next_process_id=nextpid,
                    link_type=3,prepare_time=None,wait_time=None,key_flag=int(st['key_flag']),check_flag=int(st['check_flag']),
                    backflush_flag=0,remark=remark)
                values.append(value);st['stored']=value
            if old:
                q.execute('SELECT * FROM mes_pro_route_process WHERE route_id=%s AND deleted=0 ORDER BY sort',(r['id'],));existing=q.fetchall()
                assert len(existing)==len(values) and all(same(a,b) for a,b in zip(existing,values))
            else:insert_many('mes_pro_route_process',values);newsteps+=len(values)
        # No products, BOM, or existing route rows altered by this transaction.
        for t in ['mes_pro_route_product','mes_pro_route_product_bom']:
            q.execute('SELECT * FROM '+t+' WHERE deleted=0 ORDER BY id');assert list(q.fetchall())==before[t]
        for row in before['mes_pro_route']:
            q.execute('SELECT * FROM mes_pro_route WHERE id=%s',(row['id'],));assert q.fetchone()==row
        dump('route-plan-with-ids.json',plan)
        (P/'route-import-executed.sql').write_text('START TRANSACTION;\n'+'\n'.join(log)+'\nCOMMIT;\n',encoding='utf8')
        c.commit()
        for r in plan['routes']:
            q.execute('SELECT * FROM mes_pro_route WHERE id=%s',(r['id'],));row=q.fetchone();assert row['status']==1 and row['code']==r['code']
            q.execute('SELECT * FROM mes_pro_route_process WHERE route_id=%s AND deleted=0 ORDER BY sort',(r['id'],));found=q.fetchall()
            assert len(found)==len(r['steps']) and all(same(a,b['stored']) for a,b in zip(found,r['steps']))
        for t,n in [('mes_pro_process',len(newprocess)),('mes_pro_process_content',newcontents),('mes_pro_route',len(newroutes)),('mes_pro_route_process',newsteps)]:
            q.execute('SELECT COUNT(*) n FROM '+t+' WHERE deleted=0');assert q.fetchone()['n']==len(before[t])+n
        result=dict(target='36.213.150.237:10000/mom-hm',source_files=26,unique_documents=24,duplicates=plan['duplicate_documents'],
                    new_processes=len(newprocess),new_process_contents=newcontents,new_routes=len(newroutes),new_route_steps=newsteps,
                    starred_check_steps=sum(st['starred'] for r in plan['routes'] for st in r['steps']),
                    check_steps=sum(st['check_flag'] for r in plan['routes'] for st in r['steps']),all_backflush_false=True,
                    route_status='未启用',products_bound=0,existing_test_route_preserved=True,post_commit_verified=True)
        dump('route-import-result.json',result)
        with (P/'工艺路线生成结果.csv').open('w',encoding='utf-8-sig',newline='') as f:
            w=csv.writer(f);w.writerow(['路线ID','路线编码','路线名称','来源文件编号','工序数','需质检数','星号质检数','倒冲','状态','工序顺序','待核实事项'])
            for r in plan['routes']:w.writerow([r['id'],r['code'],r['name'],r['document_code'],len(r['steps']),sum(st['check_flag'] for st in r['steps']),sum(st['starred'] for st in r['steps']),False,'未启用',' → '.join(st['name']+('*' if st['starred'] else '') for st in r['steps']),';'.join(r['issues']+r['binding_issues'])])
        print(json.dumps(result,ensure_ascii=False))
        q.execute("SELECT RELEASE_LOCK('stp-routes-20261012')")
except Exception:c.rollback();raise
finally:c.close()