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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
| """Source-backed local import. Default is a transactional rollback rehearsal."""
| from audit import connect,dump,P
| import sys,json,re,math,socket
| from decimal import Decimal,ROUND_CEILING,ROUND_HALF_UP
| from datetime import datetime,timedelta,date
| from collections import defaultdict
| sys.stdout.reconfigure(encoding='utf-8')
| D=Decimal;APPLY='--apply' in sys.argv
| source=json.loads((P/'source.json').read_text(encoding='utf-8'))
| schema=json.loads((P/'schema.json').read_text(encoding='utf-8'))
| fields={t:{r['Field']:r for r in rows} for t,rows in schema.items()}
| TODAY=date(2026,10,15);BASE=datetime(2026,10,15,8,0)
| inserted=defaultdict(list);updates=[];log=[];links=[];sequences={}
| NOTE='来源:10月15日电商未缴库计划;按表列订单数量及结算单价执行,未另加税额。'
|
| def redis(*args):
| with socket.create_connection(('127.0.0.1',6379),timeout=5) as sock:
| f=sock.makefile('rb')
| def command(parts):
| data=[str(x).encode() for x in parts];sock.sendall(b'*'+str(len(data)).encode()+b'\r\n'+b''.join(b'$'+str(len(x)).encode()+b'\r\n'+x+b'\r\n' for x in data))
| line=f.readline();kind=line[:1];v=line[1:-2]
| if kind==b'-':raise RuntimeError(v.decode())
| if kind==b'$':return None if int(v)<0 else f.read(int(v)+2)[:-2].decode()
| return int(v) if kind==b':' else v.decode()
| command(['SELECT',5]);return command(args)
|
| def number(table,col,prefix,width,rule=None):
| q.execute('select `'+col+'` value from `'+table+'` where `'+col+'` like %s',(prefix+'%',))
| floor=max([int(r['value'][len(prefix):]) for r in q.fetchall() if r['value'][len(prefix):].isdigit()] or [0])
| key=('mes:md:auto_code:'+str(rule)+':20261015') if rule else ('erp:seq_no:' if table=='erp_sale_order' else 'crm:seq_no:')+prefix
| if key not in sequences:sequences[key]=max(floor,int(redis('GET',key) or 0))
| if APPLY:
| n=redis('EVAL',"local x=tonumber(redis.call('GET',KEYS[1]) or '0'); if x<tonumber(ARGV[1]) then redis.call('SET',KEYS[1],ARGV[1]); end; local n=redis.call('INCR',KEYS[1]); redis.call('EXPIRE',KEYS[1],172800); return n",1,key,max(floor,sequences[key]))
| else:n=sequences[key]+1
| sequences[key]=n;result=prefix+str(n).zfill(width)
| if rule:ins('mes_md_auto_code_record',{'rule_id':rule,'result':result,'serial_no':n},244,BASE)
| return result
|
| def ins(table,values,user=244,at=BASE):
| v=dict(values);f=fields[table]
| for key,value in [('creator',str(user)),('updater',str(user)),('create_time',at),('update_time',at),('deleted',0),('tenant_id',0)]:
| if key in f:v.setdefault(key,value)
| assert not set(v)-set(f),(table,set(v)-set(f))
| for key,val in v.items():
| if isinstance(val,str):
| match=re.match(r'varchar\((\d+)\)',f[key]['Type']);assert not match or len(val)<=int(match[1]),(table,key,len(val))
| keys=list(v);sql='insert into `'+table+'` (`'+'`,`'.join(keys)+'`) values('+','.join(['%s']*len(keys))+')'
| q.execute(sql,list(v.values()));rid=q.lastrowid;inserted[table].append(rid);log.append(q.mogrify(sql,list(v.values()))+';');return rid
|
| def change(table,rid,values):
| q.execute('select * from `'+table+'` where id=%s for update',(rid,));before=q.fetchone();assert before
| sql='update `'+table+'` set '+','.join('`'+k+'`=%s' for k in values)+' where id=%s'
| args=list(values.values())+[rid];q.execute(sql,args);log.append(q.mogrify(sql,args)+';');updates.append({'table':table,'id':rid,'before':before,'after':values})
|
| def fetch(table):q.execute('select * from `'+table+'` where deleted=0');return q.fetchall()
| def money(v):return D(str(v)).quantize(D('.01'),rounding=ROUND_HALF_UP)
| def truth(v):return int(v in (True,1,b'\x01',"b'\\x01'"))
| def next_work(dt):
| while dt.weekday()>4 or dt.date() in holidays:dt=(dt+timedelta(days=1)).replace(hour=9,minute=0,second=0)
| return dt
| def add_hours(start,hours):
| now=start;remaining=hours*60
| while remaining:
| now=next_work(now);end=now.replace(hour=18,minute=0,second=0);available=int((end-now).total_seconds()/60)
| if available<=0:now=next_work((now+timedelta(days=1)).replace(hour=9,minute=0,second=0));continue
| take=min(remaining,available);now+=timedelta(minutes=take);remaining-=take
| if remaining:now=next_work((now+timedelta(days=1)).replace(hour=9,minute=0,second=0))
| return now
|
| c=connect()
| try:
| with c.cursor() as q:
| q.execute('select get_lock(%s,10) ok',('ecommerce-production-20261015',));assert q.fetchone()['ok']==1
| q.execute('select count(*) n from crm_sale_quotation where deleted=0 and remark like %s',('%来源:10月15日电商未缴库计划%',));assert q.fetchone()['n']==0,'Already imported'
| assert len(source)==13 and sum(money(r['amount']) for r in source)==D('2062353.18')
| for r in source:assert money(D(str(r['quantity']))*D(str(r['price'])))==money(r['amount'])
| a={t:fetch(t) for t in ['mdm_item','mes_md_item','mes_pro_route','mes_pro_route_product','mes_pro_route_process','mes_md_product_bom','mes_pd_process_param_template','mes_pd_process_param_template_detail','mes_md_workstation','mes_md_workstation_machine','mes_dv_machinery','mes_cal_holiday','mes_pro_task']}
| holidays={r['day'].date() for r in a['mes_cal_holiday'] if r['type']==2}
| assert not any(TODAY<=d<=date(2026,10,31) for d in holidays)
| q.execute('select id,name from crm_customer where id=1613 and deleted=0');assert q.fetchone()['name']=='总公司'
| before_counts={}
| for t in ['crm_receivable','mes_wm_transaction','mes_wm_material_stock','mes_wm_product_receipt','mes_wm_product_sales']:
| q.execute('select count(*) n from `'+t+'`');before_counts[t]=q.fetchone()['n']
| dump('protected-counts-before.json',before_counts)
| mdm={r['id']:r for r in a['mdm_item']};mes={r['mdm_item_id']:r for r in a['mes_md_item']};by_code={r['code']:r for r in mdm.values()}
| units={'盒':9,'袋':3,'瓶':8,'套':64}
| # New source SKUs use their exact source code. Never substitute a differently sized product.
| for code,base,name,spec,unit in [('12100271',538,'黑果枸杞','90克',8),('UC000017',965,'雪菊','50g',8),('U2100282',501,'桑椹原浆','500ml(50ml×10)',9)]:
| if code in by_code:continue
| q.execute('select greatest((select max(id) from mdm_item),(select max(id) from mes_md_item))+1 id');pid=q.fetchone()['id']
| parent=mdm[base];mid=ins('mdm_item',{'id':pid,'code':code,'name':name,'specification':spec,'category_id':parent['category_id'],'unit_measure_id':unit,'item_type':3,'status':0,'is_batch_managed':1,'is_serial_managed':0,'high_value':0,'warehouse_id':parent.get('warehouse_id'),'remark':'来源10月15日电商未缴库计划;配方及投料BOM待工艺维护,不套用其他规格配方。'},244)
| base_mes=mes[base];v={k:base_mes[k] for k in ['category_id','item_type','status'] if k in base_mes}
| ins('mes_md_item',{'id':pid,'mdm_item_id':pid,'code':code,'name':name,'specification':spec,'unit_measure_id':unit,**v,'remark':'来源10月15日电商未缴库计划'},244)
| mdm[pid]={**parent,'id':pid,'code':code,'name':name,'specification':spec,'unit_measure_id':unit};mes[pid]={'id':pid,'mdm_item_id':pid,'code':code,'name':name,'specification':spec,'unit_measure_id':unit};by_code[code]=mdm[pid]
| # Snow chrysanthemum needs a packaging flow, not the unrelated berry processing recipe.
| flower=next((r for r in a['mes_pro_route'] if r['code']=='RT-FLOWER-BOTTLE-PACK'),None)
| if flower:flower_id=flower['id']
| else:
| flower_id=ins('mes_pro_route',{'code':'RT-FLOWER-BOTTLE-PACK','name':'花类瓶装分装包装工艺路线','status':0,'remark':'用于50g雪菊分装包装;工序沿用现有基础工序,不新增配方参数。'})
| for n,process in enumerate([1,2,3,10,11,12,13]):
| original=next(r for r in a['mes_pro_route_process'] if r['process_id']==process)
| data={k:original.get(k) for k in ['param_template_id','yield_rate','color_code']}
| data.update(route_id=flower_id,process_id=process,sort=(n+1)*10,next_process_id=([1,2,3,10,11,12,13][n+1] if n<6 else None),link_type=3,key_flag=int(process==10),check_flag=int(process==12),backflush_flag=0)
| rid=ins('mes_pro_route_process',data);a['mes_pro_route_process'].append({'id':rid,**data})
| assignments=[('Q2100202',23,20,0),('Q2100201',20,23,0),('U2100335',56,27,0),('U2100336',56,21,0),('Q2100197',57,22,1),('Q2100073',13,21,2),('Q2100314',18,30,1),('Q2100077',13,26,2),('Q2100080',36,19,1),('Q2100078',13,28,2),('12100271',13,29,2),('UC000017',flower_id,30,2),('U2100282',17,16,3)]
| planned_start_days=[15,20,22,19,20,19,21,22,16,26,27,28,16]
| groups=['营养保健','原浆供货','枸杞及雪菊','桑椹原浆']
| grouped=defaultdict(list)
| for i,(code,route,day,group) in enumerate(assignments):
| row=source[i];item=by_code[code];pid=item['id'];mid=mes[pid]['id'];unit=units[row['unit']]
| # Packaging counts are 1 source finished package; preserve historical document units.
| if item['unit_measure_id']!=unit:
| assert item['unit_measure_id']==24
| change('mdm_item',pid,{'unit_measure_id':unit});change('mes_md_item',mid,{'unit_measure_id':unit});item['unit_measure_id']=unit;mes[pid]['unit_measure_id']=unit
| for table,rid in [('mdm_item',pid),('mes_md_item',mid)]:
| if (table=='mdm_item' and (item.get('name')!=row['name'] or item.get('specification')!=row['spec'])):
| change(table,rid,{'name':row['name'],'specification':row['spec']})
| elif table=='mes_md_item' and (mes[pid].get('name')!=row['name'] or mes[pid].get('specification')!=row['spec']):change(table,rid,{'name':row['name'],'specification':row['spec']})
| row={**row,'mdm_id':pid,'mes_id':mid,'route':route,'unit_id':unit,'due_date':datetime(2026,10,day,18),'planned_start':datetime(2026,10,planned_start_days[i],9),'group':group};grouped[group].append(row)
| if not any(r['item_id']==mid and r['route_id']==route for r in a['mes_pro_route_product']):ins('mes_pro_route_product',{'route_id':route,'item_id':mid,'remark':'按10月15日计划产品配置;配方及投料沿用产品自身BOM,不套用其他规格。'})
| group_refs={}
| for group,rows in grouped.items():
| amount=sum(money(r['amount']) for r in rows);count=sum(D(str(r['quantity'])) for r in rows)
| quote_no=number('crm_sale_quotation','no','QT20261015',6)
| quote=ins('crm_sale_quotation',{'no':quote_no,'name':'总公司10月'+groups[group]+'供货报价','customer_id':1613,'owner_user_id':291,'status':10,'quotation_time':BASE+timedelta(minutes=5+group),'valid_until':datetime(2026,10,30,18),'total_product_price':amount,'discount_percent':0,'discount_price':0,'total_price':amount,'tax_price':0,'remark':NOTE},291,BASE+timedelta(minutes=5+group))
| contract_no=number('crm_contract','no','HT20261015',6)
| contract=ins('crm_contract',{'no':contract_no,'name':'总公司10月'+groups[group]+'供货合同','customer_id':1613,'owner_user_id':291,'audit_status':20,'order_date':BASE+timedelta(minutes=20+group),'start_time':BASE+timedelta(minutes=20+group),'end_time':datetime(2026,11,30,18),'total_product_price':amount,'total_price':amount,'discount_percent':0,'deposit_price':0,'remark':NOTE+' 月结,2026年11月30日回款;未录实际收款。'},291,BASE+timedelta(minutes=20+group))
| sale_no=number('erp_sale_order','no','XSDD20261015',6)
| order=ins('erp_sale_order',{'no':sale_no,'status':20,'customer_id':1613,'contract_id':contract,'contract_no':contract_no,'sale_user_id':291,'order_time':BASE+timedelta(minutes=40+group),'total_count':count,'total_product_price':amount,'total_tax_price':0,'total_price':amount,'discount_percent':0,'discount_price':0,'deposit_price':0,'out_count':0,'out_status':0,'production_status':0,'remark':NOTE+' 未缴库、未发货。'},291,BASE+timedelta(minutes=40+group))
| change('crm_contract',contract,{'order_id':order,'order_no':sale_no});change('crm_sale_quotation',quote,{'contract_id':contract,'contract_no':contract_no})
| rp=ins('crm_receivable_plan',{'period':1,'customer_id':1613,'contract_id':contract,'owner_user_id':291,'return_time':datetime(2026,11,30,16),'return_type':8,'price':amount,'remind_days':3,'remind_time':datetime(2026,11,27,9),'remark':'总公司10月供货月结,11月30日一次回款;仅建计划,尚未收款。'},291,BASE+timedelta(minutes=25+group))
| for biz,bid in [(9,quote),(5,contract),(8,rp)]:ins('crm_permission',{'biz_type':biz,'biz_id':bid,'user_id':291,'level':1},291)
| for row in rows:
| value={'item_id':row['mdm_id'],'item_unit_id':row['unit_id'],'item_price':D(str(row['price'])),'count':row['quantity'],'total_price':money(row['amount'])}
| ins('crm_sale_quotation_product',{'quotation_id':quote,**value,'quotation_price':D(str(row['price'])),'tax_price':0},291,BASE+timedelta(minutes=10+group))
| ins('crm_contract_product',{'contract_id':contract,**value,'contract_price':D(str(row['price']))},291,BASE+timedelta(minutes=20+group))
| provenance='原项目号:'+str(row['code'])+';'+(row['note'] or '')+(';原进度:'+str(row['due']) if row['due'] else '')
| line=ins('erp_sale_order_items',{'order_id':order,'product_id':row['mdm_id'],'product_unit_id':row['unit_id'],'product_price':D(str(row['price'])),'count':row['quantity'],'total_price':money(row['amount']),'tax_price':0,'out_count':0,'need_production':1,'remark':provenance},291,BASE+timedelta(minutes=40+group))
| wo_code=number('mes_pro_work_order','code','WO_20261015',3,30)
| wo=ins('mes_pro_work_order',{'code':wo_code,'name':row['name']+'生产工单','type':1,'order_source_type':1,'order_source_code':sale_no,'product_id':row['mes_id'],'quantity':row['quantity'],'quantity_produced':0,'quantity_scheduled':0,'quantity_changed':0,'client_id':1613,'route_id':row['route'],'request_date':row['due_date'],'parent_id':0,'status':1,'auto_finish_flag':1,'is_feedback_approve':0,'remark':'来源10月15日未缴库计划;计划开工'+row['planned_start'].strftime('%m月%d日')+';'+(row['note'] or '待排产;未提前报工')},244,BASE+timedelta(hours=1,minutes=10))
| mps_code=number('mes_pro_mps','code','MPS20261015',3,65)
| mps=ins('mes_pro_mps',{'code':mps_code,'status':1,'sale_order_id':order,'sale_order_no':sale_no,'sale_order_item_id':line,'product_id':row['mes_id'],'quantity':row['quantity'],'quantity_produced':0,'unit_measure_id':row['unit_id'],'client_id':1613,'request_date':row['due_date'],'issue_date':BASE+timedelta(hours=1,minutes=10),'work_order_id':wo,'work_order_code':wo_code,'merge_flag':0,'remark':'来源:'+row['sheet']+'第'+str(row['row'])+'行;计划开工'+row['planned_start'].strftime('%m月%d日')+';'+provenance},244,BASE+timedelta(hours=1))
| change('erp_sale_order_items',line,{'mps_id':mps})
| processes=sorted([r for r in a['mes_pro_route_process'] if r['route_id']==row['route']],key=lambda r:r['sort'])
| # The two specified semi-finished products need only their remaining packaging stages.
| if row['note'] and '半成品已生产完毕' in row['note']:processes=[r for r in processes if r['process_id'] in (11,12,13)]
| assert processes
| for n,p in enumerate(processes):
| v={k:p.get(k) for k in ['process_id','param_template_id','yield_rate','color_code','key_flag','check_flag','backflush_flag','output_item_id','prepare_time','wait_time']}
| for k in ['key_flag','check_flag','backflush_flag']:v[k]=truth(v[k])
| v.update(work_order_id=wo,sort=(n+1)*10,link_type=3,next_process_id=processes[n+1]['process_id'] if n+1<len(processes) else None)
| ins('mes_pro_work_order_process',v)
| bom=[r for r in a['mes_md_product_bom'] if r['item_id']==row['mes_id']]
| for b in bom:ins('mes_pro_work_order_bom',{'work_order_id':wo,'item_id':b['bom_item_id'],'quantity':D(str(b['quantity']))*D(str(row['quantity']))})
| links.append({**row,'quotation_id':quote,'quotation_no':quote_no,'contract_id':contract,'contract_no':contract_no,'order_id':order,'order_no':sale_no,'order_item_id':line,'receivable_plan_id':rp,'mps_id':mps,'mps_code':mps_code,'wo_id':wo,'wo_code':wo_code,'bom_count':len(bom),'processes':processes})
| group_refs[group]={'quotation_id':quote,'contract_id':contract,'order_id':order,'amount':amount,'receivable_plan_id':rp}
| # One actual in-progress packaging job today; future jobs remain pending scheduling.
| current=next(r for r in links if r['mdm_id']==517)
| stations={r['process_id']:r for r in a['mes_md_workstation'] if r['status']==0}
| station_machines=defaultdict(set)
| for r in a['mes_md_workstation_machine']:station_machines[r['workstation_id']].add(r['machinery_id'])
| for m in a['mes_dv_machinery']:
| if m.get('workstation_id'):station_machines[m['workstation_id']].add(m['id'])
| machines={r['id']:r for r in a['mes_dv_machinery']}
| existing=[t for t in a['mes_pro_task'] if t['status'] not in (4,5)]
| first_station=stations[11]['id'];busy=[t['end_time'] for t in existing if t['workstation_id']==first_station and t['end_time']]
| start=max([datetime(2026,10,15,9)]+busy)
| assert start.date()==TODAY and start<datetime.now(),('Cannot mark a future task in progress',start)
| cumulative=D(1);targets={}
| for p in reversed(current['processes']):
| targets[p['process_id']]=(D(current['quantity'])/cumulative).to_integral_value(rounding=ROUND_CEILING)
| cumulative*=D(str(p.get('yield_rate') or 1))
| task_ids=[]
| for n,p in enumerate(current['processes']):
| station=stations[p['process_id']];resources=[machines[mid] for mid in station_machines[station['id']] if machines[mid]['status'] in (1,2)]
| assert resources and all(m['production_efficiency'] and m['production_efficiency']>0 for m in resources)
| capacity=sum(D(str(m['production_efficiency']))*D(m.get('conveyor_belt') or 1) for m in resources)
| amount=targets[p['process_id']];hours=int((amount/capacity).to_integral_value(rounding=ROUND_CEILING));hours=max(hours,1)
| for t in existing:
| if t['workstation_id']==station['id'] and t['end_time'] and t['end_time']>start:start=t['end_time']
| start=next_work(start);end=add_hours(start,hours);assert end.date()<=date(2026,10,30)
| code=number('mes_pro_task','code','TK_20261015',3,31)
| done=D(800) if n==0 else D(0);assert done<amount
| task=ins('mes_pro_task',{'code':code,'name':current['name']+'-'+str(p['process_id']),'work_order_id':current['wo_id'],'workstation_id':station['id'],'route_id':current['route'],'process_id':p['process_id'],'param_template_id':p.get('param_template_id'),'item_id':current['mes_id'],'quantity':amount,'produced_quantity':done,'qualify_quantity':done,'unqualify_quantity':0,'changed_quantity':0,'client_id':1613,'start_time':start,'end_time':end,'duration':hours,'status':2 if n==0 else 0,'first_feedback_time':datetime.now().replace(microsecond=0) if n==0 else None,'last_feedback_time':datetime.now().replace(microsecond=0) if n==0 else None,'remark':'半成品已生产完毕;本次仅排剩余包装、检验及缴库,尚未成品入库。'})
| task_ids.append(task)
| ins('mes_pro_task_assignee',{'task_id':task,'user_id':244})
| if n==0:
| code=number('mes_pro_feedback','code','FB_20261015',3,33)
| ins('mes_pro_feedback',{'code':code,'type':1,'feedback_time':datetime.now().replace(microsecond=0),'workstation_id':station['id'],'route_id':current['route'],'process_id':p['process_id'],'work_order_id':current['wo_id'],'task_id':task,'item_id':current['mes_id'],'scheduled_quantity':amount,'feedback_quantity':done,'qualified_quantity':done,'unqualified_quantity':0,'uncheck_quantity':0,'labor_scrap_quantity':0,'material_scrap_quantity':0,'other_scrap_quantity':0,'feedback_user_id':244,'approve_user_id':244,'status':4,'remark':'按本次录入要求记录今日包装部分完成800盒;非末道工序,不增加成品库存。'},244,datetime.now().replace(microsecond=0))
| if p.get('param_template_id'):
| template=next(r for r in a['mes_pd_process_param_template'] if r['id']==p['param_template_id'])
| record=ins('mes_pro_work_order_param_record',{'work_order_id':current['wo_id'],'task_id':task,'template_id':template['id'],'template_code':template['template_code'],'template_name':template['template_name'],'record_no':'TASK-'+str(task),'remark':'排产生成参数快照;实际参数待现场填写。'})
| for detail in a['mes_pd_process_param_template_detail']:
| if detail['template_id']==template['id']:ins('mes_pro_work_order_param_record_detail',{'record_id':record,'template_detail_id':detail['id'],'param_code':detail['param_code'],'param_name':detail['param_name'],'param_type':detail['param_type'],'required':truth(detail['required']),'unit_measure_id':detail.get('unit_measure_id'),'remark':detail.get('remark')})
| start=end
| change('mes_pro_work_order',current['wo_id'],{'quantity_scheduled':current['quantity']})
| if start>current['due_date']:
| current['due_date']=start.replace(hour=18,minute=0,second=0)
| change('mes_pro_work_order',current['wo_id'],{'request_date':current['due_date']})
| change('mes_pro_mps',current['mps_id'],{'request_date':current['due_date']})
| current['task_ids']=task_ids
| assert sum(g['amount'] for g in group_refs.values())==D('2062353.18')
| for row in links:
| assert row['due_date'].weekday()<5 and row['planned_start'].weekday()<5 and TODAY<=row['planned_start'].date()<=row['due_date'].date()<=date(2026,10,30)
| q.execute('select m.quantity,m.unit_measure_id,l.count,l.product_unit_id,l.mps_id from mes_pro_mps m join erp_sale_order_items l on l.id=m.sale_order_item_id where m.id=%s',(row['mps_id'],));v=q.fetchone();assert v['quantity']==v['count']==D(row['quantity']) and v['unit_measure_id']==v['product_unit_id']==row['unit_id'] and v['mps_id']==row['mps_id']
| for t,count in before_counts.items():q.execute('select count(*) n from `'+t+'`');assert q.fetchone()['n']==count,(t,'unexpected inventory or receipt change')
| result={'applied':APPLY,'database':'localhost:3306/mom-hm','total':D('2062353.18'),'groups':group_refs,'counts':{t:len(v) for t,v in inserted.items()},'links':[{k:v for k,v in r.items() if k!='processes'} for r in links],'updates':updates,'sequences':sequences,'protected_counts':before_counts}
| dump('result-applied.json' if APPLY else 'result-preview.json',result)
| (P/('applied.sql' if APPLY else 'preview.sql')).write_text('START TRANSACTION;\n'+'\n'.join(log)+'\n'+('COMMIT;' if APPLY else 'ROLLBACK;'),encoding='utf-8')
| if APPLY:c.commit()
| else:c.rollback()
| print(json.dumps({'applied':APPLY,'amount':str(result['total']),'counts':result['counts'],'current_work_order':current['wo_code'],'packaging_quantity':800,'pending_work_orders':12,'missing_bom_products':[r['name'] for r in links if not r['bom_count']]},ensure_ascii=False))
| finally:
| c.rollback()
| with c.cursor() as q:q.execute('select release_lock(%s)',('ecommerce-production-20261015',))
| c.close()
|
|