640 lines
25 KiB
Python
640 lines
25 KiB
Python
|
||
from rest_framework.response import Response
|
||
|
||
from .models import Dianpu, YonghuPingzheng
|
||
|
||
|
||
from products.models import Shangpin
|
||
from .models import Dianpu, DianpuMorenPeizhi
|
||
|
||
# shangdian/utils.py 或 tongji/utils.py
|
||
|
||
import logging
|
||
from datetime import date
|
||
from decimal import Decimal
|
||
from django.db import transaction
|
||
from django.db.models import F
|
||
from .models import Dianpu, DianpuShouzhiMeiriTongji
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def verify_shop_permission(request, frontend_user_id=None):
|
||
"""
|
||
验证商家后台用户身份及店铺状态
|
||
|
||
参数:
|
||
request: DRF request 对象
|
||
frontend_user_id: 前端传递的用户ID(用于防越权校验,可选)
|
||
|
||
返回:
|
||
(dianpu_obj, None) # 验证成功,返回店铺对象
|
||
(None, Response) # 验证失败,返回错误响应
|
||
"""
|
||
current_user = request.user
|
||
|
||
# 1. 从 request.user 获取当前认证用户的 yonghuid
|
||
# 注意:您的用户模型可能通过 JWT 的 sub 或其他字段标识,此处假设 request.user.yonghuid 存在
|
||
if not hasattr(current_user, 'yonghuid'):
|
||
logger.error(f"用户对象缺少 yonghuid 属性: {current_user}")
|
||
return None, Response(
|
||
{'code': 500, 'msg': '用户身份信息异常'}
|
||
)
|
||
|
||
auth_user_id = current_user.yonghuid
|
||
|
||
# 2. 越权检测:如果前端传了用户ID,必须与当前登录用户一致
|
||
if frontend_user_id is not None and str(frontend_user_id) != str(auth_user_id):
|
||
logger.warning(
|
||
f"越权尝试: 登录用户 {auth_user_id} 尝试操作 {frontend_user_id} 的数据"
|
||
)
|
||
return None, Response(
|
||
{'code': 403, 'msg': '无权操作其他用户数据'}
|
||
)
|
||
|
||
# 3. 查询用户凭证表,验证账号是否存在且状态正常
|
||
try:
|
||
pingzheng = YonghuPingzheng.objects.get(yonghu=auth_user_id)
|
||
if not pingzheng.is_active:
|
||
return None, Response(
|
||
{'code': 403, 'msg': '用户账号已被禁用'}
|
||
)
|
||
except YonghuPingzheng.DoesNotExist:
|
||
return None, Response(
|
||
{'code': 404, 'msg': '用户凭证不存在'}
|
||
)
|
||
|
||
# 4. 查询关联的店铺信息,验证店铺状态
|
||
try:
|
||
dianpu = Dianpu.objects.get(pingzheng=pingzheng)
|
||
if dianpu.zhuangtai != 1: # 1 表示正常
|
||
return None, Response(
|
||
{'code': 403, 'msg': '店铺已被封禁'}
|
||
)
|
||
except Dianpu.DoesNotExist:
|
||
return None, Response(
|
||
{'code': 404, 'msg': '店铺信息不存在'}
|
||
)
|
||
|
||
# 验证通过,返回店铺对象
|
||
return dianpu, None
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
'''def update_dianpu_daily_stat(
|
||
dianpu_id,
|
||
caozuo_leixing,
|
||
dingdan_jiage=Decimal('0.00'),
|
||
dianpu_shouyi=Decimal('0.00')
|
||
):
|
||
"""
|
||
原子更新店铺每日统计数据,并根据结算操作更新店铺余额与总额(高并发安全)
|
||
|
||
参数:
|
||
dianpu_id: 店铺ID (int)
|
||
caozuo_leixing: 操作类型
|
||
1 = 下单
|
||
2 = 结算(成交)
|
||
3 = 退款
|
||
dingdan_jiage: 订单金额(商品价格),默认0.00
|
||
dianpu_shouyi: 店铺本次收益金额,默认0.00
|
||
|
||
注意:
|
||
- 金额若小于0则自动设为0
|
||
- 日期自动取当天
|
||
- 使用 select_for_update 配合事务,保证并发时数据绝对准确
|
||
- 若店铺不存在,仅记录日志不中断主流程
|
||
- 结算操作会同步更新 Dianpu 表的 ketixian_yue 和 zhengqu_zonge
|
||
"""
|
||
# 参数归一化与合法性处理
|
||
dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00')
|
||
dianpu_shouyi = Decimal(str(dianpu_shouyi)) if dianpu_shouyi else Decimal('0.00')
|
||
|
||
if dingdan_jiage < 0:
|
||
dingdan_jiage = Decimal('0.00')
|
||
if dianpu_shouyi < 0:
|
||
dianpu_shouyi = Decimal('0.00')
|
||
|
||
today = date.today()
|
||
|
||
# 校验店铺是否存在(不加锁,仅校验)
|
||
if not Dianpu.objects.filter(id=dianpu_id).exists():
|
||
logger.error(f"店铺ID {dianpu_id} 不存在,统计更新失败")
|
||
return
|
||
|
||
# 开启事务,使用行锁确保并发安全
|
||
with transaction.atomic():
|
||
# 1. 获取或创建统计记录,并锁定该行
|
||
tongji, created = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create(
|
||
dianpu_id=dianpu_id,
|
||
riqi=today,
|
||
defaults={
|
||
'nian': today.year,
|
||
'yue': today.month,
|
||
'ri': today.day,
|
||
'xiadan_zongliang': 0,
|
||
'xiadan_zonge': Decimal('0.00'),
|
||
'chengjiao_zongliang': 0,
|
||
'chengjiao_zonge': Decimal('0.00'),
|
||
'tuikuan_zongliang': 0,
|
||
'tuikuan_zonge': Decimal('0.00'),
|
||
'shouyi_zonge': Decimal('0.00'),
|
||
}
|
||
)
|
||
|
||
# 根据操作类型构建统计更新字段
|
||
update_fields = {}
|
||
|
||
if caozuo_leixing == 1: # 下单
|
||
update_fields['xiadan_zongliang'] = F('xiadan_zongliang') + 1
|
||
update_fields['xiadan_zonge'] = F('xiadan_zonge') + dingdan_jiage
|
||
|
||
elif caozuo_leixing == 2: # 结算(成交)
|
||
update_fields['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1
|
||
update_fields['chengjiao_zonge'] = F('chengjiao_zonge') + dingdan_jiage
|
||
update_fields['shouyi_zonge'] = F('shouyi_zonge') + dianpu_shouyi
|
||
|
||
# 结算时同步更新店铺表的可提现余额和赚取总额(需加锁店铺行)
|
||
try:
|
||
dianpu = Dianpu.objects.select_for_update().get(id=dianpu_id)
|
||
dianpu.ketixian_yue = F('ketixian_yue') + dianpu_shouyi
|
||
dianpu.zhengqu_zonge = F('zhengqu_zonge') + dianpu_shouyi
|
||
dianpu.save(update_fields=['ketixian_yue', 'zhengqu_zonge', 'update_time'])
|
||
except Dianpu.DoesNotExist:
|
||
logger.error(f"结算时店铺ID {dianpu_id} 不存在,但已校验过,此处不应发生")
|
||
raise
|
||
|
||
elif caozuo_leixing == 3: # 退款
|
||
update_fields['tuikuan_zongliang'] = F('tuikuan_zongliang') + 1
|
||
update_fields['tuikuan_zonge'] = F('tuikuan_zonge') + dingdan_jiage
|
||
# 退款是否扣减收益需根据业务决定,此处只累加退款统计
|
||
|
||
else:
|
||
logger.warning(f"未知的操作类型: {caozuo_leixing}")
|
||
return
|
||
|
||
# 原子更新统计字段
|
||
if update_fields:
|
||
for field, value in update_fields.items():
|
||
setattr(tongji, field, value)
|
||
tongji.save(update_fields=list(update_fields.keys()))
|
||
|
||
logger.info(
|
||
f"店铺统计更新: 店铺ID={dianpu_id}, 操作类型={caozuo_leixing}, "
|
||
f"订单金额={dingdan_jiage}, 店铺收益={dianpu_shouyi}, 日期={today}"
|
||
)
|
||
|
||
|
||
|
||
|
||
|
||
def calculate_pingtai_and_dianpu_shouyi(shangpin_id, dingdan_jiage, dashou_fencheng):
|
||
"""
|
||
根据商品价格和打手分成计算平台收益和店铺收益
|
||
|
||
逻辑:
|
||
总收益 = 订单金额 - 打手分成
|
||
1. 从商品表获取所属店铺ID
|
||
2. 若商品无店铺ID:平台收益 = 总收益,店铺收益 = 0
|
||
3. 若有店铺ID:
|
||
- 查询店铺配置:
|
||
* 若店铺开启抽成,则费率 = 店铺表.shouyi_choucheng_feilv
|
||
* 否则从店铺默认配置表(ID=1)获取默认费率,若记录不存在则自动创建
|
||
- 平台收益 = 总收益 * 费率
|
||
- 店铺收益 = 总收益 - 平台收益
|
||
|
||
参数:
|
||
shangpin_id: 商品ID (int)
|
||
dingdan_jiage: 订单金额(商品价格)(Decimal 或 数字)
|
||
dashou_fencheng: 打手分成金额 (Decimal 或 数字)
|
||
|
||
返回:
|
||
(dianpu_id, pingtai_shouyi, dianpu_shouyi) 三元组
|
||
dianpu_id 可能为 None
|
||
pingtai_shouyi 必定有值
|
||
dianpu_shouyi 必定有值
|
||
"""
|
||
# 类型转换与安全处理
|
||
dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00')
|
||
dashou_fencheng = Decimal(str(dashou_fencheng)) if dashou_fencheng else Decimal('0.00')
|
||
if dingdan_jiage < 0:
|
||
dingdan_jiage = Decimal('0.00')
|
||
if dashou_fencheng < 0:
|
||
dashou_fencheng = Decimal('0.00')
|
||
|
||
# 计算总收益(可用于抽成的基础)
|
||
zong_shouyi = dingdan_jiage - dashou_fencheng
|
||
if zong_shouyi < 0:
|
||
zong_shouyi = Decimal('0.00')
|
||
|
||
# 获取商品信息及所属店铺
|
||
try:
|
||
shangpin = Shangpin.objects.select_related('dianpu').only('id', 'dianpu').get(id=shangpin_id)
|
||
except Shangpin.DoesNotExist:
|
||
logger.error(f"计算收益失败:商品ID {shangpin_id} 不存在")
|
||
return None, zong_shouyi, Decimal('0.00') # 无商品时平台收益为总收益,店铺0
|
||
|
||
dianpu_id = shangpin.dianpu_id
|
||
|
||
# 无店铺ID:平台拿走全部总收益,店铺收益为0
|
||
if dianpu_id is None:
|
||
logger.info(f"商品 {shangpin_id} 未关联店铺,平台收益 = 总收益 {zong_shouyi}")
|
||
return None, zong_shouyi, Decimal('0.00')
|
||
|
||
# 有店铺ID:查询店铺抽成配置
|
||
try:
|
||
dianpu = Dianpu.objects.only(
|
||
'id', 'kaiqi_shouyi_choucheng', 'shouyi_choucheng_feilv'
|
||
).get(id=dianpu_id)
|
||
except Dianpu.DoesNotExist:
|
||
logger.error(f"计算收益失败:店铺ID {dianpu_id} 不存在")
|
||
return dianpu_id, zong_shouyi, Decimal('0.00') # 店铺不存在时平台收益也为总收益
|
||
|
||
if dianpu.kaiqi_shouyi_choucheng and dianpu.shouyi_choucheng_feilv is not None:
|
||
feilv = dianpu.shouyi_choucheng_feilv
|
||
else:
|
||
# 从默认配置表获取或创建
|
||
moren_peizhi, created = DianpuMorenPeizhi.objects.get_or_create(
|
||
id=1,
|
||
defaults={
|
||
'moren_choucheng_feilv': Decimal('0.1000'), # 默认10%
|
||
'moren_tixian_xiane': Decimal('1000.00')
|
||
}
|
||
)
|
||
feilv = moren_peizhi.moren_choucheng_feilv
|
||
|
||
# 确保费率在合法范围内
|
||
if feilv is None or feilv < 0:
|
||
feilv = Decimal('0.0000')
|
||
if feilv > 1:
|
||
feilv = Decimal('1.0000')
|
||
|
||
# 计算平台收益(费率 * 总收益)
|
||
pingtai_shouyi = zong_shouyi * feilv
|
||
# 店铺收益 = 总收益 - 平台收益
|
||
dianpu_shouyi = zong_shouyi - pingtai_shouyi
|
||
|
||
logger.info(
|
||
f"收益计算: 商品ID={shangpin_id}, 订单金额={dingdan_jiage}, "
|
||
f"打手分成={dashou_fencheng}, 总收益={zong_shouyi}, 费率={feilv}, "
|
||
f"平台收益={pingtai_shouyi}, 店铺收益={dianpu_shouyi}"
|
||
)
|
||
|
||
return dianpu_id, pingtai_shouyi, dianpu_shouyi'''
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
def update_dianpu_daily_stat(
|
||
dianpu_id,
|
||
caozuo_leixing,
|
||
dingdan_jiage=Decimal('0.00'),
|
||
dianpu_shouyi=Decimal('0.00'),
|
||
shangji_dianpu_id=None, # 新增:上级店铺ID
|
||
shangji_fenhong=Decimal('0.00') # 新增:该笔订单给上级的分红
|
||
):
|
||
"""
|
||
原子更新店铺每日统计数据,结算时同步更新店铺余额,并安全处理上级分红统计。
|
||
|
||
参数:
|
||
dianpu_id: 本店铺ID
|
||
caozuo_leixing: 1=下单, 2=结算, 3=退款
|
||
dingdan_jiage: 订单金额
|
||
dianpu_shouyi: 店铺自身收益
|
||
shangji_dianpu_id: 上级店铺ID(可选)
|
||
shangji_fenhong: 给上级的分红金额(可选)
|
||
|
||
注意:
|
||
- 金额自动归一化,负数取0
|
||
- 使用 select_for_update 行锁保证并发安全
|
||
- 结算时,本店余额和统计先落定,上级分红统计放在 try 块内,失败不影响本店
|
||
- 新增字段均包含在统计表中:给上级次数/总额/上级ID,以及作为上级获得次数/总额
|
||
"""
|
||
# 金额安全处理
|
||
dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00')
|
||
dianpu_shouyi = Decimal(str(dianpu_shouyi)) if dianpu_shouyi else Decimal('0.00')
|
||
shangji_fenhong = Decimal(str(shangji_fenhong)) if shangji_fenhong else Decimal('0.00')
|
||
if dingdan_jiage < 0: dingdan_jiage = Decimal('0.00')
|
||
if dianpu_shouyi < 0: dianpu_shouyi = Decimal('0.00')
|
||
if shangji_fenhong < 0: shangji_fenhong = Decimal('0.00')
|
||
|
||
today = date.today()
|
||
|
||
# 轻量校验店铺存在性
|
||
if not Dianpu.objects.filter(id=dianpu_id).exists():
|
||
logger.error(f"店铺ID {dianpu_id} 不存在,统计更新失败")
|
||
return
|
||
|
||
# 开启事务,全部使用行锁
|
||
with transaction.atomic():
|
||
# 1. 获取或创建本店今日统计记录并锁定
|
||
tongji, created = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create(
|
||
dianpu_id=dianpu_id,
|
||
riqi=today,
|
||
defaults={
|
||
'nian': today.year,
|
||
'yue': today.month,
|
||
'ri': today.day,
|
||
'xiadan_zongliang': 0,
|
||
'xiadan_zonge': Decimal('0.00'),
|
||
'chengjiao_zongliang': 0,
|
||
'chengjiao_zonge': Decimal('0.00'),
|
||
'tuikuan_zongliang': 0,
|
||
'tuikuan_zonge': Decimal('0.00'),
|
||
'shouyi_zonge': Decimal('0.00'),
|
||
# 新增字段默认值
|
||
'shangji_fenhong_cishu': 0,
|
||
'shangji_fenhong_zonge': Decimal('0.00'),
|
||
'gei_shangji_cishu': 0,
|
||
'gei_shangji_zonge': Decimal('0.00'),
|
||
'shangji_id': None,
|
||
}
|
||
)
|
||
|
||
# 构建更新字段字典
|
||
update_fields_dict = {}
|
||
|
||
if caozuo_leixing == 1: # 下单
|
||
update_fields_dict['xiadan_zongliang'] = F('xiadan_zongliang') + 1
|
||
update_fields_dict['xiadan_zonge'] = F('xiadan_zonge') + dingdan_jiage
|
||
|
||
elif caozuo_leixing == 2: # 结算
|
||
# --- 本店成交统计 ---
|
||
update_fields_dict['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1
|
||
update_fields_dict['chengjiao_zonge'] = F('chengjiao_zonge') + dingdan_jiage
|
||
update_fields_dict['shouyi_zonge'] = F('shouyi_zonge') + dianpu_shouyi
|
||
|
||
# --- 更新本店主表余额(加锁) ---
|
||
try:
|
||
dianpu_locked = Dianpu.objects.select_for_update().get(id=dianpu_id)
|
||
dianpu_locked.ketixian_yue = F('ketixian_yue') + dianpu_shouyi
|
||
dianpu_locked.zhengqu_zonge = F('zhengqu_zonge') + dianpu_shouyi
|
||
dianpu_locked.save(update_fields=['ketixian_yue', 'zhengqu_zonge', 'update_time'])
|
||
except Dianpu.DoesNotExist:
|
||
logger.error(f"结算时店铺 {dianpu_id} 不存在")
|
||
raise
|
||
|
||
# --- 处理给上级的分红(本店作为下级)---
|
||
if shangji_dianpu_id and shangji_fenhong > 0:
|
||
# 更新本店统计的“给上级”字段
|
||
update_fields_dict['gei_shangji_cishu'] = F('gei_shangji_cishu') + 1
|
||
update_fields_dict['gei_shangji_zonge'] = F('gei_shangji_zonge') + shangji_fenhong
|
||
|
||
# 设置当天上级店铺ID(仅当天第一次结算时写入,后续保持不变)
|
||
current_shangji_id = tongji.shangji_id
|
||
if current_shangji_id is None:
|
||
update_fields_dict['shangji_id'] = shangji_dianpu_id
|
||
elif current_shangji_id != shangji_dianpu_id:
|
||
logger.warning(
|
||
f"店铺 {dianpu_id} 当天上级ID不一致,已有 {current_shangji_id},"
|
||
f"新上级 {shangji_dianpu_id},保持原上级ID"
|
||
)
|
||
|
||
# --- 更新上级店铺的“作为上级获得”统计(完全隔离保护) ---
|
||
try:
|
||
_update_shangji_gain_stat_within_tx(shangji_dianpu_id, shangji_fenhong)
|
||
except Exception as e:
|
||
logger.error(
|
||
f"更新上级店铺 {shangji_dianpu_id} 统计失败,不影响本店: {e}"
|
||
)
|
||
|
||
elif caozuo_leixing == 3: # 退款
|
||
update_fields_dict['tuikuan_zongliang'] = F('tuikuan_zongliang') + 1
|
||
update_fields_dict['tuikuan_zonge'] = F('tuikuan_zonge') + dingdan_jiage
|
||
else:
|
||
logger.warning(f"未知操作类型: {caozuo_leixing}")
|
||
return
|
||
|
||
# 原子更新本店统计字段
|
||
if update_fields_dict:
|
||
for field, value in update_fields_dict.items():
|
||
setattr(tongji, field, value)
|
||
tongji.save(update_fields=list(update_fields_dict.keys()))
|
||
|
||
logger.info(
|
||
f"店铺统计更新完成: dianpu={dianpu_id}, 操作={caozuo_leixing}, "
|
||
f"金额={dingdan_jiage}, 店铺收益={dianpu_shouyi}, "
|
||
f"上级ID={shangji_dianpu_id}, 上级分红={shangji_fenhong}"
|
||
)
|
||
|
||
|
||
def _update_shangji_gain_stat_within_tx(shangji_dianpu_id, fenhong_jine):
|
||
"""
|
||
在事务内更新上级店铺的“作为上级获得”统计及主表余额。
|
||
注意:本函数必须由处于同一事务中的 update_dianpu_daily_stat 调用,
|
||
并且调用方已获取必要的行锁,这里再次加锁不会死锁(按相同顺序加锁)。
|
||
"""
|
||
today = date.today()
|
||
|
||
# 获取或创建上级店铺的今日统计记录并加锁
|
||
tongji_shangji, _ = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create(
|
||
dianpu_id=shangji_dianpu_id,
|
||
riqi=today,
|
||
defaults={
|
||
'nian': today.year,
|
||
'yue': today.month,
|
||
'ri': today.day,
|
||
'xiadan_zongliang': 0,
|
||
'xiadan_zonge': Decimal('0.00'),
|
||
'chengjiao_zongliang': 0,
|
||
'chengjiao_zonge': Decimal('0.00'),
|
||
'tuikuan_zongliang': 0,
|
||
'tuikuan_zonge': Decimal('0.00'),
|
||
'shouyi_zonge': Decimal('0.00'),
|
||
'shangji_fenhong_cishu': 0,
|
||
'shangji_fenhong_zonge': Decimal('0.00'),
|
||
'gei_shangji_cishu': 0,
|
||
'gei_shangji_zonge': Decimal('0.00'),
|
||
'shangji_id': None,
|
||
}
|
||
)
|
||
|
||
# 更新上级店铺的“作为上级获得”字段
|
||
tongji_shangji.shangji_fenhong_cishu = F('shangji_fenhong_cishu') + 1
|
||
tongji_shangji.shangji_fenhong_zonge = F('shangji_fenhong_zonge') + fenhong_jine
|
||
tongji_shangji.save(update_fields=['shangji_fenhong_cishu', 'shangji_fenhong_zonge', 'update_time'])
|
||
|
||
# 更新上级店铺主表的分红余额和累计分红
|
||
shangji = Dianpu.objects.select_for_update().get(id=shangji_dianpu_id)
|
||
shangji.shangji_fenhong_leiji = F('shangji_fenhong_leiji') + fenhong_jine
|
||
shangji.shangji_ketixian_fenhong = F('shangji_ketixian_fenhong') + fenhong_jine
|
||
shangji.save(update_fields=['shangji_fenhong_leiji', 'shangji_ketixian_fenhong', 'update_time'])
|
||
|
||
logger.info(f"上级店铺 {shangji_dianpu_id} 获得分红 +{fenhong_jine}")
|
||
|
||
|
||
|
||
def calculate_pingtai_and_dianpu_shouyi(shangpin_id, dingdan_jiage, dashou_fencheng):
|
||
"""
|
||
计算平台收益、店铺收益、上级分红
|
||
|
||
分配链路(上级分红完全隔离,不影响平台及店铺核心计算):
|
||
总收益 = 订单金额 - 打手分成
|
||
平台抽成 = 总收益 * (店铺开启则用店铺费率,否则用平台默认费率)
|
||
剩余收益 = 总收益 - 平台抽成
|
||
上级分红 = 剩余收益 * 本店设置的上级分红费率(若有上级且满足条件)
|
||
店铺收益 = 剩余收益 - 上级分红
|
||
|
||
参数:
|
||
shangpin_id: 商品ID
|
||
dingdan_jiage: 订单金额
|
||
dashou_fencheng: 打手分成金额
|
||
|
||
返回:
|
||
五元组 (dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong)
|
||
- dianpu_id: 店铺ID,可能为None
|
||
- pingtai_shouyi: 平台收益
|
||
- dianpu_shouyi: 店铺最终收益
|
||
- shangji_dianpu_id: 上级店铺ID,可能为None
|
||
- shangji_fenhong: 上级分红金额,默认0.00
|
||
"""
|
||
# ==================== 金额安全处理 ====================
|
||
dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00')
|
||
dashou_fencheng = Decimal(str(dashou_fencheng)) if dashou_fencheng else Decimal('0.00')
|
||
if dingdan_jiage < 0:
|
||
dingdan_jiage = Decimal('0.00')
|
||
if dashou_fencheng < 0:
|
||
dashou_fencheng = Decimal('0.00')
|
||
|
||
zong_shouyi = dingdan_jiage - dashou_fencheng
|
||
if zong_shouyi < 0:
|
||
zong_shouyi = Decimal('0.00')
|
||
|
||
# ==================== 获取商品及店铺 ====================
|
||
try:
|
||
shangpin = Shangpin.objects.select_related('dianpu').only('id', 'dianpu').get(id=shangpin_id)
|
||
except Shangpin.DoesNotExist:
|
||
logger.error(f"收益计算失败:商品ID {shangpin_id} 不存在")
|
||
return None, zong_shouyi, Decimal('0.00'), None, Decimal('0.00')
|
||
|
||
dianpu_id = shangpin.dianpu_id
|
||
if dianpu_id is None:
|
||
logger.info(f"商品 {shangpin_id} 未关联店铺,平台收益={zong_shouyi}")
|
||
return None, zong_shouyi, Decimal('0.00'), None, Decimal('0.00')
|
||
|
||
# ==================== 获取店铺配置 ====================
|
||
try:
|
||
dianpu = Dianpu.objects.only(
|
||
'id', 'kaiqi_shouyi_choucheng', 'shouyi_choucheng_feilv',
|
||
'yaoqing_dianpu_id', 'fenhong_choucheng_feilv'
|
||
).get(id=dianpu_id)
|
||
except Dianpu.DoesNotExist:
|
||
logger.error(f"收益计算失败:店铺ID {dianpu_id} 不存在")
|
||
return dianpu_id, zong_shouyi, Decimal('0.00'), None, Decimal('0.00')
|
||
|
||
# ==================== 平台抽成 ====================
|
||
if dianpu.kaiqi_shouyi_choucheng and dianpu.shouyi_choucheng_feilv is not None:
|
||
feilv = dianpu.shouyi_choucheng_feilv
|
||
else:
|
||
moren_peizhi, created = DianpuMorenPeizhi.objects.get_or_create(
|
||
id=1,
|
||
defaults={
|
||
'moren_choucheng_feilv': Decimal('0.1000'),
|
||
'moren_tixian_xiane': Decimal('1000.00')
|
||
}
|
||
)
|
||
feilv = moren_peizhi.moren_choucheng_feilv
|
||
if feilv is None or feilv < 0:
|
||
feilv = Decimal('0.0000')
|
||
if feilv > 1:
|
||
feilv = Decimal('1.0000')
|
||
|
||
pingtai_shouyi = zong_shouyi * feilv
|
||
shengyu = zong_shouyi - pingtai_shouyi
|
||
|
||
# ==================== 上级分红(完全隔离,不影响店铺收益) ====================
|
||
shangji_dianpu_id = dianpu.yaoqing_dianpu_id
|
||
shangji_fenhong = Decimal('0.00')
|
||
|
||
if shangji_dianpu_id and dianpu.fenhong_choucheng_feilv:
|
||
try:
|
||
# 检查上级店铺状态
|
||
shangji = Dianpu.objects.filter(id=shangji_dianpu_id).only('id', 'zhuangtai').first()
|
||
if shangji and shangji.zhuangtai == 1:
|
||
fz_rate = dianpu.fenhong_choucheng_feilv
|
||
if fz_rate and fz_rate > 0:
|
||
shangji_fenhong = shengyu * fz_rate
|
||
if shangji_fenhong < 0:
|
||
shangji_fenhong = Decimal('0.00')
|
||
if shangji_fenhong > shengyu:
|
||
shangji_fenhong = shengyu
|
||
shengyu -= shangji_fenhong
|
||
else:
|
||
if shangji:
|
||
logger.info(f"上级店铺 {shangji_dianpu_id} 状态非正常,不参与分红")
|
||
else:
|
||
logger.warning(f"上级店铺ID {shangji_dianpu_id} 不存在,不参与分红")
|
||
except Exception as e:
|
||
logger.exception(f"上级分红计算异常,忽略: {e}")
|
||
shangji_fenhong = Decimal('0.00')
|
||
# 注意:shengyu 未被修改,店铺收益不变
|
||
|
||
dianpu_shouyi = shengyu
|
||
|
||
logger.info(
|
||
f"收益计算完成: 商品={shangpin_id}, 总收益={zong_shouyi}, 平台={pingtai_shouyi}, "
|
||
f"上级ID={shangji_dianpu_id}, 上级分红={shangji_fenhong}, 店铺={dianpu_shouyi}"
|
||
)
|
||
|
||
return dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
def validate_shangpin_and_dianpu(shangpin_id):
|
||
"""
|
||
校验商品及关联店铺(区分有无店铺)
|
||
|
||
无店铺:仅校验商品是否上架
|
||
有店铺:校验商品审核、封禁、上架,以及店铺是否封禁
|
||
|
||
返回:
|
||
(True, None, shangpin_obj) # 通过
|
||
(False, "错误信息", None) # 失败
|
||
"""
|
||
try:
|
||
shangpin = Shangpin.objects.select_related('dianpu').get(id=shangpin_id)
|
||
except Shangpin.DoesNotExist:
|
||
return False, "商品不存在", None
|
||
|
||
# 无店铺:只检查上架
|
||
if not shangpin.dianpu_id:
|
||
if not shangpin.shangjia_zhuangtai:
|
||
return False, "商品已下架", None
|
||
return True, None, shangpin
|
||
|
||
# 有店铺:完整校验
|
||
if not shangpin.shenhe_zhuangtai:
|
||
return False, "商品未通过平台审核", None
|
||
if shangpin.fengjin_zhuangtai:
|
||
return False, "商品已被封禁", None
|
||
if not shangpin.shangjia_zhuangtai:
|
||
return False, "商品已下架", None
|
||
|
||
try:
|
||
if shangpin.dianpu.zhuangtai != 1:
|
||
return False, "店铺已被封禁", None
|
||
except Exception:
|
||
return False, "关联店铺异常", None
|
||
|
||
return True, None, shangpin |