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']))