logistics/sell/base_sell_price.py

451 lines
20 KiB
Python
Raw Normal View History

2025-06-17 13:40:20 +08:00
# -*- coding: utf-8 -*-
import math
from pathlib import Path
import pandas as pd
from utils.gtools import MySQLconnect
# 根据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
price_path = parent_current_directory.joinpath("data")
_price_files = price_path.joinpath("售价尾端价格.xlsx")
df_2025 = None
2025-06-23 16:08:03 +08:00
df_2024 = None
2025-06-17 13:40:20 +08:00
def __new__(cls, *args, **kwargs):
"""实现单例模式,只加载一次文件"""
2025-06-23 16:08:03 +08:00
if cls.df_2024 is None:
cls.df_2024 = pd.read_excel(cls._price_files,sheet_name="2024")
2025-06-17 13:40:20 +08:00
if cls.df_2025 is None:
2025-06-23 16:08:03 +08:00
cls.df_2025 = pd.read_excel(cls._price_files,sheet_name="2025")
2025-06-17 13:40:20 +08:00
return super().__new__(cls)
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 # 税率
2025-06-23 16:08:03 +08:00
# 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()
2025-06-17 13:40:20 +08:00
2025-06-23 16:08:03 +08:00
# 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
2025-06-17 13:40:20 +08:00
# 计算快递费用
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:
2025-06-23 16:08:03 +08:00
express_base_fee = self.df_2024[self.df_2024['oz'] == oz_weight]['最终费用'].iloc[0] / self.profit_rate
2025-06-17 13:40:20 +08:00
except:
head_type = 1
# USPSA2/FEDEXA1
if head_type == 1:
try:
2025-06-23 16:08:03 +08:00
express_base_fee = self.df_2024[self.df_2024['lbs小'] == lbs_weight]['加权价格'].iloc[0] / self.profit_rate
2025-06-17 13:40:20 +08:00
except:
head_type = 2
# FEDEX
if head_type == 2:
try:
2025-06-23 16:08:03 +08:00
express_base_fee = self.df_2024[self.df_2024['lbs大'] == lbs_weight]['售价尾端价格'].iloc[0]
2025-06-17 13:40:20 +08:00
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):
2025-06-23 16:08:03 +08:00
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):
2025-06-17 13:40:20 +08:00
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 # 税率
)