diff --git a/config/views.py b/config/views.py index fb28c7a..04189ea 100644 --- a/config/views.py +++ b/config/views.py @@ -3290,8 +3290,8 @@ class CheckPhoneAuthView(APIView): return False if dashou.yajin and dashou.yajin > 0: return True - from jituan.services.club_context import resolve_club_id_from_request - club_id = resolve_club_id_from_request(self.request) + from jituan.services.club_context import resolve_effective_club_id + club_id = resolve_effective_club_id(self.request, user) if Huiyuangoumai.query.filter(yonghu_id=user.UserUID, club_id=club_id).exists(): return True if (dashou.jiedanzongliang or 0) > 0 or (dashou.chengjiaozongliang or 0) > 0: diff --git a/jituan/services/club_context.py b/jituan/services/club_context.py index a38ba10..7b187bc 100644 --- a/jituan/services/club_context.py +++ b/jituan/services/club_context.py @@ -82,9 +82,29 @@ def filter_queryset_by_club(qs, request, club_field='club_id'): return qs.filter(**{club_field: club_id}) -def club_id_for_write(request): +def resolve_effective_club_id(request, user=None): + """ + 小程序/API 实际生效俱乐部(展示价、支付价、分红须一致)。 + 优先级:Header X-Club-Id > 用户归属 ClubID > 默认 xq。 + 勿用 middleware 注入的 request.club_id(无 Header 时恒为 xq)。 + """ + header = request.META.get(f'HTTP_{CLUB_HEADER.upper().replace("-", "_")}', '') + if not header and hasattr(request, 'headers'): + header = request.headers.get(CLUB_HEADER, '') + header = (header or '').strip() + if header: + return header + if user is not None: + from jituan.services.club_user import get_user_club_id + user_club = get_user_club_id(user) + if user_club: + return user_club + return CLUB_ID_DEFAULT + + +def club_id_for_write(request, user=None): """写入业务流水时使用的 club_id(集团汇总视图写入仍须指定目标 club)。""" - return resolve_club_id_from_request(request) + return resolve_effective_club_id(request, user) def orders_for_request(request): diff --git a/jituan/services/club_write.py b/jituan/services/club_write.py index 1655f96..219dd58 100644 --- a/jituan/services/club_write.py +++ b/jituan/services/club_write.py @@ -12,7 +12,7 @@ def resolve_club_id_for_write(request=None, user=None, club_id=None): if club_id: return club_id if request is not None: - return club_id_for_write(request) + return club_id_for_write(request, user) if user is not None: return get_user_club_id(user) return CLUB_ID_DEFAULT diff --git a/jituan/services/display_config.py b/jituan/services/display_config.py index 55adf56..8117de3 100644 --- a/jituan/services/display_config.py +++ b/jituan/services/display_config.py @@ -1,6 +1,11 @@ """轮播 / 公告 / 弹窗按俱乐部读取与写入。""" from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL -from jituan.services.club_context import filter_queryset_by_club, resolve_club_id_from_request, resolve_club_scope +from jituan.services.club_context import ( + filter_queryset_by_club, + resolve_club_id_from_request, + resolve_club_scope, + resolve_effective_club_id, +) from rest_framework.response import Response # 定稿 B 方案 page_key @@ -46,16 +51,23 @@ def forbid_popup_page_out_of_scope(request, page): return None +def _display_club_id(request): + user = getattr(request, 'user', None) + if user is not None and getattr(user, 'is_authenticated', False): + return resolve_effective_club_id(request, user) + return resolve_effective_club_id(request, None) + + def get_gonggao_content(request, notice_type=1): from config.models import Gonggao - club_id = resolve_club_id_from_request(request) + club_id = _display_club_id(request) obj = Gonggao.query.filter(club_id=club_id, NoticeType=notice_type).first() return obj.Content if obj and obj.Content else '' def get_lunbo_urls(request, page_key=None, image_type=1): from config.models import Lunbo - club_id = resolve_club_id_from_request(request) + club_id = _display_club_id(request) pk = normalize_page_key(page_key, image_type=image_type) qs = Lunbo.query.filter(club_id=club_id, ImageType=image_type, page_key=pk) return [row.ImageURL for row in qs.order_by('id') if row.ImageURL] @@ -67,13 +79,11 @@ def list_response_meta(request): def get_tupianpeizhi_url(request, image_type): - """按俱乐部读图片配置(无则 xq 回退,避免 xzj 未配时全空)。""" + """按俱乐部读图片配置(各俱乐部独立,不回退 xq,避免串台)。""" from config.models import Tupianpeizhi - club_id = resolve_club_id_from_request(request) + club_id = _display_club_id(request) row = Tupianpeizhi.query.filter(club_id=club_id, ImageType=image_type).first() - if not row and club_id != CLUB_ID_DEFAULT: - row = Tupianpeizhi.query.filter(club_id=CLUB_ID_DEFAULT, ImageType=image_type).first() return (row.ImageURL or '').strip() if row else '' diff --git a/products/views.py b/products/views.py index a7bfed8..556c100 100644 --- a/products/views.py +++ b/products/views.py @@ -59,7 +59,7 @@ from jituan.services.club_config import ( get_huiyuan_price, get_huiyuan_fenchong, ) -from jituan.services.club_context import resolve_club_id_from_request +from jituan.services.club_context import resolve_effective_club_id from jituan.services.club_write import resolve_club_id_for_write from jituan.services.club_user import get_payment_openid from jituan.services.club_penalty import resolve_gsfenhong_club_id @@ -269,7 +269,7 @@ class DashouHuiyuanList(APIView): # 3. 仅返回本俱乐部可售会员(club_huiyuan_price 已配置且启用) from jituan.services.member_recharge import club_huiyuan_sellable - club_id = resolve_club_id_from_request(request) + club_id = resolve_effective_club_id(request, request.user) huiyuan_queryset = Huiyuan.query.all().only( 'huiyuan_id', 'jieshao', 'jtjieshao', 'jiage', ).order_by('jiage') @@ -290,7 +290,8 @@ class DashouHuiyuanList(APIView): return Response({ 'code': 200, 'message': '获取会员列表成功', - 'data': huiyuan_list # 对应前端接收的列表 + 'data': huiyuan_list, + 'club_id': club_id, }, status=status.HTTP_200_OK) except Exception as e: @@ -1411,7 +1412,7 @@ class HuiyuanGoumai(APIView): 'message': '会员信息不存在' }, status=status.HTTP_400_BAD_REQUEST) - club_id = resolve_club_id_from_request(request) + club_id = resolve_effective_club_id(request, request.user) from jituan.services.member_recharge import club_huiyuan_sellable sellable, club_price = club_huiyuan_sellable(club_id, huiyuanid) if not sellable: @@ -1444,7 +1445,8 @@ class HuiyuanGoumai(APIView): ) logger.info( - f"创建会员充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, 会员{huiyuanid}, 金额{jine}元") + f"创建会员充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, " + f"会员{huiyuanid}, 俱乐部{club_id}, 金额{jine}元") # 7. 生成微信支付参数 try: @@ -1471,8 +1473,10 @@ class HuiyuanGoumai(APIView): return Response({ 'code': 200, 'message': '支付参数生成成功', - 'payParams': pay_params, # 对应前端payParams - 'dingdanid': dingdanid # 对应前端dingdanid + 'payParams': pay_params, + 'dingdanid': dingdanid, + 'club_id': club_id, + 'jine': jine, }, status=status.HTTP_200_OK) except Exception as e: @@ -5738,7 +5742,7 @@ class CzhqdyView(APIView): # 会员 try: huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id) - club_id = resolve_club_id_from_request(request) + club_id = resolve_effective_club_id(request, request.user) target_price = get_huiyuan_price(club_id, huiyuan_id, huiyuan) except Huiyuan.DoesNotExist: return Response({'code': 404, 'msg': '会员不存在'}, status=status.HTTP_404_NOT_FOUND) @@ -5758,7 +5762,7 @@ class CzhqdyView(APIView): # 获取所有费率(打手佣金、管事分红、组长分红、打手押金) rates = {} - club_id = resolve_club_id_from_request(request) + club_id = resolve_effective_club_id(request, request.user) for key in ['5', '6', '8', '11']: rates[key] = get_commission_rate(club_id, key) @@ -5873,7 +5877,7 @@ class DsqrgmdhView(APIView): return Response({'code': 400, 'msg': 'yajin_jine 必须为正数'}, status=status.HTTP_400_BAD_REQUEST) target_price = None - club_id = resolve_club_id_from_request(request) + club_id = resolve_effective_club_id(request, request.user) if leixing == 1: try: huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id) @@ -5894,12 +5898,12 @@ class DsqrgmdhView(APIView): return Response({'code': 404, 'msg': '罚单不存在或不属于当前用户'}, status=status.HTTP_404_NOT_FOUND) identity = IDENTITY_CONFIG.get(shenfen_id) + from jituan.services.club_config import get_commission_rate try: - rate_obj = CommissionRate.query.get(Platform=identity['rate_key']) - rate = rate_obj.Rate + rate = get_commission_rate(club_id, identity['rate_key']) if rate >= 1: return Response({'code': 400, 'msg': f"{identity['name']}费率异常"}, status=status.HTTP_400_BAD_REQUEST) - except CommissionRate.DoesNotExist: + except Exception: return Response({'code': 400, 'msg': f"{identity['name']}费率未配置"}, status=status.HTTP_400_BAD_REQUEST) logger.info(f"目标价格={target_price}, 费率={rate}") @@ -5968,7 +5972,20 @@ class DsqrgmdhView(APIView): response_data = {} if leixing == 1: - huiyuan_record, is_first_buy = self._process_member_purchase(yonghuid, huiyuan_id, huiyuan) + huiyuan_record, is_first_buy = self._process_member_purchase( + yonghuid, huiyuan_id, huiyuan, club_id=club_id, + ) + try: + from jituan.services.member_recharge import ( + resolve_member_purchase_cishu, + _apply_member_fenhong, + ) + purchase_cishu = resolve_member_purchase_cishu( + yonghuid, huiyuan_id, dingdan_id, club_id, + ) + _apply_member_fenhong(czjilu, huiyuan, purchase_cishu) + except Exception as exc: + logger.error('余额购会员分红失败 dingdan=%s: %s', dingdan_id, exc, exc_info=True) response_data['huiyuan'] = { 'huiyuanid': huiyuan_id, 'huiyuanming': huiyuan.jieshao, @@ -6034,7 +6051,7 @@ class DsqrgmdhView(APIView): logger.error(f"接口顶层异常: {str(e)}", exc_info=True) return Response({'code': 500, 'msg': f'系统错误: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) - def _process_member_purchase(self, yonghuid, huiyuan_id, huiyuan_obj): + def _process_member_purchase(self, yonghuid, huiyuan_id, huiyuan_obj, club_id=None): is_first_buy_any = False try: record = Huiyuangoumai.query.filter( @@ -6050,13 +6067,14 @@ class DsqrgmdhView(APIView): other_count = Huiyuangoumai.query.filter(yonghu_id=yonghuid).exclude(huiyuan_id=huiyuan_id).count() if other_count == 0: is_first_buy_any = True + resolved_club = club_id or get_user_club_id(User.query.filter(UserUID=yonghuid).first()) record = Huiyuangoumai.query.create( yonghu_id=yonghuid, huiyuan_id=huiyuan_id, jieshao=huiyuan_obj.jieshao, huiyuan_zhuangtai=1, daoqi_time=timezone.now() + timedelta(days=30), - club_id=get_user_club_id(User.query.filter(UserUID=yonghuid).first()), + club_id=resolved_club, ) return record, is_first_buy_any except Exception as e: