logistics/sell/base_sell_price.py

455 lines
21 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.

# -*- 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 # 税率
)