"""Independent read-only validation of posted documents and inventory conservation."""
|
import sys,json,csv
|
from pathlib import Path
|
from decimal import Decimal
|
P=Path(__file__).parent
|
sys.path.insert(0,str(P.parent/'iqc-import-20261012/deps'))
|
import pymysql
|
D=Decimal
|
canon=lambda x:json.loads(json.dumps(x,default=str))
|
r=json.loads((P/'shipping-complete-result.json').read_text(encoding='utf8'))
|
b=json.loads((P/'shipping-complete-locked-before.json').read_text(encoding='utf8'))
|
name_fix_path=P/'contract-name-prefix-result.json'
|
name_fixes={x['id']:x for x in json.loads(name_fix_path.read_text(encoding='utf8'))['changes']} if name_fix_path.exists() else {}
|
quote_fix_path=P/'quotation-name-sync-result.json'
|
quote_name_fixes={x['id']:x for x in json.loads(quote_fix_path.read_text(encoding='utf8'))['changes']} if quote_fix_path.exists() else {}
|
date_fix_path=P/'business-create-times-result.json'
|
date_fixes={(x['table'],x['id']):x for x in json.loads(date_fix_path.read_text(encoding='utf8'))['changes']} if date_fix_path.exists() else {}
|
remark_fix_path=P/'contract-order-remarks-cleaned-result.json'
|
remark_fixes={(x['table'],x['id']):x for x in json.loads(remark_fix_path.read_text(encoding='utf8'))['changes']} if remark_fix_path.exists() else {}
|
if date_fix_path.exists():
|
date_result=json.loads(date_fix_path.read_text(encoding='utf8'))
|
latest_backup=json.loads((P/date_result['backup']).read_text(encoding='utf8'))
|
# Verify against customer state immediately before this authorized adjustment.
|
b['tables']['crm_customer']=latest_backup['tables']['crm_customer']
|
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)
|
with c.cursor() as q:
|
data={}
|
for t in b['tables']:
|
q.execute('SELECT * FROM `'+t+'` WHERE deleted=0 ORDER BY id');data[t]={x['id']:x for x in q.fetchall()}
|
receipt=data['mes_wm_misc_receipt'][r['receipt_id']];assert receipt['status']==4 and receipt['code']==r['receipt_code']
|
expected_new={'mes_wm_misc_receipt':{r['receipt_id']},'mes_wm_misc_receipt_line':{a['receipt_line_id'] for a in r['allocations']},'mes_wm_product_sales_detail':{a['detail_id'] for a in r['allocations']},'mes_wm_stock_reserve':{a['reserve_id'] for a in r['allocations']},'mes_wm_transaction':{a[k] for a in r['allocations'] for k in ['in_transaction_id','out_transaction_id']},'mes_wm_material_stock':set(r['new_stock_ids']),'mes_wm_batch':set(r['new_batch_ids'])}
|
for t,ids in expected_new.items():assert set(data[t])-{x['id'] for x in b['tables'][t]}==ids,(t,'Unexpected added rows')
|
for old in b['tables']['mes_wm_material_stock']:
|
actual=canon(data['mes_wm_material_stock'][old['id']]);diff={k for k in old if old[k]!=actual[k]};assert diff<={'updater','update_time'},(old['id'],diff)
|
for id in r['new_stock_ids']:
|
s=data['mes_wm_material_stock'][id];assert all(s[k]==0 for k in ['quantity','frozen_quantity','reserved_quantity','in_transit_quantity'])
|
for a in r['allocations']:
|
qty=D(a['quantity']);inc=data['mes_wm_transaction'][a['in_transaction_id']];out=data['mes_wm_transaction'][a['out_transaction_id']]
|
assert inc['quantity']==qty and out['quantity']==-qty and inc['type']==1 and out['type']==2 and inc['biz_type']==115 and out['biz_type']==119
|
for k in ['item_id','material_stock_id','batch_id','batch_code','warehouse_id','location_id','area_id']:assert inc[k]==out[k],(a['no'],k)
|
assert inc['material_stock_id']==a['stock_id'] and inc['biz_id']==r['receipt_id'] and inc['biz_line_id']==a['receipt_line_id'] and out['biz_id']==a['sales_id'] and out['biz_line_id']==a['sales_line_id']
|
reserve=data['mes_wm_stock_reserve'][a['reserve_id']];assert reserve['status']==3 and reserve['quantity']==qty and reserve['material_stock_id']==a['stock_id']
|
detail=data['mes_wm_product_sales_detail'][a['detail_id']];assert detail['quantity']==qty and detail['material_stock_id']==a['stock_id']
|
sl=data['mes_wm_product_sales_line'][a['sales_line_id']];nl=data['mes_wm_sales_notice_line'][a['notice_line_id']];ol=data['erp_sale_order_items'][a['order_item_id']]
|
assert sl['quantity']==nl['out_count']==ol['out_count']==ol['count']==qty and sl['batch_id']==nl['batch_id']==a['batch_id']
|
assert sl['material_stock_id']==a['stock_id'] and sl['quality_status'] is None and sl['oqc_id'] is None
|
for o in r['orders']:
|
erp=data['erp_sale_order'][o['order_id']];notice=data['mes_wm_sales_notice'][o['notice_id']];sales=data['mes_wm_product_sales'][o['sales_id']]
|
assert erp['out_count']==erp['total_count'] and erp['out_status']==3 and notice['status']==4 and notice['out_status']==2 and sales['status']==4
|
assert sales['carrier']==o['carrier'] and sales['shipping_number']==o['shipping_number'] and sales['client_id']==erp['customer_id']==o['customer_id']
|
if (P/'shipping-remarks-cleared-result.json').exists():assert sales['remark'] is None and notice['remark'] is None
|
else:assert ('模拟生成' in sales['remark']) if o['jd'] else ('青H' in sales['carrier'])
|
assert len({o['shipping_number'] for o in r['orders']})==23
|
affected_orders={o['order_id'] for o in r['orders']};affected_lines={a['order_item_id'] for a in r['allocations']}
|
for t,rows in b['tables'].items():
|
if t in ['erp_sale_order','erp_sale_order_items']:
|
ids=affected_orders if t=='erp_sale_order' else affected_lines
|
allowed={'out_count','updater','update_time'}|({'out_status'} if t=='erp_sale_order' else set())
|
for old in rows:
|
new=canon(data[t][old['id']]);diff={k for k in old if old[k]!=new[k]};allowed_row=allowed if old['id'] in ids else set()
|
if (t,old['id']) in date_fixes:
|
allowed_row=allowed_row|{'create_time','updater','update_time'}
|
assert new['create_time']==date_fixes[(t,old['id'])]['create_time']
|
if (t,old['id']) in remark_fixes:
|
allowed_row|={'remark','updater'}
|
assert new['remark']==remark_fixes[(t,old['id'])]['remark']
|
assert diff<=allowed_row,(t,old['id'],diff)
|
elif t.startswith('crm_') or t in ['mdm_item','mes_qc_oqc','mes_wm_warehouse','mes_wm_warehouse_location','mes_wm_warehouse_area']:
|
fixes=name_fixes if t=='crm_contract' else quote_name_fixes if t=='crm_sale_quotation' else {}
|
if fixes or any(k[0]==t for k in date_fixes) or any(k[0]==t for k in remark_fixes):
|
for old in rows:
|
new=canon(data[t][old['id']]);diff={k for k in old if old[k]!=new[k]}
|
allowed_row={'name','updater','update_time'} if old['id'] in fixes else set()
|
if (t,old['id']) in date_fixes:
|
allowed_row|={'create_time','updater','update_time'}
|
assert new['create_time']==date_fixes[(t,old['id'])]['create_time']
|
if (t,old['id']) in remark_fixes:
|
allowed_row|={'remark','updater'}
|
assert new['remark']==remark_fixes[(t,old['id'])]['remark']
|
assert diff<=allowed_row
|
if old['id'] in fixes:assert new['name']==fixes[old['id']]['new_name']
|
else:assert rows==canon(list(data[t].values())),('Protected table changed',t)
|
for t in ['mes_wm_transaction','mes_wm_stock_reserve','mes_wm_product_sales_detail','mes_wm_batch','mes_wm_misc_receipt','mes_wm_misc_receipt_line','mes_wm_misc_receipt_detail']:
|
for old in b['tables'][t]:assert old==canon(data[t][old['id']]),('Existing row changed',t,old['id'])
|
negative=next(x for x in data['erp_sale_order'].values() if x['no']=='26000369');assert negative['out_count']==0
|
summary={'passed':True,**r['counts'],'original_stock_rows_unchanged':len(b['tables']['mes_wm_material_stock']),'new_zero_stock_rows':len(r['new_stock_ids']),'stock_net_change':'0','financial_data_unchanged':True,'negative_order_not_shipped':'26000369','tracking_numbers':'模拟生成;自送保留青H前缀'}
|
(P/'shipping-complete-verification.json').write_text(json.dumps(summary,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(['销售订单号','客户','发货通知单号','出库单号','状态','承运商','运单或内部配送号','说明'])
|
for o in r['orders']:w.writerow([o['no'],o['customer'],o['notice_code'],o['sales_code'],'已完成',o['carrier'],o['shipping_number'],'模拟运单,非真实物流凭证' if o['jd'] else '自有车辆;车牌前缀青H;编号为内部配送号'])
|
print(json.dumps(summary,ensure_ascii=False))
|
c.close()
|