# yonghu/fadan_fenhong_utils.py # 罚款缴纳分红处理公共方法(支持更新罚单中的申请人分红金额) import logging from decimal import Decimal from django.db import transaction from orders.models import Penalty, PenaltyBonus, PenaltyBonusRate from users.models import UserShangjia logger = logging.getLogger(__name__) # 身份映射字典 # 罚单 ApplicantIdentity(shenqingren_shenfen)→ fadan_fenhong 表 Identity + fadan_fenhong_lilv 表 shenfen # 罚单侧:1=客服 2=售后 3=管理员 4=店铺 5=商家 # fadan_fenhong 表:1=商家 2=客服 3=平台管理者 4=店铺 5=售后 IDENTITY_MAP = { 1: {'fenhong': 2, 'lilv': 1}, # 客服 → 分红记录身份2,利率表 shenfen=1 2: {'fenhong': 5, 'lilv': 2}, # 售后 → 分红记录身份5,利率表 shenfen=2 3: {'fenhong': 3, 'lilv': 3}, # 管理员 4: {'fenhong': 4, 'lilv': 4}, # 店铺 5: {'fenhong': 1, 'lilv': 5}, # 商家 → 分红记录身份1,利率表 shenfen=5;钱进 user_shangjia.yue } MERCHANT_LILV_SHENFEN = 5 MERCHANT_FENHONG_SHENFEN = 1 def _normalize_club_id(club_id): cid = (club_id or '').strip() return cid or 'xq' def _read_global_fenhong_rate(lilv_shenfen): try: global_rate = PenaltyBonusRate.objects.get(Identity=lilv_shenfen) rate = global_rate.Rate if rate is not None and Decimal(str(rate)) > 0: return rate except PenaltyBonusRate.DoesNotExist: pass return None def _get_fenhong_rate(fenhongzhe_id, fenhong_shenfen, lilv_shenfen, club_id='xq'): """ 获取分红利率:个人单独利率 > 0 时优先;否则回退全局 fadan_fenhong_lilv。 """ club_id = _normalize_club_id(club_id) try: individual = PenaltyBonus.objects.get( BeneficiaryID=fenhongzhe_id, Identity=fenhong_shenfen, ClubID=club_id, IsIndividualRate=True, ) ind_rate = individual.IndividualRate if ind_rate is not None and Decimal(str(ind_rate)) > 0: return ind_rate except PenaltyBonus.DoesNotExist: pass return _read_global_fenhong_rate(lilv_shenfen) def _calc_bonus_amount(fine_amount, rate): if fine_amount is None or rate is None: return Decimal('0.00') fine = Decimal(str(fine_amount)) r = Decimal(str(rate)) if fine <= 0 or r <= 0: return Decimal('0.00') return (fine * r).quantize(Decimal('0.01')) def lock_penalty_bonus(fadan): """ 申请/改价/再次申请时锁定应得分红利率与金额,写入罚单供前端展示; 缴纳时按此锁定值入账,不再现场重算。 """ if getattr(fadan, 'BonusCredited', False): return fadan zero_rate = Decimal('0.0000') zero_amount = Decimal('0.00') applicant_id = fadan.ApplicantID src_shenfen = fadan.ApplicantIdentity if not applicant_id or src_shenfen not in IDENTITY_MAP: fadan.ApplicantBonusRate = zero_rate fadan.ApplicantBonusAmount = zero_amount fadan.save(update_fields=['ApplicantBonusRate', 'ApplicantBonusAmount']) return fadan mapped = IDENTITY_MAP[src_shenfen] club_id = _normalize_club_id(fadan.ClubID) rate = _get_fenhong_rate( applicant_id, mapped['fenhong'], mapped['lilv'], club_id=club_id, ) if rate is None: fadan.ApplicantBonusRate = zero_rate fadan.ApplicantBonusAmount = zero_amount else: fadan.ApplicantBonusRate = Decimal(str(rate)) fadan.ApplicantBonusAmount = _calc_bonus_amount(fadan.FineAmount, rate) fadan.save(update_fields=['ApplicantBonusRate', 'ApplicantBonusAmount']) return fadan def get_merchant_fenhong_lilv(yonghuid, club_id='xq'): """商家申请罚单页预览利率(尚无罚单时)。""" club_id = _normalize_club_id(club_id) rate = _get_fenhong_rate( yonghuid, MERCHANT_FENHONG_SHENFEN, MERCHANT_LILV_SHENFEN, club_id=club_id, ) return float(rate) if rate is not None else 0.0 def process_fadan_fenhong(fadan_id): """ 罚款缴纳后的分红入账:优先使用申请时已锁定的利率/金额,不现场重算。 """ try: try: fadan = Penalty.objects.get(id=fadan_id, Status=2) except Penalty.DoesNotExist: logger.error(f"罚单 {fadan_id} 不存在或状态不是已缴纳") return False, "罚单不存在或状态异常" if fadan.BonusCredited: logger.info(f"罚单 {fadan_id} 分红已入账,跳过") return True, "已分红" shenqing_chufa = fadan.ApplicantID src_shenfen = fadan.ApplicantIdentity if not shenqing_chufa: logger.info(f"罚单 {fadan_id} 无申请处罚者,不进行分红") return True, "无申请处罚者,无需分红" if src_shenfen not in IDENTITY_MAP: logger.error(f"未知的申请处罚者身份: {src_shenfen},罚单 {fadan_id}") return False, f"未知身份 {src_shenfen}" mapped = IDENTITY_MAP[src_shenfen] fenhong_shenfen = mapped['fenhong'] club_id = _normalize_club_id(fadan.ClubID) locked_amount = Decimal(str(fadan.ApplicantBonusAmount or 0)) locked_rate = fadan.ApplicantBonusRate # 申请时已锁定:缴纳只按锁定值入账,不因全局利率后续变更而重算 if locked_amount > 0 or ( locked_rate is not None and Decimal(str(locked_rate)) > 0 ): fenhong_jine = locked_amount rate = locked_rate else: # 旧数据兼容:未锁定时按当前利率现场算一次并回写 fakuan_jine = fadan.FineAmount if fakuan_jine <= 0: fadan.ApplicantBonusAmount = Decimal('0.00') fadan.save(update_fields=['ApplicantBonusAmount']) return True, "罚款金额为0,无分红" rate = _get_fenhong_rate( shenqing_chufa, fenhong_shenfen, mapped['lilv'], club_id=club_id, ) if rate is None: logger.warning(f"罚单 {fadan_id} 未配置分红利率") fadan.ApplicantBonusAmount = Decimal('0.00') fadan.save(update_fields=['ApplicantBonusAmount']) return True, "未配置分红利率,无分红" fenhong_jine = _calc_bonus_amount(fakuan_jine, rate) fadan.ApplicantBonusRate = Decimal(str(rate)) fadan.ApplicantBonusAmount = fenhong_jine fadan.save(update_fields=['ApplicantBonusRate', 'ApplicantBonusAmount']) if fenhong_jine <= 0: logger.info(f"罚单 {fadan_id} 锁定分红金额为0,无需入账") fadan.BonusCredited = True fadan.save(update_fields=['BonusCredited']) return True, "分红金额为0" if src_shenfen == 5: with transaction.atomic(): fadan = Penalty.objects.select_for_update().get(id=fadan_id, Status=2) if fadan.BonusCredited: return True, "已分红" if not UserShangjia.objects.filter(user__UserUID=shenqing_chufa).exists(): logger.error(f"商家 {shenqing_chufa} 扩展表不存在,无法分红入账") return False, "商家资料不存在,无法分红" fenhong_record, _created = PenaltyBonus.objects.get_or_create( BeneficiaryID=shenqing_chufa, Identity=fenhong_shenfen, ClubID=club_id, defaults={ 'TotalAmount': Decimal('0.00'), 'WithdrawableAmount': Decimal('0.00'), 'BonusCount': 0, 'IndividualRate': rate, 'IsIndividualRate': False, }, ) fenhong_record.TotalAmount += fenhong_jine fenhong_record.BonusCount += 1 fenhong_record.save() shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=shenqing_chufa) shangjia.yue += fenhong_jine shangjia.save(update_fields=['yue']) logger.info(f"商家 {shenqing_chufa} 余额增加 {fenhong_jine} 元(锁定值)") fadan.BonusCredited = True fadan.save(update_fields=['BonusCredited']) else: with transaction.atomic(): fadan = Penalty.objects.select_for_update().get(id=fadan_id, Status=2) if fadan.BonusCredited: return True, "已分红" fenhong_record, _created = PenaltyBonus.objects.get_or_create( BeneficiaryID=shenqing_chufa, Identity=fenhong_shenfen, ClubID=club_id, defaults={ 'TotalAmount': Decimal('0.00'), 'WithdrawableAmount': Decimal('0.00'), 'BonusCount': 0, 'IndividualRate': rate, 'IsIndividualRate': False, }, ) fenhong_record.TotalAmount += fenhong_jine fenhong_record.WithdrawableAmount += fenhong_jine fenhong_record.BonusCount += 1 fenhong_record.save() fadan.BonusCredited = True fadan.save(update_fields=['BonusCredited']) logger.info( f"罚单 {fadan_id} 分红入账成功: 申请者 {shenqing_chufa}, " f"锁定金额 {fenhong_jine} 元, 锁定利率 {rate}" ) return True, "分红处理成功" except Exception as e: logger.error(f"罚单 {fadan_id} 分红处理异常: {str(e)}", exc_info=True) return False, f"分红处理异常: {str(e)}"