# -*- coding: utf-8 -*- import math from pathlib import Path import pandas as pd from utils.gtools import DBconnect # 根据SPU判断空运或海运,1为海运 def spu_head_type(base_df: pd.DataFrame): df = base_df.copy(deep=True) spu_type = {} # 字典的键为SPU,值为一个元组(start, end),表示该SPU在原始DataFrame中的索引范围 last_spu = None spu_start_index = None for i, row in df.iterrows(): current_spu = row['SPU'] if current_spu != last_spu: spu_start_index = i last_spu = current_spu else: # 如果是同一个SPU的连续行,则更新该SPU组的结束索引 if current_spu in spu_type: spu_type[current_spu][1] = i else: # 如果SPU组的起始索引存在,则初始化一个新的SPU组,并更新结束索引 spu_type[current_spu] = [spu_start_index, i] if i == len(df) - 1 or current_spu != df.iloc[i + 1]['SPU']: if current_spu not in spu_type: spu_type[current_spu] = [i, i] # 单独处理最后一行 if last_spu and last_spu not in spu_type: spu_type[last_spu] = [spu_start_index, spu_start_index] spu_flag_values = {} for spu, index_range in spu_type.items(): # 获取每个 SPU 组的起始索引和结束索引 start_index = index_range[0] end_index = index_range[1] sub_df = df.loc[start_index:end_index] flag = 0 for index, row in sub_df.iterrows(): item_list = [ {key: float(value) for key, value in package.items()} for package in row['packages'].values() ] for item in item_list: bag_list = [item['长'], item['宽'], item['高']] sizes = sorted(bag_list, reverse=True) volume_weight = (sizes[0] * sizes[1] * sizes[2]) / 6000 if sizes[0] > 50 or sizes[1] > 40 or sizes[2] > 30 or item['重量'] > 2718 or volume_weight > 4.5: flag = 1 break if flag: break spu_flag_values[spu] = flag df.loc[start_index:end_index, 'flag'] = flag return df class SellPriceBase: parent_current_directory = Path(__file__).parent.parent df_2025 = None df_2024 = None _instance = None def __new__(cls, *args, **kwargs): """实现单例模式,只加载一次文件""" if cls._instance is None: cls._instance = super().__new__(cls) with DBconnect() as db: if cls.df_2024 is None: cls.df_2024 = pd.read_sql("select * from sell_price_ending_2024", db.eng) if cls.df_2025 is None: cls.df_2025 = pd.read_sql("select * from sell_price_ending_2025", db.eng) return cls._instance return cls._instance def __init__(self, packages, purchase_price, shipping_type, ocean_first_cny, ocean_first_usd, air_first_usd, air_cny_type, air_first_fix, exchange_rate, profit_rate, air_rate,tax_rate): self.packages = packages # 单sku包裹数据 self.purchase_price = purchase_price # 采购价/成本价 self.ocean_first_cny = ocean_first_cny # 海运头程单价CNY self.ocean_first_usd = ocean_first_usd # 海运头程单价USD self.air_first_usd = air_first_usd # 空运头程单价USD self.air_cny_type = air_cny_type # 空运头程货型单价CNY self.air_first_fix = air_first_fix # 空运头程固定单价CNY self.exchange_rate = exchange_rate # 汇率 self.profit_rate = profit_rate # 利润系数 self.air_rate = air_rate # 空运分配占比 self.shipping_type = shipping_type # 1为海运0为空运,由spu定好的 self.tax_rate = tax_rate # 税率 # def get_fee_df(self): # if self.df_status == 0: # self.df_status = 1 # self.mat = MySQLconnect('mat') # self.engine = self.mat.engine() # self.adf = pd.read_sql('SELECT * FROM `usps_0814`', self.engine) # self.bdf = pd.read_sql('SELECT * FROM `uandf_0814`', self.engine) # self.cdf = pd.read_sql('SELECT * FROM `fedex_2504`', self.engine) # # 获取对应价格表 # def get_fee(self, head_type,adf,bdf,cdf): # mat = MySQLconnect('mat') # engine = mat.engine() # try: # if head_type == 0: # df = self.adf # elif head_type == 1: # df = self.bdf # elif head_type == 2: # df = self.cdf # else: # df = pd.DataFrame([99999], columns=['错误']) # except Exception as e: # print(f"发生错误: {e}") # df = pd.DataFrame() # finally: # engine.dispose() # return df # 计算快递费用 def cal_express_fee(self): head_type = 0 # 默认为USPS index = 0 # 标记卡派和不可售 for package in self.packages: volume_weight = package.get_volume_weight(6000) if package.weight > 420: head_type = 1 # UandF if package.fst_size > 50 or package.sed_size > 40 or package.trd_size > 30 or package.weight > 2718 or volume_weight > 3624: head_type = 2 # FEDEX if package.fst_size > 264 or package.weight > 67000 or volume_weight > 95000 or package.girth > 391: head_type = 3 # 卡派 index = 1 if package.fst_size > 310: head_type = 4 # 不可售 index = 1 lbs_weight_sum = 0 express_base_fee1 = 0 sku_express_fee = 0 # 单产品快递基础费 for package in self.packages: oz_weight = math.ceil(package.weight / 28) lbs_weight = math.ceil(max(package.get_volume_weight(8500) / 0.453, package.weight / 453)) lbs_weight_sum += lbs_weight express_base_fee = 0 oversize_charge = 0 ahs_weight = 0 ahs_dimension = 0 if head_type == 0: try: express_base_fee = self.df_2024[self.df_2024['oz'] == oz_weight]['最终费用'].iloc[0] / self.profit_rate except: head_type = 1 # USPSA2/FEDEXA1 if head_type == 1: try: express_base_fee = self.df_2024[self.df_2024['lbs小'] == lbs_weight]['加权价格'].iloc[0] / self.profit_rate except: head_type = 2 # FEDEX if head_type == 2: try: express_base_fee = self.df_2024[self.df_2024['lbs大'] == lbs_weight]['售价尾端价格'].iloc[0] except: express_base_fee = 99999 head_type = 3 # 额外费用计算 if package.fst_size > 238 or package.girth > 315: # 如果包裹符合大包裹 oversize_charge = 109.4 elif package.weight > 21000: # 注意是个负数 ahs_weight = -5.6 elif package.weight > 116 or package.girth > 251: # 虽然是0,但是保留这一条逻辑 ahs_dimension = 0 express_price = (express_base_fee + oversize_charge + ahs_weight + ahs_dimension) express_base_fee1 += express_base_fee sku_express_fee += express_price # 卡派 if head_type in (3, 4): continue # 计算合并的卡派价格 ltl_over_weightfee = 0 sku_total_cubic_feet = 0 # 库存总体立方英尺 ltlover_size = 0 for package in self.packages: cubic_feet = math.ceil(package.volume / 1000000 * 35.3147) # 1立方米 = 35.3147立方英尺 sku_total_cubic_feet += cubic_feet # 额外费用 if package.fst_size > 250: ltlover_size = round(110 / self.profit_rate - 118, 1) # 订单物流放118,其他在售价里 if 111000 <= package.weight < 130000: ltl_over_weightfee = max(ltl_over_weightfee, round(70 / self.profit_rate - 78, 1)) # 订单物流放78,其他在售价里 if 130000 <= package.weight < 157000: ltl_over_weightfee = max(ltl_over_weightfee, round(110 / self.profit_rate - 118, 1)) # 订单物流放118,其他在售价里 # 基础费用 if sku_total_cubic_feet < 25: ltl_base_fee1 = round(163 / self.profit_rate / 2, 2) elif sku_total_cubic_feet < 35: ltl_base_fee1 = round(180 / self.profit_rate / 2, 2) else: # 大于一个立方的(35立方英尺) 按照mei立方英尺*5美金 # 最低为190美金 ltl_base_fee1 = round(max(190, 5 * sku_total_cubic_feet) / self.profit_rate / 2, 2) ltl_base_fee = ltl_base_fee1 * 1.025 # 2.5%平摊偏远附加费成本 # 最后卡派费用等于基础费用+ 超长+ 超重费 ltl_fee = ltl_base_fee + ltlover_size + ltl_over_weightfee # 最后根据卡派费用和快递费用,取小值 if index == 0: # 该sku没有卡派和不可售包裹 sku_total_express_fee = min(ltl_fee, sku_express_fee) else: sku_total_express_fee = ltl_fee return sku_total_express_fee def cal_sell_price(self): sell_price = 0 express = self.cal_express_fee() if self.shipping_type == 0: # 空运 for package in self.packages: volume_weight = package.get_volume_weight(6000) type_weight = min(337, max(37, package.density)) # 货型 = 密度? air_first_cny = (self.air_cny_type * type_weight + self.air_first_fix) air_first_cny_total = air_first_cny * volume_weight air_first_usd_total = self.air_first_usd * volume_weight sell_price += (self.purchase_price / self.exchange_rate / self.profit_rate + ((air_first_cny_total / self.exchange_rate + air_first_usd_total) / self.profit_rate + express) * self.air_rate) else: # 海运 or 不可售 volume_weight = sum([package.get_volume_weight(6000) for package in self.packages]) ocean_first_cny_total = self.ocean_first_cny * volume_weight ocean_first_usd_total = self.ocean_first_usd * volume_weight sell_price = ((ocean_first_cny_total + self.purchase_price) / self.exchange_rate + ocean_first_usd_total) / self.profit_rate + express return sell_price # 计算快递费用 def cal_express_fee_2025(self): head_type = 0 # 默认为USPS index = 0 # 标记卡派和不可售 for package in self.packages: volume_weight = package.get_volume_weight(6000) if package.weight > 420: head_type = 1 # UandF if package.fst_size > 50 or package.sed_size > 40 or package.trd_size > 30 or package.weight > 2718 or volume_weight > 3624: head_type = 2 # FEDEX if package.fst_size > 264 or package.weight > 67000 or volume_weight > 95000 or package.girth > 391: head_type = 3 # 卡派 index = 1 if package.fst_size > 310: head_type = 4 # 不可售 index = 1 lbs_weight_sum = 0 express_base_fee1 = 0 sku_express_fee = 0 # 单产品快递基础费 for package in self.packages: oz_weight = math.ceil(package.weight / 28) lbs_weight = math.ceil(max(package.get_volume_weight(8500) / 0.453, package.weight / 453)) lbs_weight_sum += lbs_weight express_base_fee = 0 oversize_charge = 0 ahs_weight = 0 ahs_dimension = 0 # 本来是从数据库拿数据,我这里直接把数据放这里 if head_type == 0: try: express_base_fee = self.df_2025[self.df_2025['oz'] == oz_weight]['最终费用'].iloc[0] / self.profit_rate except: head_type = 1 # USPSA2/FEDEXA1 if head_type == 1: try: express_base_fee = self.df_2025[self.df_2025['lbs小'] == lbs_weight]['加权价格'].iloc[0] / self.profit_rate except: head_type = 2 # FEDEX if head_type == 2: try: express_base_fee = self.df_2025[self.df_2025['lbs大'] == lbs_weight]['售价尾端价格'].iloc[0] except: express_base_fee = 99999 head_type = 3 # 额外费用计算 if package.fst_size > 238 or package.girth > 315: # 如果包裹符合大包裹 oversize_charge = 109.4 elif package.weight > 21000: # 注意是个负数 ahs_weight = 6.1 elif package.length > 116 or package.girth > 251: # 虽然是0,但是保留这一条逻辑 ahs_dimension = 7.4 express_price = (express_base_fee + oversize_charge + ahs_weight + ahs_dimension) express_base_fee1 += express_base_fee sku_express_fee += express_price # 卡派 if head_type in (3, 4): continue # 计算合并的卡派价格 ltl_over_weightfee = 0 sku_total_cubic_feet = 0 # 库存总体立方英尺 ltlover_size = 0 for package in self.packages: cubic_feet = math.ceil(package.volume / 1000000 * 35.3147) # 1立方米 = 35.3147立方英尺 sku_total_cubic_feet += cubic_feet # 额外费用 if package.fst_size > 250: ltlover_size = round(110 / self.profit_rate - 118, 1) # 订单物流放118,其他在售价里 if 111000 <= package.weight < 130000: ltl_over_weightfee = max(ltl_over_weightfee, round(70 / self.profit_rate - 78, 1)) # 订单物流放78,其他在售价里 if 130000 <= package.weight < 157000: ltl_over_weightfee = max(ltl_over_weightfee, round(110 / self.profit_rate - 118, 1)) # 订单物流放118,其他在售价里 # 基础费用 if sku_total_cubic_feet < 25: ltl_base_fee1 = round(163 / self.profit_rate / 2, 2) elif sku_total_cubic_feet < 35: ltl_base_fee1 = round(180 / self.profit_rate / 2, 2) else: # 大于一个立方的(35立方英尺) 按照mei立方英尺*5美金 # 最低为190美金 ltl_base_fee1 = round(max(190, 5 * sku_total_cubic_feet) / self.profit_rate / 2, 2) ltl_base_fee = ltl_base_fee1 * 1.025 # 2.5%平摊偏远附加费成本 # 最后卡派费用等于基础费用+ 超长+ 超重费 ltl_fee = ltl_base_fee + ltlover_size + ltl_over_weightfee # 最后根据卡派费用和快递费用,取小值 if index == 0: # 该sku没有卡派和不可售包裹 sku_total_express_fee = min(ltl_fee, sku_express_fee) else: sku_total_express_fee = ltl_fee return sku_total_express_fee def cal_sell_price_2025(self): sell_price = 0 express = self.cal_express_fee_2025() if self.shipping_type == 0: # 空运 for package in self.packages: volume_weight = package.get_volume_weight(6000) type_weight = min(337, max(37, package.density)) # 货型 = 密度? air_first_cny = (self.air_cny_type * type_weight + self.air_first_fix) air_first_cny_total = air_first_cny * volume_weight air_first_usd_total = self.air_first_usd * volume_weight sell_price += (self.purchase_price / self.exchange_rate / self.profit_rate + (((air_first_cny_total / self.exchange_rate + air_first_usd_total)+ self.purchase_price*self.tax_rate/self.exchange_rate)/ self.profit_rate + express) * self.air_rate) else: # 海运 or 不可售 volume_weight = sum([package.get_volume_weight(6000) for package in self.packages]) ocean_first_cny_total = self.ocean_first_cny * volume_weight ocean_first_usd_total = self.ocean_first_usd * volume_weight sell_price = (((ocean_first_cny_total + self.purchase_price) / self.exchange_rate + ocean_first_usd_total) + self.purchase_price*self.tax_rate/self.exchange_rate) / self.profit_rate + express return (sell_price,express,ocean_first_cny_total,ocean_first_usd_total) @classmethod def litfad(cls, packages, purchase_price, shipping_type): return cls( packages, # 单sku包裹数据 purchase_price, # 采购价/成本价 shipping_type, # 1为海运0为空运,由spu定好的 ocean_first_cny=1.077, #1.077, # 海运头程单价CNY ocean_first_usd=1.06, #1.06, # 海运头程单价USD air_first_usd=0.65, # 空运头程单价USD air_cny_type=0.093, # 空运头程货型单价CNY air_first_fix=27.7, # 空运头程固定单价CNY exchange_rate=7, # 汇率 profit_rate=0.45, #0.45, # 利润系数 air_rate=0.7, # 空运分配占比 tax_rate = 0 # 税率 ) @classmethod def litfad_2025(cls, packages, purchase_price, shipping_type): return cls( packages, # 单sku包裹数据 purchase_price, # 采购价/成本价 shipping_type, # 1为海运0为空运,由spu定好的 ocean_first_cny=1, #1.077, # 海运头程单价CNY ocean_first_usd=1, #1.06, # 海运头程单价USD air_first_usd=0.65, # 空运头程单价USD air_cny_type=0.093, # 空运头程货型单价CNY air_first_fix=22.7, # 空运头程固定单价CNY exchange_rate=6.5, # 汇率 profit_rate=0.359, #0.45, # 利润系数 air_rate=0.7, # 空运分配占比 tax_rate = 0.145 # 税率 ) @classmethod def runnup(cls, packages, purchase_price, shipping_type): return cls( packages, # 单sku包裹数据 purchase_price, # 采购价/成本价 shipping_type, # 1为海运0为空运,由spu定好的 ocean_first_cny=1.077, # 海运头程单价CNY ocean_first_usd=1.06, # 海运头程单价USD air_first_usd=0.65, # 空运头程单价USD air_cny_type=0.093, # 空运头程货型单价CNY air_first_fix=27.7, # 空运头程固定单价CNY exchange_rate=6, # 汇率 profit_rate=0.45, # 利润系数 air_rate=0.7, # 空运分配占比 tax_rate = 0.145 # 税率 ) @classmethod def lakiq(cls, packages, purchase_price, shipping_type): return cls( packages, # 单sku包裹数据 purchase_price, # 采购价/成本价 shipping_type, # 1为海运0为空运,由spu定好的 ocean_first_cny=1.1, # 海运头程单价CNY ocean_first_usd=1.1, # 海运头程单价USD air_first_usd=0.65, # 空运头程单价USD air_cny_type=0.093, # 空运头程货型单价CNY air_first_fix=27.7, # 空运头程固定单价CNY exchange_rate=6, # 汇率 profit_rate=0.45, # 利润系数 air_rate=0.7, # 空运分配占比 tax_rate = 0.145 # 税率 ) @classmethod def kwoking(cls, packages, purchase_price, shipping_type): return cls( packages, # 单sku包裹数据 purchase_price, # 采购价/成本价 shipping_type, # 1为海运0为空运,由spu定好的 ocean_first_cny=1.08, # 海运头程单价CNY ocean_first_usd=1.07, # 海运头程单价USD air_first_usd=0.65, # 空运头程单价USD air_cny_type=0.093, # 空运头程货型单价CNY air_first_fix=27.7, # 空运头程固定单价CNY exchange_rate=6, # 汇率 profit_rate=0.45, # 利润系数 air_rate=0.7, # 空运分配占比 tax_rate = 0.145 # 税率 )