diff --git a/backend/view.py b/backend/view.py index 845968e..ef9fbd9 100644 --- a/backend/view.py +++ b/backend/view.py @@ -911,6 +911,7 @@ class KefuGetDashouDetailView(APIView): 'wechat': '', 'phone': user_main.Phone or '', 'CreateTime': user_main.UserCreateTime, + 'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '', 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, @@ -951,6 +952,7 @@ class KefuGetDashouDetailView(APIView): 'wechat': dashou.wechat or '', 'phone': user_main.Phone or '', 'CreateTime': user_main.UserCreateTime, + 'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '', 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, @@ -1554,6 +1556,7 @@ class KefuGetShangjiaDetailView(APIView): 'jinyuedingdan': shangjia.jinyuedingdan, 'jinyueliushui': str(shangjia.jinyueliushui), 'CreateTime': shangjia.CreateTime.isoformat() if shangjia.CreateTime else None, + 'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '', } return Response({'code': 0, 'data': data}) @@ -3824,6 +3827,7 @@ class GetZuzhangDetailView(APIView): 'jinyue_fenyong': str(zuzhang.jinyue_fenyong), 'fenyong_zonge': str(zuzhang.fenyong_zonge), 'CreateTime': zuzhang.CreateTime.isoformat() if zuzhang.CreateTime else None, + 'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '', } # 余额提现相关 diff --git a/jituan/services/member_recharge.py b/jituan/services/member_recharge.py index cf9e0a5..84c5f51 100644 --- a/jituan/services/member_recharge.py +++ b/jituan/services/member_recharge.py @@ -127,7 +127,12 @@ def _apply_member_fenhong(order, huiyuan, purchase_cishu): logger.info('会员分红已处理,跳过: %s', order.dingdan_id) return - dashou = UserDashou.query.get(user__UserUID=order.yonghuid) + try: + dashou = UserDashou.query.get(user__UserUID=order.yonghuid) + except UserDashou.DoesNotExist: + logger.warning('打手不存在,跳过会员分红: %s', order.yonghuid) + return + guanshi_id = dashou.yaoqingren if not guanshi_id: logger.info('打手%s无邀请管事,跳过会员分红', order.yonghuid) @@ -282,6 +287,35 @@ def _add_first_buy_jifen(yonghuid): logger.warning('首次购会员加积分失败,打手不存在: %s', yonghuid) +def repair_member_entitlement_if_paid(dingdan_id): + """ + 订单已标为已支付(zhuangtai=3)但 huiyuangoumai 缺失时补开通会员。 + 用于修复历史脏数据或履约中断。 + """ + order = Czjilu.query.filter(dingdan_id=dingdan_id, leixing=1).first() + if not order or order.zhuangtai != 3 or not order.huiyuan_id: + return False + + exists = Huiyuangoumai.query.filter( + yonghu_id=order.yonghuid, + huiyuan_id=order.huiyuan_id, + ).exists() + if exists: + return True + + try: + huiyuan = Huiyuan.query.get(huiyuan_id=order.huiyuan_id) + except Huiyuan.DoesNotExist: + logger.error('补开通会员失败,会员不存在 dingdan=%s huiyuan=%s', dingdan_id, order.huiyuan_id) + return False + + club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT + with transaction.atomic(): + _apply_huiyuan_goumai(order.yonghuid, order.huiyuan_id, huiyuan, club_id) + logger.warning('已补开通会员 dingdan=%s yonghu=%s huiyuan=%s', dingdan_id, order.yonghuid, order.huiyuan_id) + return True + + @transaction.atomic def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback'): """ @@ -315,7 +349,10 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback' f'支付金额{paid}与订单{order_amt}不一致', 'amount_mismatch', ) - huiyuan = Huiyuan.query.get(huiyuan_id=order.huiyuan_id) + huiyuan = Huiyuan.query.filter(huiyuan_id=order.huiyuan_id).first() + if not huiyuan: + raise MemberRechargeError(f'会员不存在: {order.huiyuan_id}', 'huiyuan_not_found') + club_id = getattr(order, 'club_id', None) or CLUB_ID_DEFAULT is_first_purchase_of_member = not Huiyuangoumai.query.filter( @@ -328,12 +365,12 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback' order.yonghuid, order.huiyuan_id, order.dingdan_id, club_id, ) - jine_decimal = Decimal(str(order.jine)) - try: - from jituan.services.szjilu_accounting import record_wechat_income_once - record_wechat_income_once(jine_decimal, club_id, biz_ref=f'cz:{dingdan_id}') - except Exception as exc: - logger.error('会员收支统计更新失败: %s', exc, exc_info=True) + # 先开通会员,再记账;分红失败不得回滚会员 + goumai_record, _, is_first_buy_any = _apply_huiyuan_goumai( + order.yonghuid, order.huiyuan_id, huiyuan, club_id, + ) + if is_first_buy_any: + _add_first_buy_jifen(order.yonghuid) order.zhuangtai = 3 order.save(update_fields=['zhuangtai']) @@ -341,13 +378,20 @@ def fulfill_member_recharge(dingdan_id, paid_amount_yuan=None, source='callback' huiyuan.goumai_cishu = (huiyuan.goumai_cishu or 0) + 1 huiyuan.save(update_fields=['goumai_cishu']) - goumai_record, _, is_first_buy_any = _apply_huiyuan_goumai( - order.yonghuid, order.huiyuan_id, huiyuan, club_id, - ) - if is_first_buy_any: - _add_first_buy_jifen(order.yonghuid) + jine_decimal = Decimal(str(order.jine)) + try: + from jituan.services.szjilu_accounting import record_wechat_income_once + record_wechat_income_once(jine_decimal, club_id, biz_ref=f'cz:{dingdan_id}') + except Exception as exc: + logger.error('会员收支统计更新失败: %s', exc, exc_info=True) - _apply_member_fenhong(order, huiyuan, purchase_cishu) + try: + _apply_member_fenhong(order, huiyuan, purchase_cishu) + except Exception as exc: + logger.error( + '会员分红失败但会员已开通 dingdan=%s err=%s', + dingdan_id, exc, exc_info=True, + ) logger.info( '会员充值履约完成 dingdan=%s source=%s club=%s purchase_cishu=%s first_member=%s', @@ -419,6 +463,11 @@ def confirm_member_recharge_paid(dingdan_id, yonghuid): goumai = Huiyuangoumai.query.filter( yonghu_id=yonghuid, huiyuan_id=order.huiyuan_id, ).first() + if not goumai: + repair_member_entitlement_if_paid(dingdan_id) + goumai = Huiyuangoumai.query.filter( + yonghu_id=yonghuid, huiyuan_id=order.huiyuan_id, + ).first() if not goumai: raise MemberRechargeError('订单已付但会员记录缺失,请联系客服', 'data_error') return { diff --git a/products/views.py b/products/views.py index 0f740bb..4eec1d5 100644 --- a/products/views.py +++ b/products/views.py @@ -1767,6 +1767,9 @@ class HuiyuanHuitiao(View): if order.zhuangtai != 9: logger.warning(f"会员订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}") + if order.zhuangtai == 3: + from jituan.services.member_recharge import repair_member_entitlement_if_paid + repair_member_entitlement_if_paid(out_trade_no) return self.wechat_response('SUCCESS', 'OK') wechat_amount_yuan = total_fee / 100 @@ -1968,6 +1971,9 @@ class HuiyuanLunxun(APIView): 'message': '会员订单尚未支付完成' }, status=status.HTTP_400_BAD_REQUEST) + from jituan.services.member_recharge import repair_member_entitlement_if_paid + repair_member_entitlement_if_paid(dingdanid) + # 5. 查询会员信息 try: huiyuan = Huiyuan.query.get(huiyuan_id=order.huiyuan_id) diff --git a/users/views.py b/users/views.py index 1205fcf..50505f0 100644 --- a/users/views.py +++ b/users/views.py @@ -8585,6 +8585,7 @@ class KefuGetDashouDetailView(APIView): 'wechat': '', 'phone': user_main.Phone or '', 'CreateTime': user_main.UserCreateTime, + 'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '', 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, @@ -8621,6 +8622,7 @@ class KefuGetDashouDetailView(APIView): 'wechat': dashou.wechat or '', 'phone': user_main.Phone or '', 'CreateTime': user_main.UserCreateTime, + 'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '', 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang,