From d5b8ff5b8a14b21fbfea6cb62e4dca2595efd47f Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 21 Jul 2026 01:55:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=95=86=E5=93=81/=E4=B8=93=E5=8C=BA/?= =?UTF-8?q?=E5=BA=97=E9=93=BA=E6=8C=89=E4=BF=B1=E4=B9=90=E9=83=A8=E5=88=86?= =?UTF-8?q?=E7=A6=BB=EF=BC=8C=E5=B7=A5=E5=8D=95=E7=BB=9F=E8=AE=A1=E4=B8=8E?= =?UTF-8?q?=E7=82=B9=E5=8D=95=E6=89=8B=E6=9C=BA=E5=8F=B7=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 商品与专区、店铺增加 club_id(历史默认 xq);列表按俱乐部过滤;工单列表返回待办统计;新增 hqshouji/rzwlsjh 手机号查询与认证接口。 Co-authored-by: Cursor --- backend/views/products.py | 52 ++++----- backend/views/shops.py | 11 +- gongdan/views.py | 37 +++++- jituan/services/catalog_scope.py | 8 +- .../0012_shangpin_zhuanqu_club_id.py | 42 +++++++ products/models.py | 8 ++ products/views/product_query.py | 9 +- products/views/shangpin_admin.py | 21 ++-- shop/migrations/0002_dianpu_club_id.py | 29 +++++ shop/models.py | 4 + shop/views/shop_base_views.py | 75 ++++++++----- shop/views/shop_member_views.py | 4 +- users/urls.py | 5 +- users/views/__init__.py | 2 + users/views/user_info.py | 105 +++++++++++++++++- 15 files changed, 332 insertions(+), 80 deletions(-) create mode 100644 products/migrations/0012_shangpin_zhuanqu_club_id.py create mode 100644 shop/migrations/0002_dianpu_club_id.py diff --git a/backend/views/products.py b/backend/views/products.py index 0681681..08eeb8c 100644 --- a/backend/views/products.py +++ b/backend/views/products.py @@ -141,9 +141,6 @@ class GetProductBaseDataView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) # 获取所有商品类型(按 paixu 升序) type_qs = ShangpinLeixing.query.all().order_by('paixu') @@ -166,12 +163,19 @@ class GetProductBaseDataView(APIView): 'product_count': product_count, }) - # 获取所有专区(按 paixu 升序) - zone_qs = ShangpinZhuanqu.query.all().order_by('paixu') + # 获取专区(按俱乐部隔离;集团汇总看全部) + from jituan.services.club_context import filter_club_char_field + zone_qs = filter_club_char_field( + ShangpinZhuanqu.query.all().order_by('paixu'), request, field='club_id' + ) _zone_counts = { item['zhuanqu_id']: item['cnt'] - for item in Shangpin.query.filter( - dianpu__isnull=True, dianpu_leixing__isnull=True, zhuanqu_id__isnull=False + for item in filter_club_char_field( + Shangpin.query.filter( + dianpu__isnull=True, dianpu_leixing__isnull=True, zhuanqu_id__isnull=False + ), + request, + field='club_id', ).values('zhuanqu_id').annotate(cnt=Count('id')) } zone_list = [] @@ -184,6 +188,7 @@ class GetProductBaseDataView(APIView): 'paixu': z.paixu, 'shenhezhuangtai': z.shenhezhuangtai, 'product_count': product_count, + 'club_id': getattr(z, 'club_id', '') or '', }) # 获取所有会员(只需 ID、介绍、价格) @@ -235,9 +240,6 @@ class GetProductListView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) # 获取参数 leixing_id = request.data.get('leixing_id') @@ -257,11 +259,13 @@ class GetProductListView(APIView): except: return Response({'code': 400, 'msg': '分页参数错误'}) - # 🔥 修改:构建查询集,增加公共商品筛选(店铺为空且店铺商品类型为空) + # 🔥 公共商品 + 按俱乐部隔离(集团汇总看全部) + from jituan.services.club_context import filter_club_char_field qs = Shangpin.query.filter( dianpu__isnull=True, # 所属店铺为空 dianpu_leixing__isnull=True # 店铺商品类型为空 ) + qs = filter_club_char_field(qs, request, field='club_id') if leixing_id: try: @@ -323,8 +327,10 @@ class GetProductListView(APIView): 'jiage': str(p.jiage), 'kucun': p.kucun, 'zhenshi_xiaoliang': p.zhenshi_xiaoliang, + 'duiwai_xiaoliang': p.duiwai_xiaoliang, 'tupian_url': p.tupian_url or '', 'jieshao': p.jieshao or '', + 'club_id': getattr(p, 'club_id', '') or '', }) return Response({ @@ -365,9 +371,6 @@ class GetProductListView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) # 获取所有商品类型(按 paixu 升序) type_qs = ShangpinLeixing.query.all().order_by('paixu') @@ -454,9 +457,6 @@ class GetProductListView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) # 获取参数 leixing_id = request.data.get('leixing_id') @@ -578,9 +578,6 @@ class SaveProductOrderView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) sort_type = request.data.get('type', '').strip().lower() if sort_type not in ('leixing', 'zhuanqu'): @@ -643,9 +640,6 @@ class AddProductView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) # 获取表单字段 biaoqian = request.data.get('biaoqian', '').strip() @@ -774,6 +768,8 @@ class AddProductView(APIView): # 创建商品 try: + from jituan.services.club_write import resolve_club_id_for_write + club_id = resolve_club_id_for_write(request) with transaction.atomic(): product = Shangpin.query.create( biaoqian=biaoqian, @@ -793,6 +789,7 @@ class AddProductView(APIView): ewai_dashou_fencheng=ewai_fencheng, paixu=0, # 新商品默认排序最后 shenhezhuangtai=1, # 正常状态 + club_id=club_id, ) except Exception as e: logger.error(f"创建商品失败: {str(e)}", exc_info=True) @@ -843,9 +840,6 @@ class DeleteProductView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) # 查询商品是否存在 try: @@ -908,9 +902,6 @@ class GetProductDetailView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) # 查询商品 try: @@ -992,9 +983,6 @@ class UpdateProductView(APIView): return permissions if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - scope_err = block_non_group_catalog_scope(request) - if scope_err: - return Response({'code': 400, 'msg': scope_err}) # 使用事务,将 select_for_update 放在事务内 with transaction.atomic(): diff --git a/backend/views/shops.py b/backend/views/shops.py index c69d4d0..e9366c1 100644 --- a/backend/views/shops.py +++ b/backend/views/shops.py @@ -142,8 +142,10 @@ class ShopListView(APIView): zhengqu_zonge_min = request.data.get('zhengqu_zonge_min') zhengqu_zonge_max = request.data.get('zhengqu_zonge_max') - # 4. 构建查询集(先查店铺,预加载关联凭证) + # 4. 构建查询集(先查店铺,预加载关联凭证;按俱乐部隔离) + from jituan.services.club_context import filter_club_char_field queryset = Dianpu.query.select_related('pingzheng').all() + queryset = filter_club_char_field(queryset, request, field='club_id') if dianpu_mingcheng: queryset = queryset.filter(dianpu_mingcheng__icontains=dianpu_mingcheng) @@ -189,6 +191,7 @@ class ShopListView(APIView): 'kaiqi_meiri_xiane': dianpu.kaiqi_meiri_xiane, 'pingzheng__yonghu': pingzheng.yonghu, # 用户ID 'pingzheng__zhanghao': pingzheng.zhanghao, # 登录账号 + 'club_id': getattr(dianpu, 'club_id', '') or '', }) # 获取默认配置(ID=1,不存在则创建) @@ -327,6 +330,7 @@ class ShopModifyView(APIView): mima=mima, is_active=True ) + from jituan.services.club_write import resolve_club_id_for_write dianpu = Dianpu.query.create( pingzheng=pingzheng, dianpu_mingcheng=dianpu_mingcheng, @@ -336,6 +340,7 @@ class ShopModifyView(APIView): shouyi_choucheng_feilv=request.data.get('shouyi_choucheng_feilv', 0), kaiqi_meiri_xiane=request.data.get('kaiqi_meiri_xiane', False), meiri_tixian_xiane=request.data.get('meiri_tixian_xiane', 0), + club_id=resolve_club_id_for_write(request), ) return Response({'code': 0, 'msg': '店铺创建成功', 'dianpu_id': dianpu.id}) except IntegrityError as e: @@ -458,8 +463,10 @@ class ShopListForProductView(APIView): # 店铺状态筛选(1正常,0封禁,不传则全部) zhuangtai = request.data.get('zhuangtai') - # ---------- 构建查询集(关联凭证表获取用户ID) ---------- + # ---------- 构建查询集(关联凭证表获取用户ID;按俱乐部隔离) ---------- + from jituan.services.club_context import filter_club_char_field shops = Dianpu.query.select_related('pingzheng').all() + shops = filter_club_char_field(shops, request, field='club_id') # 状态筛选:只接受 0 或 1 if zhuangtai is not None: diff --git a/gongdan/views.py b/gongdan/views.py index f5f5047..f1720c2 100644 --- a/gongdan/views.py +++ b/gongdan/views.py @@ -281,6 +281,7 @@ class GongdanAdminListView(APIView): return err page = int(request.data.get('page') or 1) page_size = min(int(request.data.get('page_size') or 20), 100) + from django.db.models import Q qs = Gongdan.query.all().order_by('-CreateTime') try: from jituan.services.club_context import filter_club_char_field @@ -290,12 +291,17 @@ class GongdanAdminListView(APIView): zhuangtai = request.data.get('zhuangtai') leixing = request.data.get('leixing') keyword = (request.data.get('keyword') or '').strip() - if zhuangtai not in (None, ''): + only_escalated = str(request.data.get('only_escalated') or '').lower() in ('1', 'true', 'yes') + need_handle = str(request.data.get('need_handle') or '').lower() in ('1', 'true', 'yes') + if need_handle: + qs = qs.filter(Q(zhuangtai=1) | Q(zhuangtai=3)) + elif zhuangtai not in (None, ''): qs = qs.filter(zhuangtai=int(zhuangtai)) + if only_escalated: + qs = qs.filter(shengji_cishu__gt=0).filter(Q(zhuangtai=1) | Q(zhuangtai=3)) if leixing not in (None, ''): qs = qs.filter(leixing=int(leixing)) if keyword: - from django.db.models import Q qs = qs.filter( Q(gongdan_id__icontains=keyword) | Q(shenqingren_id__icontains=keyword) @@ -306,7 +312,32 @@ class GongdanAdminListView(APIView): total = qs.count() start = max(page - 1, 0) * page_size rows = [serialize_gongdan(g, include_images=False) for g in qs[start:start + page_size]] - return _ok({'list': rows, 'total': total, 'page': page, 'page_size': page_size}) + + # 统计基于俱乐部范围全量(不受当前筛选影响),方便客服一眼看到待办量 + stats_qs = Gongdan.query.all() + try: + from jituan.services.club_context import filter_club_char_field + stats_qs = filter_club_char_field(stats_qs, request, field='club_id') + except Exception: + pass + stats = { + 'total': stats_qs.count(), + 'pending': stats_qs.filter(zhuangtai=1).count(), + 'done': stats_qs.filter(zhuangtai=2).count(), + 'dissatisfied': stats_qs.filter(zhuangtai=3).count(), + 'closed': stats_qs.filter(zhuangtai=4).count(), + 'escalated': stats_qs.filter(shengji_cishu__gt=0).filter( + Q(zhuangtai=1) | Q(zhuangtai=3) + ).count(), + 'need_handle': stats_qs.filter(Q(zhuangtai=1) | Q(zhuangtai=3)).count(), + } + return _ok({ + 'list': rows, + 'total': total, + 'page': page, + 'page_size': page_size, + 'stats': stats, + }) class GongdanAdminHandleView(APIView): diff --git a/jituan/services/catalog_scope.py b/jituan/services/catalog_scope.py index 13ce76b..8b24294 100644 --- a/jituan/services/catalog_scope.py +++ b/jituan/services/catalog_scope.py @@ -1,15 +1,15 @@ -"""集团全局商品目录(商品/类型/专区)须在集团汇总视图操作。""" +"""商品与专区按俱乐部隔离;类型仍可走集团汇总。""" from jituan.constants import DATA_SCOPE_ALL from jituan.services.club_context import resolve_club_scope GROUP_CATALOG_SCOPE_MSG = ( - '商品、类型与专区为集团全局数据,请在顶栏切换为「集团汇总(全部子公司)」后操作;' - '具体小程序仅可在「小程序配置」中做上架/图标等俱乐部侧配置。' + '商品类型为集团全局数据,请在顶栏切换为「集团汇总(全部子公司)」后操作;' + '商品与专区已按俱乐部隔离,切换对应俱乐部即可管理。' ) def block_non_group_catalog_scope(request): - """非集团汇总视图时返回错误文案,否则返回 None。""" + """非集团汇总视图时返回错误文案,否则返回 None。用于类型等全局目录。""" if resolve_club_scope(request) != DATA_SCOPE_ALL: return GROUP_CATALOG_SCOPE_MSG return None diff --git a/products/migrations/0012_shangpin_zhuanqu_club_id.py b/products/migrations/0012_shangpin_zhuanqu_club_id.py new file mode 100644 index 0000000..3d9fc0d --- /dev/null +++ b/products/migrations/0012_shangpin_zhuanqu_club_id.py @@ -0,0 +1,42 @@ +from django.db import migrations, models +from django.db.models import Q + + +def backfill_club_xq(apps, schema_editor): + Shangpin = apps.get_model('products', 'Shangpin') + ShangpinZhuanqu = apps.get_model('products', 'ShangpinZhuanqu') + Shangpin.objects.filter(Q(club_id__isnull=True) | Q(club_id='')).update(club_id='xq') + ShangpinZhuanqu.objects.filter(Q(club_id__isnull=True) | Q(club_id='')).update(club_id='xq') + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0011_huiyuan_is_bundle'), + ] + + operations = [ + migrations.AddField( + model_name='shangpin', + name='club_id', + field=models.CharField( + db_index=True, + default='xq', + help_text='公共商品按俱乐部隔离;历史数据默认星阙 xq', + max_length=16, + verbose_name='所属俱乐部', + ), + ), + migrations.AddField( + model_name='shangpinzhuanqu', + name='club_id', + field=models.CharField( + db_index=True, + default='xq', + help_text='公共专区按俱乐部隔离;历史数据默认星阙 xq', + max_length=16, + verbose_name='所属俱乐部', + ), + ), + migrations.RunPython(backfill_club_xq, migrations.RunPython.noop), + ] diff --git a/products/models.py b/products/models.py index a1512ac..a1111b8 100644 --- a/products/models.py +++ b/products/models.py @@ -55,6 +55,10 @@ class Shangpin(QModel): shangjia_zhuangtai = models.BooleanField(default=True, verbose_name='是否上架') fengjin_zhuangtai = models.BooleanField(default=True, verbose_name='是否封禁') shenhe_zhuangtai = models.BooleanField(default=False, verbose_name='平台审核状态',help_text='True=审核通过,False=未审核') + club_id = models.CharField( + max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部', + help_text='公共商品按俱乐部隔离;历史数据默认星阙 xq', + ) class Meta: db_table = 'shangpin' @@ -121,6 +125,10 @@ class ShangpinZhuanqu(QModel): ) shangjia_zhuangtai = models.BooleanField(default=True, verbose_name='是否上架') fengjin_zhuangtai = models.BooleanField(default=True, verbose_name='是否封禁') + club_id = models.CharField( + max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部', + help_text='公共专区按俱乐部隔离;历史数据默认星阙 xq', + ) class Meta: diff --git a/products/views/product_query.py b/products/views/product_query.py index ecd78fb..ce34002 100644 --- a/products/views/product_query.py +++ b/products/views/product_query.py @@ -69,6 +69,7 @@ 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 +from jituan.constants import CLUB_ID_DEFAULT import traceback @@ -115,8 +116,9 @@ class ShangpinHuoquView(APIView): if visible_leixing_ids: zhuanqu_queryset = ShangpinZhuanqu.query.filter( leixing_id__in=visible_leixing_ids, - shenhezhuangtai=1 # 🔴【添加】只筛选审核状态为2的专区 - ).order_by('-paixu', 'id') # 先按paixu降序,再按id升序 + shenhezhuangtai=1, + club_id=club_id or CLUB_ID_DEFAULT, + ).order_by('-paixu', 'id') else: zhuanqu_queryset = ShangpinZhuanqu.query.none() @@ -137,7 +139,8 @@ class ShangpinHuoquView(APIView): # 注意:这里使用values()只获取指定字段,提高查询效率 shangpin_queryset = Shangpin.query.filter( zhuanqu_id__in=visible_zhuanqu_ids, - shenhezhuangtai=1 # 🔴【添加】只筛选审核状态为2的商品 + shenhezhuangtai=1, + club_id=club_id or CLUB_ID_DEFAULT, ).values( 'id', 'biaoqian', 'jiage', 'leixing_id', 'zhuanqu_id', 'tupian_url', 'duiwai_xiaoliang', 'paixu' diff --git a/products/views/shangpin_admin.py b/products/views/shangpin_admin.py index 77da00a..7063cde 100644 --- a/products/views/shangpin_admin.py +++ b/products/views/shangpin_admin.py @@ -357,9 +357,11 @@ class ShangpinZhuanquFabuView(APIView): ) # === 3. 创建商品专区记录 === + club_id = resolve_club_id_for_write(request) zhuanqu_obj = ShangpinZhuanqu( mingzi=mingzi, - leixing_id=leixing_id + leixing_id=leixing_id, + club_id=club_id, ) zhuanqu_obj.save() @@ -604,6 +606,7 @@ class ShangpinFabuView(APIView): duiwai_xiaoliang_value = 0 # 4.2 创建商品对象 + club_id = resolve_club_id_for_write(request) shangpin_obj = Shangpin( biaoqian=biaoti, jiage=jiage_value, @@ -616,6 +619,7 @@ class ShangpinFabuView(APIView): yaoqiuleixing=yaoqiuleixing_int, huiyuan_id=huiyuan_id_value, yongjin=yongjin_value, + club_id=club_id, # tupian_url 先留空,上传成功后再更新 ) @@ -1354,11 +1358,13 @@ class AdShangpinHuQu(APIView): 'yongjin' # 佣金要求(如果yaoqiuleixing=2) ) - # 9. 查询商品专区数据 - shangpinzhuanqu_data = ShangpinZhuanqu.query.all().values( + # 9. 查询商品专区数据(按当前俱乐部) + admin_club_id = resolve_effective_club_id(request, getattr(request, 'user', None)) or 'xq' + shangpinzhuanqu_data = ShangpinZhuanqu.query.filter(club_id=admin_club_id).values( 'id', # 专区ID 'mingzi', # 专区名称 - 'leixing_id' # 所属类型ID + 'leixing_id', # 所属类型ID + 'club_id', ) # 10. 查询会员数据 @@ -1368,15 +1374,16 @@ class AdShangpinHuQu(APIView): 'jiage' # 会员价格 ) - # 11. 查询商品数据 - shangpin_data = Shangpin.query.all().values( + # 11. 查询商品数据(按当前俱乐部) + shangpin_data = Shangpin.query.filter(club_id=admin_club_id).values( 'id', # 商品ID 'biaoqian', # 商品标题/标签 'jiage', # 商品价格 'leixing_id', # 商品类型ID 'zhuanqu_id', # 商品专区ID 'tupian_url', # 商品图片URL - 'duiwai_xiaoliang' # 商品对外销量 + 'duiwai_xiaoliang', # 商品对外销量 + 'club_id', ) # 12. 构造返回数据 diff --git a/shop/migrations/0002_dianpu_club_id.py b/shop/migrations/0002_dianpu_club_id.py new file mode 100644 index 0000000..0e63825 --- /dev/null +++ b/shop/migrations/0002_dianpu_club_id.py @@ -0,0 +1,29 @@ +from django.db import migrations, models +from django.db.models import Q + + +def backfill_club_xq(apps, schema_editor): + Dianpu = apps.get_model('shop', 'Dianpu') + Dianpu.objects.filter(Q(club_id__isnull=True) | Q(club_id='')).update(club_id='xq') + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='dianpu', + name='club_id', + field=models.CharField( + db_index=True, + default='xq', + help_text='店铺按俱乐部隔离;历史数据默认星阙 xq', + max_length=16, + verbose_name='所属俱乐部', + ), + ), + migrations.RunPython(backfill_club_xq, migrations.RunPython.noop), + ] diff --git a/shop/models.py b/shop/models.py index f90e81f..95280e7 100644 --- a/shop/models.py +++ b/shop/models.py @@ -59,6 +59,10 @@ class Dianpu(QModel): verbose_name='作为上级累计获得分红') shangji_ketixian_fenhong = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='作为上级可提现分红余额') + club_id = models.CharField( + max_length=16, default='xq', db_index=True, verbose_name='所属俱乐部', + help_text='店铺按俱乐部隔离;历史数据默认星阙 xq', + ) class Meta: db_table = 'dianpu' diff --git a/shop/views/shop_base_views.py b/shop/views/shop_base_views.py index ad3acd8..d472f30 100644 --- a/shop/views/shop_base_views.py +++ b/shop/views/shop_base_views.py @@ -158,17 +158,20 @@ class ShopGoodsView(APIView): # 2. 判断用户是否拥有特殊身份 has_special = self._has_special_identity(user_main) - # 3. 根据绑定关系及店铺状态决定数据获取策略 + # 3. 根据绑定关系及店铺状态决定数据获取策略(店铺须属当前俱乐部) + from jituan.constants import CLUB_ID_DEFAULT + from jituan.services.club_context import resolve_effective_club_id + club_id = resolve_effective_club_id(request, user_main) or CLUB_ID_DEFAULT + if binding: dianpu = binding.dianpu - if dianpu.zhuangtai == 1: - # 店铺正常 -> 返回店铺专属商品(需通过平台审核) + shop_club = (getattr(dianpu, 'club_id', None) or CLUB_ID_DEFAULT) + if dianpu.zhuangtai == 1 and shop_club == club_id: data = self._get_shop_goods(dianpu) else: - # 店铺被封 -> 返回公共商品,有特殊身份则返回审核状态为1的,否则返回2的 + # 店铺封禁或不属于当前俱乐部 → 公共商品 data = self._get_public_goods(True) else: - # 无绑定店铺,根据身份决定返回公共商品的审核状态 data = self._get_public_goods(has_special) return Response({'code': 200, 'data': data}) @@ -241,9 +244,10 @@ class ShopGoodsView(APIView): ).order_by('-paixu', 'id') shangpin_data = list(shangpin_queryset) - # 字段映射:前端统一使用 leixing_id + # 字段映射:前端统一使用 leixing_id;已拼展示用 xiaoliang 别名 for sp in shangpin_data: sp['leixing_id'] = sp.pop('dianpu_leixing_id') + sp['xiaoliang'] = sp.get('duiwai_xiaoliang') or 0 else: shangpin_data = [] @@ -260,32 +264,50 @@ class ShopGoodsView(APIView): - 必须满足 dianpu 为空且 dianpu_leixing 为空(即纯公共数据)。 - 审核状态:若 use_audit_status_one=True,则返回 shenhezhuangtai=1 的数据; 否则返回 shenhezhuangtai=2 的数据(审核专用)。 + - 按俱乐部隔离:类型走俱乐部可见配置;专区/商品按 club_id 过滤(历史默认 xq)。 """ + from jituan.constants import CLUB_ID_DEFAULT + from jituan.services.club_context import resolve_effective_club_id + from jituan.services.club_shangpin_leixing import build_club_leixing_list_for_api + target_shenhe = 1 if use_audit_status_one else 2 + club_id = resolve_effective_club_id(self.request, getattr(self.request, 'user', None)) or CLUB_ID_DEFAULT - # 1. 公共商品类型:原有审核状态为 target_shenhe - leixing_queryset = ShangpinLeixing.query.filter( - shenhezhuangtai=target_shenhe - ).order_by('-paixu', 'id') - - leixing_data = [] - for lx in leixing_queryset: - leixing_data.append({ - 'id': lx.id, - 'jieshao': lx.jieshao, - 'tupian_url': lx.tupian_url, - 'paixu': lx.paixu - }) + # 1. 公共商品类型:按俱乐部可见类型 + if use_audit_status_one: + leixing_rows = build_club_leixing_list_for_api(club_id) + leixing_data = [ + { + 'id': row['id'], + 'jieshao': row['jieshao'], + 'tupian_url': row['tupian_url'], + 'paixu': row['paixu'], + } + for row in leixing_rows + ] + else: + leixing_queryset = ShangpinLeixing.query.filter( + shenhezhuangtai=target_shenhe + ).order_by('-paixu', 'id') + leixing_data = [] + for lx in leixing_queryset: + leixing_data.append({ + 'id': lx.id, + 'jieshao': lx.jieshao, + 'tupian_url': lx.tupian_url, + 'paixu': lx.paixu + }) visible_leixing_ids = [lx['id'] for lx in leixing_data] - # 2. 专区:dianpu 为空,且 dianpu_leixing 为空,leixing_id 在可见范围内,原有审核状态为 target_shenhe + # 2. 专区:当前俱乐部 + 公共 if visible_leixing_ids: zhuanqu_queryset = ShangpinZhuanqu.query.filter( dianpu__isnull=True, - dianpu_leixing__isnull=True, # 🔥 必须为空 + dianpu_leixing__isnull=True, leixing_id__in=visible_leixing_ids, - shenhezhuangtai=target_shenhe + shenhezhuangtai=target_shenhe, + club_id=club_id, ).order_by('-paixu', 'id') else: zhuanqu_queryset = ShangpinZhuanqu.query.none() @@ -299,20 +321,23 @@ class ShopGoodsView(APIView): 'paixu': zq.paixu }) - # 3. 商品:dianpu 为空,dianpu_leixing 为空,专区在可见范围内,原有审核状态为 target_shenhe + # 3. 商品:当前俱乐部 + 公共 zhuanqu_ids = [zq['id'] for zq in zhuanqu_data] if zhuanqu_ids: shangpin_queryset = Shangpin.query.filter( dianpu__isnull=True, - dianpu_leixing__isnull=True, # 🔥 必须为空 + dianpu_leixing__isnull=True, leixing_id__in=visible_leixing_ids, zhuanqu_id__in=zhuanqu_ids, - shenhezhuangtai=target_shenhe + shenhezhuangtai=target_shenhe, + club_id=club_id, ).values( 'id', 'biaoqian', 'jiage', 'tupian_url', 'duiwai_xiaoliang', 'paixu', 'leixing_id', 'zhuanqu_id' ).order_by('-paixu', 'id') shangpin_data = list(shangpin_queryset) + for sp in shangpin_data: + sp['xiaoliang'] = sp.get('duiwai_xiaoliang') or 0 else: shangpin_data = [] diff --git a/shop/views/shop_member_views.py b/shop/views/shop_member_views.py index d2627ca..ba143ed 100644 --- a/shop/views/shop_member_views.py +++ b/shop/views/shop_member_views.py @@ -275,14 +275,14 @@ class SubShopModifyView(APIView): mima=mima, is_active=True ) - # 创建店铺,关联上级 + # 创建店铺,关联上级(俱乐部与上级一致) dianpu = Dianpu.query.create( pingzheng=pingzheng, dianpu_mingcheng=dianpu_mingcheng, lianxi_dianhua=lianxi_dianhua or '', yaoqing_dianpu_id=parent_dianpu.id, # 关键:绑定上级 fenhong_choucheng_feilv=fenhong_feilv, - # 其他字段使用默认值 + club_id=getattr(parent_dianpu, 'club_id', None) or 'xq', ) return Response({ 'code': 200, diff --git a/users/urls.py b/users/urls.py index efb8f85..2d8905c 100644 --- a/users/urls.py +++ b/users/urls.py @@ -16,7 +16,8 @@ from .views import WechatMiniProgramLoginView, UserInfoUpdateView, DashouXinxiAP KefuWithdrawListView, KefuWithdrawDetailView, KefuWithdrawActionView, KefuPunishView, AdKfglView, AdKftjView, \ AdKfxgView,GuanshiRegisterView, WechatLoginAndGuanshiRegisterView,ZuzhangZhuceView, ZuzhangXinxiView,KefuForceCompleteView,HuoQuYaoQingRenView, DashouJianquanView, LaobanJianquanView,\ FaKuanJiFenTongJiView, DaShouFaKuanLieBiaoView, ShangjiaCufaTongjiView, ShangjiaFakuanLieBiaoView,\ - FaKuanShenSuView, FaKuanPayView,FaKuanHuitiaoView, FaKuanPayPollView, FaKuanPayFailView,KaohePayView,KaohePayCallbackView, KaohePayPollView,KaohePayFailView, TixianAssetView, GuanshiContactView,TixianQueRenAutoView, TixianShenqingV3View, TixianCallbackV3View + FaKuanShenSuView, FaKuanPayView,FaKuanHuitiaoView, FaKuanPayPollView, FaKuanPayFailView,KaohePayView,KaohePayCallbackView, KaohePayPollView,KaohePayFailView, TixianAssetView, GuanshiContactView,TixianQueRenAutoView, TixianShenqingV3View, TixianCallbackV3View, \ + GetUserPhoneView, BindUserPhoneView from .tixian_shenhe_views import TixianZddkshApplyView from .paihang_views import PhbHqsjView from .xzj_paihang_views import XzjPhbHqsjView @@ -26,6 +27,8 @@ from rank.reward_views import PhbJlXxView, PhbJlWdView, PhbJlLqView, PhbJlPhbVie urlpatterns = [ path('wechatlogin', WechatMiniProgramLoginView.as_view(), name='用户一键微信登录'), path('xiugai', UserInfoUpdateView.as_view(), name='用户信息修改'), + path('hqshouji', GetUserPhoneView.as_view(), name='获取用户手机号认证状态'), + path('rzwlsjh', BindUserPhoneView.as_view(), name='一键认证手机号'), # 打手信息接口 path('dashouxinxi', DashouXinxiAPIView.as_view(), name='打手信息查询'), # 更改在线状态接口 diff --git a/users/views/__init__.py b/users/views/__init__.py index 356e275..e776e46 100644 --- a/users/views/__init__.py +++ b/users/views/__init__.py @@ -27,6 +27,8 @@ from .user_info import ( ShangJiaXinXiView, ShangjiaNichengGengxinView, UserInfoUpdateView, + GetUserPhoneView, + BindUserPhoneView, YaoqingmaView, ZaixianZhuangtaiAPIView, huoquKehuduanIP, diff --git a/users/views/user_info.py b/users/views/user_info.py index f66f736..734bd33 100644 --- a/users/views/user_info.py +++ b/users/views/user_info.py @@ -1175,4 +1175,107 @@ class LaobanJianquanView(APIView): 'code': 0, 'allow': 0, 'msg': '您暂无订单记录,消息功能暂不可用' - }) + }) + + +def _mask_phone(phone): + p = (phone or '').strip() + if len(p) < 7: + return p + return p[:3] + '****' + p[-4:] + + +def _phone_auth_completed(user): + if bool(getattr(user, 'PhoneVerified', False)): + return True + phone = (getattr(user, 'Phone', None) or '').strip() + return len(phone) >= 11 + + +class GetUserPhoneView(APIView): + """ + 点单提交页:查询当前用户手机号认证状态。 + POST /yonghu/hqshouji + 返回:phone / phone_masked / phone_verified / need_auth + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + user = request.user + uid = getattr(user, 'UserUID', None) + if uid: + try: + user = User.query.get(UserUID=uid) + except User.DoesNotExist: + pass + verified = _phone_auth_completed(user) + phone = (getattr(user, 'Phone', None) or '').strip() + return Response({ + 'code': 0, + 'msg': 'ok', + 'data': { + 'phone': phone if verified else '', + 'phone_masked': _mask_phone(phone) if verified else '', + 'phone_verified': verified, + 'need_auth': not verified, + }, + }) + + +class BindUserPhoneView(APIView): + """ + 点单提交页:一键获取手机号并完成认证(无需头像)。 + POST /yonghu/rzwlsjh + 参数:shoujihao_code(微信 getPhoneNumber 返回的 code) + """ + permission_classes = [IsAuthenticated] + + def post(self, request): + code = ( + request.data.get('shoujihao_code') + or request.data.get('code') + or '' + ) + if isinstance(code, str): + code = code.strip() + else: + code = str(code or '').strip() + if not code: + return Response({ + 'code': 6, + 'msg': '未收到手机号授权,请重新点击获取手机号', + 'data': None, + }) + + user = request.user + uid = getattr(user, 'UserUID', None) + if uid: + try: + user = User.query.get(UserUID=uid) + except User.DoesNotExist: + pass + + # 复用资料页的微信换号逻辑 + binder = UserInfoUpdateView() + phone = binder._get_phone_safe(code, request) + if not phone: + return Response({ + 'code': 6, + 'msg': '手机号获取失败,请重新授权', + 'data': None, + }) + + user.Phone = phone + user.PhoneVerified = True + user.save(update_fields=['Phone', 'PhoneVerified']) + + return Response({ + 'code': 0, + 'msg': '认证成功', + 'data': { + 'phone': phone, + 'phone_masked': _mask_phone(phone), + 'phone_verified': True, + 'need_auth': False, + }, + })