云
昨天 74b0d9c5ab1dc316b29f8525dcffe45225d4c43c
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
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']))