Files
baiyue-logistics-chat/logistics_chat/quote_fees.py
T

99 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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