From 38cc3c8b51ff1ada14d053494a9b933832c7ed51 Mon Sep 17 00:00:00 2001 From: Wenxixi <2911872386@qq.com> Date: Wed, 10 Sep 2025 13:37:38 +0800 Subject: [PATCH] 1 --- data/Metro.xlsx | 4 +- data/XMILES.xlsx | 4 +- logisticsClass/logisticsTail_AU.py | 134 +++++++++++++--- logisticsClass/logisticsTail_EUR.py | 106 ++++++++++++ logisticsClass/logisticsTail_UK.py | 9 +- logisticsClass/logisticsTail_US.py | 1 - sell/sell_price.py | 32 ++-- utils/apply_active_config.py | 4 +- utils/logistics_name_config.py | 2 +- 物流t投递审核.py | 240 +++++++++++++++++++++------- 10 files changed, 429 insertions(+), 107 deletions(-) diff --git a/data/Metro.xlsx b/data/Metro.xlsx index 32ccd1a..a66b40a 100644 --- a/data/Metro.xlsx +++ b/data/Metro.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8028948bf09132193a1d2e0c133090f9fbb7c07026d4e216cc6cdc8edaa8d7e4 -size 2484691 +oid sha256:954c50b849945b6a7936ad4ed2cefe8fdf0ac8767ec5accfd028b1130943ac21 +size 2471753 diff --git a/data/XMILES.xlsx b/data/XMILES.xlsx index b3da2bb..98e56be 100644 --- a/data/XMILES.xlsx +++ b/data/XMILES.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c3763893f5c1fd1f02607efb6c2d696aa96869df8d50f6e6ac3d9dcbc1003de -size 42645 +oid sha256:8ccfa334139aa34baf822be97b24d50ef4f4cb45f373420f42cc8f17e1403f0a +size 41452 diff --git a/logisticsClass/logisticsTail_AU.py b/logisticsClass/logisticsTail_AU.py index f3deacf..34acd25 100644 --- a/logisticsClass/logisticsTail_AU.py +++ b/logisticsClass/logisticsTail_AU.py @@ -46,6 +46,17 @@ class PostLogistics_AU(TailLogistics): self.eparcel_price = self.__class__.eparcel_price self.fuel_rate =0.051# 0.074 0.064 + 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 def calculate_fee(self, packages, postcode): """ 计费重: 常规计费, 4000 @@ -65,15 +76,10 @@ class PostLogistics_AU(TailLogistics): detail_amount['tail_amount'] = 88888 return detail_amount - # 先看邮编呐 - self.eparcel_zone['邮编'] = self.eparcel_zone['邮编'].astype(str).str.zfill(4) - - filtered_df = self.eparcel_zone[self.eparcel_zone['邮编'] == postcode] - if filtered_df.empty: + post = self.is_remote(postcode) + if post == "不支持配送": detail_amount['tail_amount'] = 99999 return detail_amount - - post = filtered_df['地区代码'].iloc[0] # 获取第一行的 '地区代码' # 根据post筛选出对应行 row = self.eparcel_price[self.eparcel_price['post'] == post] # 检查 row 是否为空 @@ -138,7 +144,16 @@ class TollLogistics_AU(TailLogistics): 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 = { @@ -155,14 +170,10 @@ class TollLogistics_AU(TailLogistics): else: detail_amount['tail_amount'] = 88888 return detail_amount - # 先看邮编呐 - 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: + post = self.is_remote(postcode) + if post == "不支持配送": detail_amount['tail_amount'] = 99999 return detail_amount - - post = filtered_df['post'].iloc[0] # 获取第一行的 '地区代码' # 根据post筛选出对应行 row = self.toll_price[self.toll_price['post'] == post] # 检查 row 是否为空 @@ -246,6 +257,17 @@ class AllLogistics_AU(TailLogistics): 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] # 多包裹的情况下,只收一次最大的 + + 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 def calculate_fee(self, packages, postcode): # 抛重4000 detail_amount = { @@ -265,15 +287,10 @@ class AllLogistics_AU(TailLogistics): else: detail_amount['tail_amount'] = 88888 return detail_amount - - # 先看邮编呐 - 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: + post = self.is_remote(postcode) + if post == "不支持配送": detail_amount['tail_amount'] = 99999 return detail_amount - - post = filtered_df['post'].iloc[0] # 获取第一行的 '地区代码' 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] @@ -343,7 +360,82 @@ class AllLogistics_AU(TailLogistics): detail_amount['fuel'] += detail_amount[key] * self.fuel_rate detail_amount['tail_amount'] += detail_amount['fuel'] return detail_amount + +# 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 +# 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 if __name__ == '__main__': # 测试 aau = PostLogistics_AU() diff --git a/logisticsClass/logisticsTail_EUR.py b/logisticsClass/logisticsTail_EUR.py index 726feae..e000381 100644 --- a/logisticsClass/logisticsTail_EUR.py +++ b/logisticsClass/logisticsTail_EUR.py @@ -860,5 +860,111 @@ class EURZGLogistics_IR(EURZGLogistics): self.limit_kg = 30 self.remote = 999999 self.fuel_rate = 0.01 + def is_remote(self,postcode): + return 0 + + # DPD-NV,德国始发 +class DPDNVLogistics(TailLogistics): + """DPD-NV""" + currency = "EUR" + company = "DPD-NV" + def __init__(self): + super().__init__() + self.base_20 = None + self.base_31= None + self.oversize = 4.62 + self.big_package = 40.15 + self.remote = 0 + def is_remote(self,postcode): + """判断是否偏远,1偏远0非偏远""" + raise NotImplementedError("Subclasses must implement remote calculation.") + def calculate_fee(self,packages,postcode): + """计算快递费用,40kg,250cm长,330cm围长""" + detail_amount = { + "base":0.00, + "oversize":0.00, + "big_package":0.00, + "remote":0.00, + "tail_amount":0.00 + } + isremote = self.is_remote(postcode) + if isremote == "邮编格式不合法": + detail_amount['tail_amount'] = 99999 + return detail_amount + if isremote == 1: + detail_amount['remote'] = self.remote + + for package in packages: # 逐个处理列表中的每个包裹 + if package.fst_size >= 250 or package.weight >= 40000 or package.girth>=330: + detail_amount['tail_amount'] = 99999 + return detail_amount + detail_amount['base'] += self.base_31 if package.weight >= 20000 else self.base_20 + if package.fst_size >= 175 or package.weight >= 31500 or package.girth>=300: + detail_amount['big_package'] = self.big_package + elif package.fst_size >= 120 or package.sed_size>=60 or package.volume>=150000: + detail_amount['oversize'] = self.oversize + + + for key in detail_amount: + if key != 'tail_amount': + detail_amount['tail_amount'] += detail_amount[key] + return detail_amount +class DPDNVLogistics_DE(DPDNVLogistics): + country_code = 'DE' + country = 'Germany' + def __init__(self): + super().__init__() + self.base_20 = 4.3625 + self.base_31= 4.6125 + def is_remote(self,postcode): + return 0 + +class DPDNVLogistics_FR(DPDNVLogistics): + country_code = 'FR' + country = 'France' + def __init__(self): + super().__init__() + self.base_20 = 12.2375 + self.base_31= 12.4875 + def is_remote(self,postcode): + return 0 + +class DPDNVLogistics_IT(DPDNVLogistics): + country_code = 'IT' + country = 'Italy' + def __init__(self): + super().__init__() + self.base_20 = 14.0375 + self.base_31= 14.7375 + def is_remote(self,postcode): + return 0 + +class DPDNVLogistics_PT(DPDNVLogistics): + country_code = 'PT' + country = 'Portugal' + def __init__(self): + super().__init__() + self.base_20 = 18.45 + self.base_31= 22.375 + def is_remote(self,postcode): + return 0 + +class DPDNVLogistics_ES(DPDNVLogistics): + country_code = 'ES' + country = 'Spain' + def __init__(self): + super().__init__() + self.base_20 = 14.0375 + self.base_31=14.7375 + def is_remote(self,postcode): + return 0 + +class DPDNVLogistics_IR(DPDNVLogistics): + country_code = 'IR' + country = 'Ireland' + def __init__(self): + super().__init__() + self.base_20 = 18.45 + self.base_31= 22.375 def is_remote(self,postcode): return 0 \ No newline at end of file diff --git a/logisticsClass/logisticsTail_UK.py b/logisticsClass/logisticsTail_UK.py index 3f9e090..4371d00 100644 --- a/logisticsClass/logisticsTail_UK.py +++ b/logisticsClass/logisticsTail_UK.py @@ -271,9 +271,9 @@ class KPNVlogistics_UK(TailLogistics): # class KPDXLogistics_UK(TailLogistics): # country_code = 'UK' # country = 'United Kingdom' -# company = 'DX-EL' +# company = 'AIT-FUXIN' # currency = 'GBP' -# logistics_type = LogisticsType.LTL +# # logistics_type = LogisticsType.LTL # def __init__(self): # super().__init__() # self.base_fee = 0 @@ -283,6 +283,11 @@ class KPNVlogistics_UK(TailLogistics): # "oversize":0.00, # "tail_amount":0.00 # } +# for package in packages: +# if package.weight>=250000 or package.volume>=1500000: +# detail_amount['tail_amount'] = 99999 +# return detail_amount +# if if __name__ == '__main__': # # 关闭渠道 diff --git a/logisticsClass/logisticsTail_US.py b/logisticsClass/logisticsTail_US.py index e3a6d4b..41726a8 100644 --- a/logisticsClass/logisticsTail_US.py +++ b/logisticsClass/logisticsTail_US.py @@ -662,7 +662,6 @@ class MetroNYLogistics_US(TailLogistics): return detail_amount zone = zone_result.iloc[0] - total_cuft = 0 max_weight = 0 max_length = 0 diff --git a/sell/sell_price.py b/sell/sell_price.py index b5fabcc..465db4e 100644 --- a/sell/sell_price.py +++ b/sell/sell_price.py @@ -137,30 +137,30 @@ def air_order_price(packages): return express_fee, express_type # 美国售价2025 -def call_sell_price_2025(price, package_dict): +def call_sell_price_2025(price, packages): """ price:采购价 package_dict:包裹数据 head_type:头程类型 海运/空运 """ - packages = Package_group() - def extract_number(value): - # 提取字符串中的第一个数字 - match = re.search(r"[-+]?\d*\.\d+|\d+", str(value)) - return float(match.group()) if match else 0.0 + # packages = Package_group() + # def extract_number(value): + # # 提取字符串中的第一个数字 + # match = re.search(r"[-+]?\d*\.\d+|\d+", str(value)) + # return float(match.group()) if match else 0.0 - for key, package in package_dict.items(): - package['长'] = extract_number(package['长']) - package['宽'] = extract_number(package['宽']) - package['高'] = extract_number(package['高']) - package['重量'] = extract_number(package['重量']) + # for key, package in package_dict.items(): + # package['长'] = extract_number(package['长']) + # package['宽'] = extract_number(package['宽']) + # package['高'] = extract_number(package['高']) + # package['重量'] = extract_number(package['重量']) - if package['长'] == 0 or package['宽'] == 0 or package['高'] == 0 or package['重量'] == 0: - return 0,0,0 - packages.add_package(Package(key,package['长'], package['宽'], package['高'], package['重量'])) + # if package['长'] == 0 or package['宽'] == 0 or package['高'] == 0 or package['重量'] == 0: + # return 0,0,0 + # packages.add_package(Package(key,package['长'], package['宽'], package['高'], package['重量'])) - if packages is None: - return 0,0,0 + # if packages is None: + # return 0,0,0 litfad = SellPriceBase.litfad_2025(packages, price,1) # 修改版本,网站售价 sell_price = litfad.cal_sell_price_2025() diff --git a/utils/apply_active_config.py b/utils/apply_active_config.py index 08bd9ce..9fcb1d0 100644 --- a/utils/apply_active_config.py +++ b/utils/apply_active_config.py @@ -21,10 +21,10 @@ ACTIVE_LOGISTICS = { # EUR "DPDASLLogistics":False, # 需关闭 + "DPDNVLogistics":True, # GB - "bigLogistics_UK":False, # 需关闭 - "KPZGLogistics_UK":False, # 需关闭 + "KPZGLogistics_UK":True, # 需关闭 "bigLogistics_UK":False, # 需关闭,智谷旧版大件,8,新版是分区的 } diff --git a/utils/logistics_name_config.py b/utils/logistics_name_config.py index 6d986f7..f98fa85 100644 --- a/utils/logistics_name_config.py +++ b/utils/logistics_name_config.py @@ -48,7 +48,7 @@ logistics_name = { "空AMS-EUR":"EUR-ZG", "海NL-卡派2":"卡派-GEL", "海NL-DPD-ASL":"DPD-ASL", - "海NL-DPD-NV":"--" + "海NL-DPD-NV":"DPD-NV" } diff --git a/物流t投递审核.py b/物流t投递审核.py index d756742..21216d3 100644 --- a/物流t投递审核.py +++ b/物流t投递审核.py @@ -10,13 +10,38 @@ from utils.logisticsBill import BillFactory, Billing from utils.countryOperator import OperateCountry from utils.Package import Package, Package_group from utils.logistics_name_config import logistics_name +from datetime import date +# 货币转换,其他转RMB +def convert_currency(amount, current_currency): + """ + 货币转换 + """ + if amount is None or amount ==0: + return "金额为空" + if amount >=9999: + return "无可用渠道" + if current_currency == "USD": + amount=amount*7 + elif current_currency == "GBP": + amount =amount*9 + elif current_currency == "EUR": + amount = amount*8 + elif current_currency == "AUD": + amount = amount*5 + elif current_currency == "CAD": + amount = amount*5 + elif current_currency == "JPY": + amount =amount*0.05 + return amount + # 获取数据 def fetch_order_data(): """从数据库获取原始订单数据""" with MySQLconnect('ods') as db: sql = """ SELECT - ol.order_date, + DATE_FORMAT(ol.order_date, '%%Y-%%m-%%d') AS order_date, + DATE_FORMAT(oe.投递时间, '%%Y-%%m-%%d') AS 投递时间, ol.fund_status, oe.`包裹状态`, oe.包裹号 AS package, @@ -33,9 +58,8 @@ def fetch_order_data(): pvi.weight AS 重量, pfi.express_fee AS 基础估算, pfi.express_additional_fee AS 偶发估算, - pfi.express_fee + pfi.express_additional_fee AS 总估算, - - oe.快递公司 + pfi.express_fee + pfi.express_additional_fee AS 包裹总估算, + oe.快递公司 AS 投递渠道 FROM ods.order_express oe LEFT JOIN ods.express_company ecm ON oe.快递公司 = ecm.快递公司 @@ -43,14 +67,16 @@ def fetch_order_data(): LEFT JOIN ods.package_fee_info pfi ON oe.包裹号 = pfi.package LEFT JOIN ods.order_list ol ON oe.单号 = ol.order_id WHERE - oe.包裹状态 REGEXP '已经投递|发货仓出库' - AND oe.`快递公司` NOT REGEXP "--" + oe.包裹状态 not REGEXP '已作废|--|客户签收' + # AND oe.`快递公司` NOT REGEXP "--" AND `卡板发货时间` REGEXP "--" AND ol.fund_status NOT REGEXP '等待|全额退款' AND ol.site_name REGEXP 'litfad|kwoking|lakiq' AND oe.投递时间 >= DATE_SUB(NOW(), INTERVAL 3 DAY) - AND pvi.length>0 AND pvi.width >0 AND pvi.hight>0 AND pvi.weight>0 - and oe.目的国 regexp 'United States' + AND pvi.length>0 AND pvi.width >0 AND pvi.hight>0 AND pvi.weight>0 + and oe.目的国 regexp 'United States|Australia|United Kingdom|Germany|France|Spain|Italy|Netherlands|Belgium' + order by ol.order_id,ol.order_date + """ return pd.read_sql(sql, db.engine()) @@ -67,6 +93,7 @@ def cal_min_fee(raw_data: pd.DataFrame): package_group = Package_group() opCountry = OperateCountry(group['目的国'].iloc[0]) express_fee = 0 + express_type='' for index, row in group.iterrows(): # 计算一票一件 packages=Package_group() @@ -74,29 +101,37 @@ def cal_min_fee(raw_data: pd.DataFrame): packages.add_package(package) bill_express = Billing("1",opCountry,packages,row['postcode'],company_name=None,head_type=1,beizhu="") if bill_express.tail_amount[0] == 0 or bill_express.tail_amount[0] >=9999: - df.loc[index,"快递尾端费用"] = "不可派" + df.loc[index,"单票最小费用"] = "" + df.loc[index,"单票渠道"] = "" express_fee = 999999 + express_type = '不可派' else: - df.loc[index,"快递尾端费用"] = bill_express.tail_amount[0] - df.loc[index,"快递尾端渠道"] = bill_express.company_name + df.loc[index,"单票最小费用"] = bill_express.tail_amount[0] + df.loc[index,"单票渠道"] = bill_express.company_name express_fee += bill_express.tail_amount[0] + express_type = bill_express.logistic_type + if bill_express.logistic_type == '卡派': + express_type = '卡派单包裹' + # 计算一票多件 package_group.add_package(package) # 计算一票多件 if len(package_group) > 1: bill_ltl = Billing("1",opCountry,package_group,row['postcode'],company_name=None,head_type=1,beizhu="") - df.loc[df['order_id']==order_id,'卡派尾端费用'] = bill_ltl.tail_amount[0]/len(package_group) - df.loc[df['order_id']==order_id,'卡派尾端渠道'] = bill_ltl.company_name + if bill_ltl.tail_amount[0] == 0 or bill_ltl.tail_amount[0] >=9999: + df.loc[df['order_id']==order_id,'多票最小费用'] = "" + df.loc[df['order_id']==order_id,'多票渠道'] = "不可派" + df.loc[df['order_id']==order_id,'多票最小费用'] = bill_ltl.tail_amount[0]/len(package_group) + df.loc[df['order_id']==order_id,'多票渠道'] = bill_ltl.company_name min_fee = min(bill_ltl.tail_amount[0],express_fee) + df.loc[df['order_id']==order_id,'最优总费用'] = min_fee + df.loc[df['order_id']==order_id,'最优渠道类型'] = bill_ltl.logistic_type if min_fee == bill_ltl.tail_amount[0] else express_type else: min_fee = express_fee - if min_fee == express_fee: - df.loc[df['order_id']==order_id,'最优总物流费用'] = min_fee - df.loc[df['order_id']==order_id,'最优渠道类型'] = "快递" - else: - df.loc[df['order_id']==order_id,'最优总物流费用'] = min_fee - df.loc[df['order_id']==order_id,'最优渠道类型'] = "卡派" - df.loc[df['order_id']==order_id,'尾端货币'] = bill_ltl.tail_amount[1] + df.loc[df['order_id']==order_id,'最优总费用'] = min_fee + df.loc[df['order_id']==order_id,'最优渠道类型'] = express_type + + df.loc[df['order_id']==order_id,'尾端货币'] = bill_express.tail_amount[1] return df # 订单层面审核,防止出现混合渠道投递,卡派订单包含多个不同快递单号,多渠道订单总重量小于1000KG @@ -113,7 +148,7 @@ def analyze_orders(raw_data: pd.DataFrame): '渠道类型': '未知类型', '基础估算': 0, '偶发估算': 0, - '总估算': 0, + '包裹总估算': 0, '重量': 0, '长': 0, '宽': 0, @@ -137,16 +172,19 @@ def analyze_orders(raw_data: pd.DataFrame): grouped = data.groupby('order_id') aggregated = pd.DataFrame({ + '订单时间': grouped['order_date'].first(), + '最晚投递时间': grouped['投递时间'].max(), '包裹数量': grouped.size(), '总重量': grouped['重量'].sum(), - '总基础估算': grouped['基础估算'].sum(), - '总附加估算': grouped['偶发估算'].sum(), - '总物流估算': grouped['总估算'].sum(), + '订单总估算': grouped['包裹总估算'].sum(), '包裹数据': grouped.apply(create_package_details), # 使用新函数 - '快递公司列表': grouped['快递公司'].unique(), + '投递渠道列表': grouped['投递渠道'].unique(), '渠道类型列表': grouped['渠道类型'].unique(), - '邮编列表': grouped['postcode'].first(), - '快递跟踪号': grouped['快递跟踪号'].unique() + '快递跟踪号': grouped['快递跟踪号'].unique(), + '最优渠道推荐':grouped['最优渠道'].first(), + '最优渠道类型':grouped['最优渠道类型'].first(), + '最优总费用':grouped['最优总费用'].first(), + '费用差(RMB)':grouped['费用差(RMB)'].first(), }).reset_index() # 3. 实现业务逻辑判断(保持不变) @@ -159,7 +197,7 @@ def analyze_orders(raw_data: pd.DataFrame): return '未知类型' def determine_channel_type(row): - if len(row['快递公司列表']) > 1: + if len(row['投递渠道列表']) > 1: return '多渠道' else: return '单渠道' @@ -195,14 +233,14 @@ def analyze_orders(raw_data: pd.DataFrame): rule_results = aggregated.apply(apply_business_rules, axis=1) aggregated = pd.concat([aggregated, rule_results], axis=1) - + aggregated['测算日期'] = date.today().strftime("%Y-%m-%d") # 5. 整理最终输出列 final_columns = [ - 'order_id', '订单类型', '渠道种类', + 'order_id','订单时间','最晚投递时间', '订单类型', '渠道种类','快递跟踪号', '包裹数量', '总重量', - '总基础估算', '总附加估算', '总物流估算', - '快递公司列表', '邮编列表', - '包裹数据' ,'状态', '备注','快递跟踪号'# 使用新列名 + '订单总估算', + '投递渠道列表', + '包裹数据' ,'状态', '备注','最优渠道推荐','最优总费用','费用差(RMB)','测算日期'# 使用新列名 ] return aggregated[final_columns] @@ -216,48 +254,130 @@ def analyze_logistics(df: pd.DataFrame): # 1. 计算最优渠道和费用 df= cal_min_fee(df) # 判断渠道是否一致 - df['最优渠道'] = df.apply(lambda row: row['快递尾端渠道'] if row['最优渠道类型'] == "快递" else row['卡派尾端渠道'], axis=1) - df['渠道一致'] = df.apply(lambda row: row['最优渠道'] == logistics_name.get(row['快递公司']), axis=1) + df['测算日期'] = date.today().strftime("%Y-%m-%d") + df['最优渠道'] = df.apply(lambda row: row['单票渠道'] if row['最优渠道类型'] == "快递" or row['最优渠道类型'] == "卡派单包裹" else row['多票渠道'], axis=1) + df['渠道一致'] = df.apply(lambda row: row['最优渠道'] == logistics_name.get(row['投递渠道']), axis=1) # 2. 计算费用是否一致 + def all_estimate(row): - if row['总估算'] is None or row['总估算'] ==0: - return "暂无系统估算值" - if row['最优总物流费用'] is None or row['最优总物流费用'] ==0: - return "暂无最优费用" - if row['尾端货币'] == "USD": - all_estimate= row['总估算']/7 - elif row['尾端货币'] == "GBP": - all_estimate = row['总估算']/9 - elif row['尾端货币'] == "EUR": - all_estimate = row['总估算']/8 - elif row['尾端货币'] == "AUD": - all_estimate = row['总估算']/5 - elif row['尾端货币'] == "CAD": - all_estimate = row['总估算']/5 - elif row['尾端货币'] == "JPY": - all_estimate = row['总估算']/0.05 + if row['最优总费用'] >=9999: + return "费用有误" + all_estimate = convert_currency(row['最优总费用'], row['尾端货币']) return all_estimate - - df['费用一致'] = df.apply(lambda row: False if isinstance(all_estimate(row), str) else abs(all_estimate(row) - row['最优总物流费用']) < 1,axis=1) + df['订单总估算']= df.groupby('order_id')['包裹总估算'].transform('sum') + df['费用一致'] = df.apply(lambda row: False if isinstance(all_estimate(row), str) else abs(all_estimate(row) - row['订单总估算']) < 1,axis=1) - df['费用差(当地货币)'] = df.apply(lambda row: "费用有误" if isinstance(all_estimate(row), str) else row['最优总物流费用'] - all_estimate(row),axis=1) + df['费用差(RMB)'] = df.apply(lambda row: "费用有误" if isinstance(all_estimate(row), str) else round( all_estimate(row)-row['订单总估算'],2),axis=1) + df['是否改投'] = df.apply(lambda row: "不改投" if row['渠道一致'] == True else 0,axis=1) # 渠道一致只检查费用问题,无需改投,0不确定,需要人工确认 + df['异常情况'] = None + # 调整输出列 + final_columns = ['order_date','投递时间','fund_status','包裹状态','运输方式','快递跟踪号','目的国','postcode','快递分区','order_id','package','长','宽','高','重量', + '基础估算','偶发估算','包裹总估算','订单总估算','本地估算RMB','渠道类型','投递渠道','单票最小费用','单票渠道','多票最小费用','多票渠道','最优总费用', + '最优渠道','最优渠道类型','尾端货币','渠道一致','费用一致','费用差(RMB)','测算日期','是否改投','异常情况'] + return df[final_columns] + +# 系统渠道下的本地计算费用 +def local_fee_cal(df: pd.DataFrame): + df_grouped= df.groupby('快递跟踪号') + for order_num, group in df_grouped: + postcode = group['postcode'].iloc[0] + if pd.isna(postcode) or str(postcode).lower() == "nan": + continue + + packages= Package_group() # Metro-SAIR + company_name = logistics_name.get(group['投递渠道'].iloc[0]) + opCountry = OperateCountry(group['目的国'].iloc[0]) + + total_weight=0 # 按体积重分费用 + for index,row in group.iterrows(): + if row['长'] == 0 or row['宽'] == 0 or row['高'] == 0 or row['重量'] == 0: + continue + total_weight = row['长']*row['宽']*row['高']/6000 + package = Package(row['package'],row['长'],row['宽'],row['高'],row['重量']) + packages.add_package(package) + try: + bill = Billing(str(index),opCountry,packages,postcode,company_name=company_name,head_type=1,beizhu='1') + for index,row in group.iterrows(): + propertion = bill.bill_dict()["体积重"]/total_weight + tail_fee = bill.tail_amount[0]*propertion + # 转rmb + tail_fee = convert_currency(tail_fee, bill.tail_amount[1]) + df.loc[df['package']==row['package'],'本地估算RMB'] =round(tail_fee,2) if tail_fee <9999 else "暂无配置" + except: + df.loc[df['快递跟踪号'] == order_num, '本地估算RMB']= "暂无配置" + continue + print(bill) return df + +# 合并新旧df并写入 +def append_result(new_data, excel_path, only_columns): + try: + df_existing = pd.read_excel(excel_path,dtype={'order_id': str}) + except FileNotFoundError: + # 文件不存在就直接存 + new_data.to_excel(excel_path, index=False) + return + # 识别老表里的特殊列 + special_cols = [col for col in ['是否改投', '异常情况','是否处理'] if col in df_existing.columns] + # 新老合并(先全部concat起来以便后面筛选) + df_all = pd.concat([df_existing, new_data], ignore_index=True) + # 找出:重复的(即同时在新旧里都有的 only_columns 值) + duplicated_keys = set(df_existing[only_columns]) & set(new_data[only_columns]) + # 1️⃣ 对有重复的 key → 保留 旧表的特殊列 + 新表的其他列 + if duplicated_keys: + duplicated_keys = list(duplicated_keys) + # 老表保留特殊列 + old_part = df_existing[df_existing[only_columns].isin(duplicated_keys)][[only_columns] + special_cols] + # 新表保留除特殊列外的所有列 + new_part = new_data[new_data[only_columns].isin(duplicated_keys)] + new_part_no_special = new_part.drop(columns=special_cols, errors='ignore') + # 合并 + merged_part = new_part_no_special.merge(old_part, on=only_columns, how='left') + else: + merged_part = pd.DataFrame(columns=df_all.columns) # 空 + # 2️⃣ 对没有重复的 → 直接保留新表的完整行 + unique_new_part = new_data[~new_data[only_columns].isin(duplicated_keys)] + # 3️⃣ 把 老数据的全部 + 处理好的新数据拼起来 + final_result = pd.concat([df_existing, merged_part, unique_new_part], ignore_index=True) + # 去重(以 only_columns 为唯一键,保留最后一次出现的) + final_result = final_result.drop_duplicates(subset=[only_columns], keep='last') + # 写回 + final_result.to_excel(excel_path, index=False) + + def main(): + # 将前一天改投的数据保存到excel + # 1.先读取logistics_analysis,并筛选是否改投列为1的数据 + # 2.将筛选结果追加到另一个excel + df_new = pd.read_excel(r'D:\test\logistics\拦截数据\logistics_analysis.xlsx') + df_new = df_new [df_new ['是否改投'] == "是"] + df_new = df_new[['目的国','运输方式','order_id','package','基础估算','偶发估算','包裹总估算', + '渠道类型','最优渠道类型','投递渠道','最优渠道','尾端货币','订单总估算','最优总费用','费用差(RMB)','测算日期','是否改投','异常情况']] + target_file1 = r'D:\test\logistics\拦截数据\改投记录表.xlsx' + append_result(df_new,target_file1,'package') + print("前一天的数据已保存") + # 获取数据 raw_data = fetch_order_data() print('已获取数据') - # 订单层面审核 - order_result = analyze_orders(raw_data) - print('已完成订单层面审核') - order_result.to_excel(r'D:\test\logistics\拦截数据\order_analysis.xlsx', index=False) + # 本地计算投递渠道的费用 + order_result =local_fee_cal(raw_data) # 计算最优渠道和费用 raw_data = analyze_logistics(raw_data) + target_file2 = r'D:\test\logistics\拦截数据\logistics_analysis.xlsx' + append_result(raw_data,target_file2,'package') print('已完成物流费用层面审核') - raw_data.to_excel(r'D:\test\logistics\拦截数据\logistics_analysis.xlsx', index=False) + # 订单层面审核 + order_result = analyze_orders(raw_data) + target_file3 = r'D:\test\logistics\拦截数据\order_analysis.xlsx' + append_result(order_result,target_file3,'order_id') + print('已完成订单层面审核') + if __name__ == '__main__': - main() + # 取数 +