import json,re,csv
|
from datetime import datetime
|
from collections import defaultdict,Counter
|
from read_links import P,encode
|
|
def norm(name):
|
return re.sub(r'[\s\-—_()(),,·]','',name or '').lower()
|
def family(name):
|
n=norm(name)
|
if '燕窝' in n and '小罐' in n:return '小罐即食燕窝'
|
if '燕窝' in n and '碗装' in n:return '碗装即食燕窝'
|
if n in ('黑果枸杞原浆罐头','黑果枸杞原浆','黑枸杞原浆'):return '黑枸杞原浆'
|
if n in ('有机黑果枸杞','黑果枸杞'):return '黑果枸杞'
|
return n
|
|
def make(s):
|
tables={t:{r['id']:r for r in v['rows']} for t,v in s['tables'].items()}
|
customers=tables['crm_customer'];mes=tables['mes_md_item'];mdm=tables['mdm_item'];orders=tables['erp_sale_order']
|
references=[]
|
for line in tables['erp_sale_order_items'].values():
|
order=orders.get(line['order_id']);item=mdm.get(line['product_id'])
|
if order and item and order['customer_id'] in customers:
|
references.append({'order_id':order['id'],'order_no':order['no'],'line_id':line['id'],
|
'customer_id':order['customer_id'],'product_id':item['id'],'product_name':item['name'],
|
'specification':item['specification'],'order_time':order['order_time']})
|
branded=[x for x in customers.values() if x['name']=='北京同仁堂健康药业股份有限公司']
|
assert len(branded)==1
|
result=[]
|
for id in s['target_ids']:
|
qc=tables['mes_qc_oqc'][id];item=mes[qc['item_id']]
|
assert qc['client_id'] is None and not qc['source_doc_type'] and not qc['source_doc_id']
|
tiers=[('同物料历史销售客户',[r for r in references if r['product_id']==(item.get('mdm_item_id') or item['id'])]),
|
('同名产品历史销售客户(规格可能不同)',[r for r in references if norm(r['product_name'])==norm(item['name'])]),
|
('同系列产品历史销售客户',[r for r in references if family(r['product_name'])==family(item['name'])])]
|
matches=[];basis=None
|
for label,rs in tiers:
|
if rs:basis=label;matches=rs;break
|
evidence=[];candidates=[]
|
if matches:
|
groups=defaultdict(list)
|
for ref in matches:groups[ref['customer_id']].append(ref)
|
def ranking(cid):
|
rs=groups[cid]
|
nearest=min(abs((datetime.fromisoformat(r['order_time'])-datetime.fromisoformat(qc['inspect_date'])).total_seconds()) for r in rs)
|
return (-len({r['order_id'] for r in rs}),nearest,cid)
|
winner=sorted(groups,key=ranking)[0];evidence=groups[winner]
|
candidates=[{'customer_id':cid,'customer_name':customers[cid]['name'],'distinct_orders':len({x['order_id'] for x in rs})} for cid,rs in groups.items()]
|
else:
|
assert re.match(r'^BJTRT\s+QH\s+2026-',qc['code']),qc['code']
|
winner=branded[0]['id'];basis='检验单企业标识与既有客户名称参考匹配(无同产品销售依据)'
|
evidence=[{'qc_code':qc['code'],'customer_id':winner,'customer_name':customers[winner]['name']}]
|
assert winner in customers
|
result.append({'id':id,'code':qc['code'],'item_id':item['id'],'product_name':item['name'],'specification':item['specification'],
|
'before':None,'after':winner,'customer_name':customers[winner]['name'],'basis':basis,'inferred':True,
|
'candidates':candidates,'evidence':evidence,'source_before':qc})
|
return {'state':s['state'],'changes':result,'summary':{'count':len(result),
|
'by_customer':dict(Counter(x['customer_name'] for x in result)),
|
'by_basis':dict(Counter(x['basis'] for x in result)),
|
'authorisation':'用户明确允许参考其他业务数据推定绑定;非原始交易客户证明'}}
|
|
if __name__=='__main__':
|
s=json.loads((P/'oqc-context.json').read_text(encoding='utf8'));p=make(s)
|
(P/'oqc-binding-plan.json').write_text(encode(p),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 p['changes']:w.writerow([r['id'],r['code'],r['product_name'],r['specification'],r['after'],r['customer_name'],r['basis'],','.join(x.get('order_no','') for x in r['evidence'])])
|
print(encode(p['summary']))
|