99 lines
4.9 KiB
Python
99 lines
4.9 KiB
Python
"""Amounts come from the active book; explicit customer-approved fee policies live here."""
|
||
|
||
from decimal import Decimal
|
||
import re
|
||
|
||
from .pricing import dims_to_cm
|
||
|
||
|
||
def document(book,required,service=None):
|
||
rows=[d for d in book['documents'] if (not service or d['sheet'] in (service,'海派'))
|
||
and all(term in d['content'] for term in required)]
|
||
return rows[0]['content'] if rows else ''
|
||
|
||
|
||
def amount(text,pattern):
|
||
match=re.search(pattern,text,re.I)
|
||
return Decimal(match.group(1)) if match else None
|
||
|
||
|
||
def material_fee(book):
|
||
text=document(book,('铁铝','附加费'))
|
||
return amount(text,r'铁铝制品[^\n]*?[¥¥]\s*(\d+(?:\.\d+)?)\s*/KG')
|
||
|
||
|
||
def specimens(intent):
|
||
if intent.get('packages'):
|
||
return [{**p,'dims':p.get('dims') or intent.get('dims'),
|
||
'dim_unit':p.get('dim_unit') if p.get('dims') else intent.get('dim_unit','cm')} for p in intent['packages']]
|
||
if intent.get('boxes')==1 and intent.get('weight'):
|
||
return [{'count':1,'weight':intent['weight'],'dims':intent.get('dims'),'dim_unit':intent.get('dim_unit','cm')}]
|
||
return []
|
||
|
||
|
||
def limit_fees(intent,book):
|
||
"""Maximum per-piece limit fee, not the sum of overlapping weight/size charges.
|
||
|
||
The malformed 0<length<274 rule does not create a new threshold. Only two
|
||
independently explicit large-piece conditions can establish that tier.
|
||
"""
|
||
normal=document(book,('正常货物',))
|
||
cleaned=re.sub(r'\s+','',normal).replace('<','<').upper()
|
||
limits=[amount(cleaned,p) for p in (r'0KG<单件重量<(\d+)KG',r'0CM<最长单边长度<(\d+)CM',
|
||
r'0CM<第二边<(\d+)CM',r'最长边<(\d+)CM')]
|
||
normal_fee=amount(document(book,('超重收',)),r'超重收\s*[++]?\s*(\d+(?:\.\d+)?)')
|
||
size_text=document(book,('普通超尺寸',))
|
||
size_fee=amount(size_text,r'加收\s*(\d+(?:\.\d+)?)\s*RMB/件')
|
||
large_text=document(book,('大尺寸货物','任意2个'))
|
||
large_fee=amount(large_text,r'加收\s*(\d+(?:\.\d+)?)\s*RMB/件')
|
||
huge=document(book,('超大件货物',))
|
||
huge_fee=amount(huge,r'加收\s*USD\s*(\d+(?:\.\d+)?)')
|
||
result={'cny':Decimal(0),'items':[],'notes':[]}
|
||
if not all(limits) or normal_fee is None:return result
|
||
max_weight,max_length,max_second,max_girth=limits
|
||
for piece in specimens(intent):
|
||
weight=Decimal(piece['weight']);count=piece['count']
|
||
dims=sorted([Decimal(str(x)) for x in dims_to_cm(piece['dims'],piece['dim_unit'])],reverse=True) if piece['dims'] else []
|
||
length,second,girth=(dims[0],dims[1],dims[0]+2*(dims[1]+dims[2])) if dims else (None,None,None)
|
||
if weight>68 or (dims and (length>270 or girth>419)):
|
||
result['notes'].append('该件可能超出承运尺寸或重量,需人工确认是否接收。'+(f'原表超大件若接收的附加费为{huge_fee:g}USD/件。' if huge_fee else ''))
|
||
continue
|
||
candidates=[]
|
||
if weight>max_weight:candidates.append(normal_fee)
|
||
if dims and (length>max_length or second>max_second or girth>max_girth) and size_fee is not None:candidates.append(size_fee)
|
||
if large_fee is not None and max_weight<weight<68 and dims and 330<girth<419:
|
||
candidates.append(large_fee)
|
||
elif dims and (length>=243 or girth>=330):
|
||
result['notes'].append('该件接近大尺寸档,原表有一处长度条件不清,适用档位需人工确认。')
|
||
if candidates:
|
||
fee=max(candidates)*count
|
||
result['cny']+=fee
|
||
result['items'].append(f'超重/超尺寸按最高项{max(candidates):g}元/件×{count}件={fee:g}元')
|
||
return result
|
||
|
||
|
||
def charge_details(intent,rate,remote,book):
|
||
from .quote_engine import quantity
|
||
|
||
result={'cny':Decimal(0),'items':[],'notes':[]}
|
||
charged=quantity(intent,rate)
|
||
if charged is None:return result
|
||
if rate['kind']=='parcel' and remote:
|
||
if remote['fee'] is not None:
|
||
fee=charged*Decimal(remote['fee']);result['cny']+=fee
|
||
result['items'].append(f'偏远费{remote["fee"]}元/KG×{charged:g}KG={fee:g}元')
|
||
else:result['notes'].append('遥远地址的附加费需单独确认,当前金额未包含该项。')
|
||
if intent.get('material')=='metal':
|
||
per_kg=material_fee(book)
|
||
if per_kg is not None and rate['unit']=='kg':
|
||
fee=charged*per_kg;result['cny']+=fee
|
||
result['items'].append(f'铁铝附加费{per_kg:g}元/KG×{charged:g}KG={fee:g}元')
|
||
elif per_kg is not None:
|
||
result['notes'].append(f'铁铝附加费为{per_kg:g}元/KG,当前按方运费未包含该项;需确认适用计费重量后单列,不能把CBM直接乘公斤附加费。')
|
||
if rate['kind']=='parcel':
|
||
limits=limit_fees(intent,book)
|
||
result['cny']+=limits['cny'];result['items']+=limits['items'];result['notes']+=limits['notes']
|
||
if intent.get('address_type')=='non_fba':
|
||
result['notes'].append('非亚马逊地址还需核对住宅/商业派送附加费,原表此项需单独询价。')
|
||
return result
|