logistics/logisticsClass/logisticsTail_AU.py

446 lines
20 KiB
Python
Raw Permalink Normal View History

2025-06-17 13:40:20 +08:00
""" 澳大利亚尾端物流模块实现类"""
from pathlib import Path
import re
import math
import pandas as pd
from logisticsClass.logisticsBaseClass import TailLogistics
from utils.Package import Package, Package_group
"""
counrty:Australia
company:POST,ALL,TOLL
port:海SY,空SYD(default)
currency:str = 'AUD'(default)
logistics_type:LogisticsType (快递(default),卡派)
"""
# POST
class PostLogistics_AU(TailLogistics):
country_code = 'AU'
country = 'Australia'
company = 'POST'
currency = 'AUD'
_is_loaded = False
parent_current_directory = Path(__file__).parent.parent
remote_path = parent_current_directory.joinpath("data")
_postcode_files = remote_path.joinpath("澳洲三大渠道.xlsx")
def __new__(cls):
"""实现单例模式,只加载一次文件"""
if not cls._is_loaded:
cls._load_postcodes() # 第一次实例化时加载文件
cls._is_loaded = True # 标记文件已加载
return super().__new__(cls)
# Path_current_directory = Path(__file__).parent
# current_directory = os.path.dirname(__file__)
@classmethod
def _load_postcodes(cls):
"""加载文件"""
# 获取eparcel邮编所属的分区
cls.eparcel_zone = pd.read_excel(str(cls._postcode_files),sheet_name="eparcel_postcode",usecols="A:B")
# 获取eparcel的价格
cls.eparcel_price = pd.read_excel(str(cls._postcode_files),sheet_name="eparcel",usecols="A:M")
def __init__(self):
super().__init__()
self.eparcel_zone = self.__class__.eparcel_zone
self.eparcel_price = self.__class__.eparcel_price
self.fuel_rate =0.051# 0.074 0.064
2025-09-10 13:37:38 +08:00
def is_remote(self, postcode):
"""判断分区"""
# 先看邮编呐
self.eparcel_zone['邮编'] = self.eparcel_zone['邮编'].astype(str).str.zfill(4)
filtered_df = self.eparcel_zone[self.eparcel_zone['邮编'] == postcode]
if filtered_df.empty:
return "不支持配送"
post = filtered_df['地区代码'].iloc[0] # 获取第一行的 '地区代码'
return post
2025-06-17 13:40:20 +08:00
def calculate_fee(self, packages, postcode):
"""
计费重: 常规计费, 4000
2025-07-16 00:05:12 +08:00
限制条件: 32kg,最长边100cm.3KG以内实重计费3KG以上标准计费2.5KG按3KG算
2025-06-17 13:40:20 +08:00
是否有燃油费
"""
detail_amount = {
"base":0.00,
"fuel":0.00,
"tail_amount":0.00
}
if isinstance(postcode, str):
postcode = ''.join(filter(str.isdigit, postcode)) # 只保留数字部分
if postcode: # 确保postcode不是空字符串
postcode = str(int(postcode)).zfill(4) # 转换为整数再补齐4位
else:
detail_amount['tail_amount'] = 88888
return detail_amount
2025-09-10 13:37:38 +08:00
post = self.is_remote(postcode)
if post == "不支持配送":
2025-06-17 13:40:20 +08:00
detail_amount['tail_amount'] = 99999
return detail_amount
# 根据post筛选出对应行
row = self.eparcel_price[self.eparcel_price['post'] == post]
# 检查 row 是否为空
if row.empty:
detail_amount['tail_amount'] = 99999
return detail_amount
base_data = list(zip(self.eparcel_price.columns[1:], row.values[0][1:]))
for package in packages:
2025-07-16 00:05:12 +08:00
# 2500g-3000g按3kg算
if 2500<=package.weight<3000:
package.weight = 3000
if package.weight > 3000:
billing_weight = max(package.weight, package.volume/4)
else:
billing_weight = package.weight
2025-06-17 13:40:20 +08:00
2025-07-16 00:05:12 +08:00
if package.fst_size > 100 or billing_weight > 22000:
2025-06-17 13:40:20 +08:00
detail_amount['tail_amount'] = 99999
return detail_amount
if billing_weight > 22000:
detail_amount['base'] += base_data[-2][1] + base_data[-1][1] * math.ceil(billing_weight/1000)
else:
for weight,price in base_data[:-2]:
if billing_weight <=weight*1000:
detail_amount['base'] += price
break
detail_amount['fuel'] = detail_amount['base'] * self.fuel_rate
detail_amount['tail_amount'] = detail_amount['base'] + detail_amount['fuel']
return detail_amount
# TOLL
class TollLogistics_AU(TailLogistics):
country_code = 'AU'
country = 'Australia'
company = 'TOLL'
currency = 'AUD'
_is_loaded = False
parent_current_directory = Path(__file__).parent.parent
remote_path = parent_current_directory.joinpath("data")
_postcode_files = remote_path.joinpath("澳洲三大渠道.xlsx")
def __new__(cls):
"""实现单例模式,只加载一次文件"""
if not cls._is_loaded:
cls._load_postcodes() # 第一次实例化时加载文件
cls._is_loaded = True # 标记文件已加载
return super().__new__(cls)
@classmethod
def _load_postcodes(cls):
"""加载文件"""
cls.toll_zone = pd.read_excel(str(cls._postcode_files),sheet_name="toll_postcode",usecols="A:B")
cls.toll_price = pd.read_excel(str(cls._postcode_files),sheet_name="toll",usecols="A:D")
cls.toll_remote = pd.read_excel(str(cls._postcode_files),sheet_name="toll_remote",usecols="A:D")
def __init__(self):
super().__init__()
self.toll_zone = self.__class__.toll_zone
self.toll_price = self.__class__.toll_price
self.toll_remote = self.__class__.toll_remote
self.fuel_rate = 0.0725
self.oversize_fee = [15.5,62]
2025-09-10 13:37:38 +08:00
def is_remote(self, postcode):
"""判断分区"""
# 先看邮编呐
self.toll_zone['postcode'] = self.toll_zone['postcode'].astype(str).str.zfill(4)
filtered_df = self.toll_zone[self.toll_zone['postcode'] == postcode]
if filtered_df.empty:
return "不支持配送"
post = filtered_df['post'].iloc[0] # 获取第一行的 '地区代码'
return post
2025-06-17 13:40:20 +08:00
def calculate_fee(self, packages, postcode):
# 抛重4000
detail_amount = {
"base":0.00,
"oversize":0.00,
"remote":0.00,
"fuel":0.00,
"tail_amount":0.00
}
if isinstance(postcode, str):
postcode = ''.join(filter(str.isdigit, postcode)) # 只保留数字部分
if postcode: # 确保postcode不是空字符串
postcode = str(int(postcode)).zfill(4) # 转换为整数再补齐4位
else:
detail_amount['tail_amount'] = 88888
return detail_amount
2025-09-10 13:37:38 +08:00
post = self.is_remote(postcode)
if post == "不支持配送":
2025-06-17 13:40:20 +08:00
detail_amount['tail_amount'] = 99999
return detail_amount
# 根据post筛选出对应行
row = self.toll_price[self.toll_price['post'] == post]
# 检查 row 是否为空
if row.empty:
detail_amount['tail_amount'] = 99999
return detail_amount
base = row['Base'].iloc[0]
per = self.toll_price[self.toll_price['post']==post]['Per'].iloc[0]
minimun = self.toll_price[self.toll_price['post']==post]['Minimun'].iloc[0]
for package in packages:
volume_weight = package.get_volume_weight(4)
billing_weight = max(package.weight,volume_weight)
detail_amount['base'] += max(base + per * math.ceil(billing_weight/1000), minimun)
2025-07-16 00:05:12 +08:00
if package.weight >= 35000 or package.fst_size >= 180 or package.volume >=700000:
# detail_amount['oversize'] += self.oversize_fee[1]
detail_amount['tail_amount'] = 99999 # ERP配置的超过这个尺寸就不派送这个了
2025-06-17 13:40:20 +08:00
if package.weight >30000 or package.fst_size > 120 or package.sed_size > 80:
detail_amount['oversize'] += self.oversize_fee[0]
2025-07-16 00:05:12 +08:00
2025-06-17 13:40:20 +08:00
# 计算偏远附加费,只跟地区有关,不需要放入循环内部计算
2025-07-16 00:05:12 +08:00
# postcode_counts = self.toll_zone[self.toll_zone['postcode'] == postcode].shape[0] # 该邮编总共多少个地区
remote_count = self.toll_remote[self.toll_remote['postcode'] == int(postcode)].shape[0] # 该邮编有多少个偏远地区
# postcode_counts = int(postcode_counts)
2025-06-17 13:40:20 +08:00
remote_count=int(remote_count)
# 将 price 列中的 NaN 替换为 0
self.toll_remote['price'] = self.toll_remote['price'].fillna(0)
# 该邮编偏远地区的偏远费
2025-07-16 00:05:12 +08:00
remote_fee = self.toll_remote[self.toll_remote['postcode'] == int(postcode)]['price'].mean()
2025-06-17 13:40:20 +08:00
# 检查 remote_fee 是否为 NaN若是则设为 0
if pd.isna(remote_fee):
remote_fee = 0
2025-07-16 00:05:12 +08:00
# # 计算最终的偏远附加费
# if postcode_counts > 0 and remote_count > 0:
# remote_fee = remote_fee * remote_count / postcode_counts
# else:
# remote_fee = 0 # 处理为其他合适的值
2025-06-17 13:40:20 +08:00
detail_amount['remote'] = remote_fee * len(packages)
for key in detail_amount:
if key!= 'tail_amount' and key!= 'fuel':
detail_amount['tail_amount'] += detail_amount[key]
detail_amount['fuel'] += detail_amount[key] * self.fuel_rate
detail_amount['tail_amount'] += detail_amount['fuel']
return detail_amount
# ALL
class AllLogistics_AU(TailLogistics):
country_code = 'AU'
country = 'Australia'
company = 'ALL'
currency = 'AUD'
_is_loaded = False
parent_current_directory = Path(__file__).parent.parent
remote_path = parent_current_directory.joinpath("data")
_postcode_files = remote_path.joinpath("澳洲三大渠道.xlsx")
def __new__(cls):
"""实现单例模式,只加载一次文件"""
if not cls._is_loaded:
cls._load_postcodes() # 第一次实例化时加载文件
cls._is_loaded = True # 标记文件已加载
return super().__new__(cls)
@classmethod
def _load_postcodes(cls):
"""加载文件"""
cls.all_zone = pd.read_excel(str(cls._postcode_files),sheet_name="allied_postcode",usecols="A:C")
cls.all_price = pd.read_excel(str(cls._postcode_files),sheet_name="allied",usecols="A:D")
cls.all_remote = pd.read_excel(str(cls._postcode_files),sheet_name="allied_remote",usecols="A:E")
def __init__(self):
super().__init__()
self.all_zone = self.__class__.all_zone
self.all_price = self.__class__.all_price
self.all_remote = self.__class__.all_remote
self.fuel_rate = 0.269
self.homedelivery_fee = [5.51,11.02,38.56,82.61]
self.oversize_fee = [7.8,10.92,12.41,26.42,92.15,123.96,160.07]
self.handing_fee = 15.34 # 不收燃油费
self.twomancrew_fee = [49.92,78,124.8] # 多包裹的情况下,只收一次最大的
2025-09-10 13:37:38 +08:00
def is_remote(self, postcode):
"""判断分区"""
# 先看邮编呐
# 先看邮编呐
self.all_zone['postcode'] = self.all_zone['postcode'].astype(str).str.zfill(4)
filtered_df = self.all_zone[self.all_zone['postcode'] == postcode]
if filtered_df.empty:
return "不支持配送"
post = filtered_df['post'].iloc[0] # 获取第一行的 '地区代码'
return post
2025-06-17 13:40:20 +08:00
def calculate_fee(self, packages, postcode):
# 抛重4000
detail_amount = {
"base":0.00,
"Homedelivery":0.00,
"twomancrew":0.00, # 多包裹的情况下,只收一次最大的
"lengthover":0.00,
"fuel":0.00,
"widthover":0.00, # 240cm内不收燃油费
"handling":0.00,
"tail_amount":0.00
}
if isinstance(postcode, str):
postcode = ''.join(filter(str.isdigit, postcode)) # 只保留数字部分
if postcode: # 确保postcode不是空字符串
postcode = str(int(postcode)).zfill(4) # 转换为整数再补齐4位
else:
detail_amount['tail_amount'] = 88888
return detail_amount
2025-09-10 13:37:38 +08:00
post = self.is_remote(postcode)
if post == "不支持配送":
2025-06-17 13:40:20 +08:00
detail_amount['tail_amount'] = 99999
return detail_amount
try:
base = self.all_price[self.all_price['post']==post]['Base'].iloc[0]
per = self.all_price[self.all_price['post']==post]['Per'].iloc[0]
minimun = self.all_price[self.all_price['post']==post]['Minimun'].iloc[0]
except:
detail_amount['tail_amount'] = 99999
return detail_amount
total_weight = 0 # 总计费重,用于计算偏远附加费
for package in packages:
volume_weight = package.get_volume_weight(4)
billing_weight = max(package.weight,volume_weight)
total_weight += billing_weight # 总计费重,用于计算偏远附加费
detail_amount['base'] += max(base + per * math.ceil(billing_weight/1000), minimun)
if billing_weight <= 22000:
detail_amount['Homedelivery'] += self.homedelivery_fee[0]
elif 22000<billing_weight <=55000:
detail_amount['Homedelivery'] += self.homedelivery_fee[1]
elif 55000<package.weight<=90000 or 55000<volume_weight<=135000:
detail_amount['Homedelivery'] += self.homedelivery_fee[2]
else:
detail_amount['Homedelivery'] += self.homedelivery_fee[3]
[7.8,10.92,12.41,26.42,92.15,123.96,160.07]
if 110<package.fst_size <=160:
detail_amount['widthover'] += self.oversize_fee[0]
elif 160<package.fst_size <=240:
detail_amount['widthover'] += self.oversize_fee[1]
elif 240<package.fst_size <=359:
detail_amount['lengthover'] += self.oversize_fee[2]
elif 359<package.fst_size <=419:
detail_amount['lengthover'] += self.oversize_fee[3]
elif 419<package.fst_size <=479:
detail_amount['lengthover'] += self.oversize_fee[4]
elif 479<package.fst_size <=599:
detail_amount['lengthover'] += self.oversize_fee[5]
elif package.fst_size > 599:
detail_amount['lengthover'] += self.oversize_fee[6]
if 130<=package.fst_size <=190 and package.sed_size > 90 and (package.weight >46000 or billing_weight>91000):
detail_amount['twomancrew'] = self.twomancrew_fee[0]
if 190<=package.fst_size <=240 and package.sed_size > 130 and (package.weight >55000 or billing_weight>110000):
detail_amount['twomancrew'] = self.twomancrew_fee[1]
if 240<package.fst_size and package.sed_size > 130 and (package.weight >75000 or billing_weight>150000):
detail_amount['twomancrew'] = self.twomancrew_fee[2]
if billing_weight >30000 or package.fst_size >110:
detail_amount['handling'] += self.handing_fee
# 计算偏远附加费,只跟地区有关,不需要放入循环内部计算
postcode_counts = self.all_zone[self.all_zone['postcode'] == postcode].shape[0] # 该邮编总共多少个地区
remote_count = self.all_remote[self.all_remote['Postcode'] == postcode].shape[0] # 该邮编有多少个偏远地区
postcode_counts = int(postcode_counts)
remote_count=int(remote_count)
# 该邮编偏远地区的偏远费
remote_base_fee = self.all_remote[self.all_remote['Postcode']==postcode]['Base'].mean() # 该邮编偏远地区的偏远基础费
remote_per_fee = self.all_remote[self.all_remote['Postcode']==postcode]['Per'].mean()
# 检查 remote_base_fee 是否为 NaN若是则设为 0
if pd.isna(remote_base_fee) or pd.isna(remote_per_fee):
remote_base_fee = 0
remote_per_fee = 0
# 计算最终的偏远附加费
if postcode_counts > 0 and remote_count > 0:
detail_amount['remote'] = remote_base_fee+remote_per_fee*math.ceil(total_weight/1000)* remote_count/postcode_counts
for key in detail_amount:
if key!= 'tail_amount' and key!= 'fuel':
detail_amount['tail_amount'] += detail_amount[key]
if key!='widthover' and key!='handling':
detail_amount['fuel'] += detail_amount[key] * self.fuel_rate
detail_amount['tail_amount'] += detail_amount['fuel']
return detail_amount
2025-09-10 13:37:38 +08:00
# TMS
# class TMSLogistics_AU(TailLogistics):
# country_code = 'AU'
# country = 'Australia'
# company = 'TMS'
# currency = 'AUD'
# _is_loaded = False
# parent_current_directory = Path(__file__).parent.parent
# remote_path = parent_current_directory.joinpath("data")
# _postcode_files = remote_path.joinpath("澳洲三大渠道.xlsx")
# def __new__(cls):
# """实现单例模式,只加载一次文件"""
# if not cls._is_loaded:
# cls._load_postcodes() # 第一次实例化时加载文件
# cls._is_loaded = True # 标记文件已加载
# return super().__new__(cls)
# @classmethod
# def _load_postcodes(cls):
# """加载文件"""
# cls.toll_zone = pd.read_excel(str(cls._postcode_files),sheet_name="toll_postcode",usecols="A:B")
# cls.toll_price = pd.read_excel(str(cls._postcode_files),sheet_name="toll",usecols="A:D")
# cls.toll_remote = pd.read_excel(str(cls._postcode_files),sheet_name="toll_remote",usecols="A:D")
# def __init__(self):
# super().__init__()
# self.toll_zone = self.__class__.toll_zone
# self.toll_price = self.__class__.toll_price
# self.toll_remote = self.__class__.toll_remote
# self.fuel_rate = 0.0725
# self.oversize_fee = [15.5,62]
# def is_remote(self, postcode):
# """判断分区"""
# # 先看邮编呐
# self.toll_zone['postcode'] = self.toll_zone['postcode'].astype(str).str.zfill(4)
# filtered_df = self.toll_zone[self.toll_zone['postcode'] == postcode]
# if filtered_df.empty:
# return "不支持配送"
# post = filtered_df['post'].iloc[0] # 获取第一行的 '地区代码'
# return post
# def calculate_fee(self, packages, postcode):
# # 抛重4000
# detail_amount = {
# "base":0.00,
# "oversize":0.00,
# "remote":0.00,
# "fuel":0.00,
# "tail_amount":0.00
# }
# if isinstance(postcode, str):
# postcode = ''.join(filter(str.isdigit, postcode)) # 只保留数字部分
# if postcode: # 确保postcode不是空字符串
# postcode = str(int(postcode)).zfill(4) # 转换为整数再补齐4位
# else:
# detail_amount['tail_amount'] = 88888
# return detail_amount
# post = self.is_remote(postcode)
# if post == "不支持配送":
# detail_amount['tail_amount'] = 99999
# return detail_amount
# # 根据post筛选出对应行
# row = self.toll_price[self.toll_price['post'] == post]
# # 检查 row 是否为空
# if row.empty:
# detail_amount['tail_amount'] = 99999
# return detail_amount
2025-06-17 13:40:20 +08:00
2025-09-10 13:37:38 +08:00
# base = row['Base'].iloc[0]
# per = self.toll_price[self.toll_price['post']==post]['Per'].iloc[0]
# minimun = self.toll_price[self.toll_price['post']==post]['Minimun'].iloc[0]
# for package in packages:
# return detail_amount
2025-06-17 13:40:20 +08:00
if __name__ == '__main__':
# 测试
aau = PostLogistics_AU()
package = Package("wxx",40,25,25,1780)
packages = Package_group([package])
aau.calculate_fee(packages,'3101')
TollLogistics_AU.active = True
PostLogistics_AU.active = True