From 6515568ec07d5f4f8507be17d0897489d242fc8d Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 21 Jul 2026 03:20:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=AA=E7=9C=8B=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E6=8C=89=E4=BF=B1=E4=B9=90=E9=83=A8=E9=9A=94=E7=A6=BB=EF=BC=8C?= =?UTF-8?q?=E5=95=86=E5=93=81=E7=AE=A1=E7=90=86=E6=94=AF=E6=8C=81=E4=BF=B1?= =?UTF-8?q?=E4=B9=90=E9=83=A8=E8=A7=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 未绑店出货开关按 club_id 配置;后台商品列表可在具体小程序下管理并切换审核展示。 Co-authored-by: Cursor --- backend/urls.py | 3 +- backend/view.py | 2 + backend/views/products.py | 89 +++++++++++++++++-- backend/views/shops.py | 61 ++++++++++--- shop/migrations/0004_shenhe_shezhi_club_id.py | 44 +++++++++ shop/models.py | 13 ++- shop/services/shenhe_config.py | 43 +++++++++ shop/views/shop_base_views.py | 14 ++- shop/views/shop_product_views.py | 22 +++-- 9 files changed, 249 insertions(+), 42 deletions(-) create mode 100644 shop/migrations/0004_shenhe_shezhi_club_id.py create mode 100644 shop/services/shenhe_config.py diff --git a/backend/urls.py b/backend/urls.py index 466d624..385f1b0 100644 --- a/backend/urls.py +++ b/backend/urls.py @@ -5,7 +5,7 @@ from .view import (GetRolePermissionView, ModifyRolePermissionView, AddRoleView, GetAdminRolesView, GetAdminUserListView, AddAdminUserView, ModifyAdminUserView, KefuGetDashouListView, KefuGetDashouDetailView, KefuUpdateDashouView, KefuGetShangjiaListView, KefuGetShangjiaDetailView, KefuUpdateShangjiaView, GetProductBaseDataView, GetProductListView, SaveProductOrderView, - AddProductView, DeleteProductView, GetProductDetailView, UpdateProductView, UpdateMemberView, GetMemberListView, + AddProductView, DeleteProductView, GetProductDetailView, UpdateProductView, UpdateZhiKanShenheView, UpdateMemberView, GetMemberListView, AddMemberView, GetGuanliListView, GetGuanliDetailView, PromoteGuanshiToZuzhangView, ChangeInviterView, UpdateGuanliView, GetWithdrawSettingsView, UpdateWithdrawSettingsView, GetZuzhangListView, GetZuzhangDetailView, UpdateZuzhangView, GetOperationLogListView, GetProductTypeZoneView, ModifyProductTypeZoneView, GetRateView, ModifyRateView, PopupNoticeListAPIView, PopupNoticeModifyAPIView,ShopListView, @@ -44,6 +44,7 @@ urlpatterns = [ path('htscsp', DeleteProductView.as_view(), name='后台删除商品'), path('htsphqxq', GetProductDetailView.as_view(), name='后台获取商品详情'), path('bcspxg', UpdateProductView.as_view(), name='后台更新商品数据'), + path('htxgzhikan', UpdateZhiKanShenheView.as_view(), name='后台更新只看审核开关'), path('hthqhylb', GetMemberListView.as_view(), name='后台获取会员列表'), path('htxghyxx', UpdateMemberView.as_view(), name='后台更新会员信息'), diff --git a/backend/view.py b/backend/view.py index 12fe0cc..d0c0803 100644 --- a/backend/view.py +++ b/backend/view.py @@ -41,6 +41,7 @@ from .views.kefu import ( from .views.products import ( GetProductBaseDataView, GetProductListView, SaveProductOrderView, AddProductView, DeleteProductView, GetProductDetailView, UpdateProductView, + UpdateZhiKanShenheView, ) from .views.members import ( GetMemberListView, UpdateMemberView, AddMemberView, @@ -103,6 +104,7 @@ __all__ = [ # products 'GetProductBaseDataView', 'GetProductListView', 'SaveProductOrderView', 'AddProductView', 'DeleteProductView', 'GetProductDetailView', 'UpdateProductView', + 'UpdateZhiKanShenheView', # members 'GetMemberListView', 'UpdateMemberView', 'AddMemberView', 'ClubMemberCatalogView', 'ClubMemberEnableView', 'ClubMemberDisableView', diff --git a/backend/views/products.py b/backend/views/products.py index 08eeb8c..5a7ece1 100644 --- a/backend/views/products.py +++ b/backend/views/products.py @@ -142,14 +142,24 @@ class GetProductBaseDataView(APIView): if '2200a' not in permissions: return Response({'code': 403, 'msg': '无权限管理商品'}) - # 获取所有商品类型(按 paixu 升序) + # 获取所有商品类型(按 paixu 升序);商品数按当前俱乐部统计 + from jituan.services.club_context import filter_club_char_field, resolve_club_scope + from jituan.constants import DATA_SCOPE_ALL + from jituan.services.club_write import resolve_club_id_for_write + from shop.services.shenhe_config import get_or_create_shenhe_config, is_zhi_kan_shenhe_enabled + type_qs = ShangpinLeixing.query.all().order_by('paixu') - # 批量聚合商品数量,避免循环内 N+1 count + # 批量聚合商品数量,避免循环内 N+1 count(按俱乐部) + _public_sp = filter_club_char_field( + Shangpin.query.filter( + dianpu__isnull=True, dianpu_leixing__isnull=True, leixing_id__isnull=False + ), + request, + field='club_id', + ) _type_counts = { item['leixing_id']: item['cnt'] - for item in Shangpin.query.filter( - dianpu__isnull=True, dianpu_leixing__isnull=True, leixing_id__isnull=False - ).values('leixing_id').annotate(cnt=Count('id')) + for item in _public_sp.values('leixing_id').annotate(cnt=Count('id')) } type_list = [] for t in type_qs: @@ -164,7 +174,6 @@ class GetProductBaseDataView(APIView): }) # 获取专区(按俱乐部隔离;集团汇总看全部) - 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' ) @@ -194,12 +203,24 @@ class GetProductBaseDataView(APIView): # 获取所有会员(只需 ID、介绍、价格) member_list = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage')) + # 本俱乐部「只看审核」开关(集团汇总不下发可写状态) + club_scoped = resolve_club_scope(request) != DATA_SCOPE_ALL + zhi_kan_shenhe = True + club_id = '' + if club_scoped: + club_id = resolve_club_id_for_write(request) or '' + get_or_create_shenhe_config(club_id) + zhi_kan_shenhe = is_zhi_kan_shenhe_enabled(club_id) + return Response({ 'code': 0, 'data': { 'type_list': type_list, 'zone_list': zone_list, 'member_list': member_list, + 'zhi_kan_shenhe': zhi_kan_shenhe, + 'club_scoped': club_scoped, + 'club_id': club_id, } }) @@ -701,6 +722,14 @@ class AddProductView(APIView): zhuanqu_obj = ShangpinZhuanqu.query.get(id=zhuanqu_id) if zhuanqu_obj.leixing_id != leixing_id: return Response({'code': 400, 'msg': '专区不属于该商品类型'}) + from jituan.constants import DATA_SCOPE_ALL + from jituan.services.club_context import resolve_club_scope + from jituan.services.club_write import resolve_club_id_for_write + if resolve_club_scope(request) != DATA_SCOPE_ALL: + write_club = resolve_club_id_for_write(request) + zone_club = getattr(zhuanqu_obj, 'club_id', None) or 'xq' + if zone_club != write_club: + return Response({'code': 400, 'msg': '专区不属于当前俱乐部,请先切换俱乐部或选择本俱乐部专区'}) except ShangpinZhuanqu.DoesNotExist: return Response({'code': 400, 'msg': '商品专区不存在'}) @@ -1134,6 +1163,52 @@ class UpdateProductView(APIView): return Response({'code': 0, 'msg': '商品修改成功', 'data': {'id': product.id}}) - +class UpdateZhiKanShenheView(APIView): + """ + 商品管理页:按俱乐部更新「只看审核商品」开关。 + 权限:2200a + POST /houtai/htxgzhikan + """ + permission_classes = [] + parser_classes = [JSONParser] + + def post(self, request): + username = request.data.get('username', '').strip() + if not username: + return Response({'code': 401, 'msg': '缺少username'}) + + kefu, permissions = verify_kefu_permission(request, username) + if kefu is None: + return permissions + if '2200a' not in permissions: + return Response({'code': 403, 'msg': '无权限管理商品'}) + + val = request.data.get('zhi_kan_shenhe') + if val is None: + return Response({'code': 400, 'msg': '缺少参数 zhi_kan_shenhe'}) + + from jituan.constants import DATA_SCOPE_ALL + from jituan.services.club_context import resolve_club_scope + from jituan.services.club_write import resolve_club_id_for_write + from shop.services.shenhe_config import get_or_create_shenhe_config + + if resolve_club_scope(request) == DATA_SCOPE_ALL: + return Response({'code': 400, 'msg': '请先切换到具体俱乐部再修改只看审核开关'}) + + try: + with transaction.atomic(): + club_id = resolve_club_id_for_write(request) + config = get_or_create_shenhe_config(club_id) + config = DianpuShangpinShenheShezhi.objects.select_for_update().get(pk=config.pk) + config.zhi_kan_shenhe = bool(val) + config.save(update_fields=['zhi_kan_shenhe']) + return Response({ + 'code': 0, + 'msg': '只看审核开关已更新', + 'data': {'club_id': club_id, 'zhi_kan_shenhe': bool(val)}, + }) + except Exception: + logging.getLogger(__name__).exception('更新只看审核开关失败') + return Response({'code': 500, 'msg': '服务器错误'}) diff --git a/backend/views/shops.py b/backend/views/shops.py index 264d768..0759179 100644 --- a/backend/views/shops.py +++ b/backend/views/shops.py @@ -421,17 +421,38 @@ class ShopPublicTypeAndAuditView(APIView): logger.exception("查询公共商品类型失败") return Response({'code': 500, 'msg': '服务器错误,请稍后重试'}) - # ---------- 获取审核模式配置 ---------- - audit_config = DianpuShangpinShenheShezhi.query.filter(id=1).first() - kaiqi_shenhe = audit_config.kaiqi_shenhe if audit_config else False - zhi_kan_shenhe = getattr(audit_config, 'zhi_kan_shenhe', True) if audit_config else True + # ---------- 获取本俱乐部审核模式配置 ---------- + from jituan.services.club_write import resolve_club_id_for_write + from shop.services.shenhe_config import get_or_create_shenhe_config + from jituan.constants import DATA_SCOPE_ALL + from jituan.services.club_context import resolve_club_scope + + if resolve_club_scope(request) == DATA_SCOPE_ALL: + # 集团汇总:不自动创建,返回默认值提示前端切到具体俱乐部 + return Response({ + 'code': 0, + 'data': { + 'public_types': type_list, + 'kaiqi_shenhe': False, + 'zhi_kan_shenhe': True, + 'club_scoped': False, + 'msg': '请切换到具体俱乐部查看/修改审核展示开关', + } + }) + + club_id = resolve_club_id_for_write(request) + audit_config = get_or_create_shenhe_config(club_id) + kaiqi_shenhe = audit_config.kaiqi_shenhe + zhi_kan_shenhe = bool(getattr(audit_config, 'zhi_kan_shenhe', True)) return Response({ 'code': 0, 'data': { 'public_types': type_list, 'kaiqi_shenhe': kaiqi_shenhe, - 'zhi_kan_shenhe': bool(zhi_kan_shenhe), + 'zhi_kan_shenhe': zhi_kan_shenhe, + 'club_id': club_id, + 'club_scoped': True, } }) @@ -640,18 +661,28 @@ class ShopProductModifyView(APIView): if kaiqi is None: return Response({'code': 400, 'msg': '缺少参数 kaiqi_shenhe'}) + from jituan.constants import DATA_SCOPE_ALL + from jituan.services.club_context import resolve_club_scope + from jituan.services.club_write import resolve_club_id_for_write + from shop.services.shenhe_config import get_or_create_shenhe_config + + if resolve_club_scope(request) == DATA_SCOPE_ALL: + return Response({'code': 400, 'msg': '请先切换到具体俱乐部再修改审核模式'}) + try: - config, _ = DianpuShangpinShenheShezhi.objects.select_for_update().get_or_create(id=1) + club_id = resolve_club_id_for_write(request) + config = get_or_create_shenhe_config(club_id) + config = DianpuShangpinShenheShezhi.objects.select_for_update().get(pk=config.pk) config.kaiqi_shenhe = bool(kaiqi) config.save(update_fields=['kaiqi_shenhe']) - return Response({'code': 0, 'msg': '审核模式已更新'}) + return Response({'code': 0, 'msg': '审核模式已更新', 'data': {'club_id': club_id}}) except Exception as e: logger.exception("更新审核模式失败") return Response({'code': 500, 'msg': '服务器错误'}) @transaction.atomic def _update_zhi_kan_shenhe(self, request, user_perms): - """点单端:未绑店用户是否按身份只看审核专用商品。""" + """点单端:未绑店用户是否按身份只看审核专用商品(按俱乐部)。""" if not has_perm_code(user_perms, '1199ab'): return Response({'code': 403, 'msg': '无权限修改只看审核开关'}) @@ -659,11 +690,21 @@ class ShopProductModifyView(APIView): if val is None: return Response({'code': 400, 'msg': '缺少参数 zhi_kan_shenhe'}) + from jituan.constants import DATA_SCOPE_ALL + from jituan.services.club_context import resolve_club_scope + from jituan.services.club_write import resolve_club_id_for_write + from shop.services.shenhe_config import get_or_create_shenhe_config + + if resolve_club_scope(request) == DATA_SCOPE_ALL: + return Response({'code': 400, 'msg': '请先切换到具体俱乐部再修改只看审核开关'}) + try: - config, _ = DianpuShangpinShenheShezhi.objects.select_for_update().get_or_create(id=1) + club_id = resolve_club_id_for_write(request) + config = get_or_create_shenhe_config(club_id) + config = DianpuShangpinShenheShezhi.objects.select_for_update().get(pk=config.pk) config.zhi_kan_shenhe = bool(val) config.save(update_fields=['zhi_kan_shenhe']) - return Response({'code': 0, 'msg': '只看审核开关已更新'}) + return Response({'code': 0, 'msg': '只看审核开关已更新', 'data': {'club_id': club_id}}) except Exception: logger.exception("更新只看审核开关失败") return Response({'code': 500, 'msg': '服务器错误'}) diff --git a/shop/migrations/0004_shenhe_shezhi_club_id.py b/shop/migrations/0004_shenhe_shezhi_club_id.py new file mode 100644 index 0000000..a65f6f1 --- /dev/null +++ b/shop/migrations/0004_shenhe_shezhi_club_id.py @@ -0,0 +1,44 @@ +from django.db import migrations, models + + +def forwards_fill_club_id(apps, schema_editor): + Model = apps.get_model('shop', 'DianpuShangpinShenheShezhi') + # 历史全局单例 → 归到星阙 xq;若已有多行则只保留一条 + rows = list(Model.objects.all().order_by('id')) + if not rows: + return + keep = rows[0] + keep.club_id = keep.club_id or 'xq' + keep.save(update_fields=['club_id']) + for extra in rows[1:]: + extra.delete() + + +def noop_reverse(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0003_zhi_kan_shenhe'), + ] + + operations = [ + migrations.AddField( + model_name='dianpushangpinshenheshezhi', + name='club_id', + field=models.CharField( + db_index=True, default='xq', max_length=16, verbose_name='所属俱乐部', + ), + ), + migrations.RunPython(forwards_fill_club_id, noop_reverse), + migrations.AlterField( + model_name='dianpushangpinshenheshezhi', + name='club_id', + field=models.CharField( + db_index=True, default='xq', max_length=16, unique=True, + verbose_name='所属俱乐部', + ), + ), + ] diff --git a/shop/models.py b/shop/models.py index 42714ea..087f813 100644 --- a/shop/models.py +++ b/shop/models.py @@ -211,11 +211,14 @@ class ShangpinLeixingDianpu(QModel): -# shangdian/models.py 或独立应用 models.py class DianpuShangpinShenheShezhi(QModel): - """店铺商品审核模式全局配置(仅一条记录)""" + """点单展示 / 店铺商品审核:按俱乐部各一条。""" + club_id = models.CharField( + max_length=16, unique=True, default='xq', db_index=True, + verbose_name='所属俱乐部', + ) kaiqi_shenhe = models.BooleanField(default=False, verbose_name='是否开启商品审核模式') - # True=未注册打手等特殊身份时只看审核专用商品;False=未绑店一律看正常公共商品 + # True=未绑店且无打手等身份只看审核专用;False=未绑店一律看正常公共商品 zhi_kan_shenhe = models.BooleanField(default=True, verbose_name='是否只看审核商品') CreateTime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间') @@ -224,7 +227,3 @@ class DianpuShangpinShenheShezhi(QModel): db_table = 'dianpu_shangpin_shenhe_shezhi' verbose_name = '商品审核模式配置' verbose_name_plural = verbose_name - - def save(self, *args, **kwargs): - self.pk = 1 # 强制使用 ID=1 保证唯一 - super().save(*args, **kwargs) diff --git a/shop/services/shenhe_config.py b/shop/services/shenhe_config.py new file mode 100644 index 0000000..44a7cb6 --- /dev/null +++ b/shop/services/shenhe_config.py @@ -0,0 +1,43 @@ +"""点单端商品展示 / 店铺审核模式:按俱乐部隔离。""" +from jituan.constants import CLUB_ID_DEFAULT +from shop.models import DianpuShangpinShenheShezhi + + +def _norm_club_id(club_id): + return (club_id or CLUB_ID_DEFAULT).strip() or CLUB_ID_DEFAULT + + +def get_or_create_shenhe_config(club_id): + """获取或创建某俱乐部的审核展示配置。""" + cid = _norm_club_id(club_id) + config, _ = DianpuShangpinShenheShezhi.objects.get_or_create( + club_id=cid, + defaults={ + 'kaiqi_shenhe': False, + 'zhi_kan_shenhe': True, + }, + ) + return config + + +def is_zhi_kan_shenhe_enabled(club_id): + """ + 未绑店用户是否只看审核专用商品。 + 无配置时默认 True,保持历史行为。 + """ + config = DianpuShangpinShenheShezhi.query.filter( + club_id=_norm_club_id(club_id) + ).first() + if not config: + return True + return bool(getattr(config, 'zhi_kan_shenhe', True)) + + +def is_kaiqi_shenhe_enabled(club_id): + """店铺发布商品是否需平台审核。无配置默认 False。""" + config = DianpuShangpinShenheShezhi.query.filter( + club_id=_norm_club_id(club_id) + ).first() + if not config: + return False + return bool(config.kaiqi_shenhe) diff --git a/shop/views/shop_base_views.py b/shop/views/shop_base_views.py index a693db3..602ad6f 100644 --- a/shop/views/shop_base_views.py +++ b/shop/views/shop_base_views.py @@ -139,22 +139,20 @@ class ShopGoodsView(APIView): # 店铺封禁或不属于当前俱乐部 → 公共正常商品 data = self._get_public_goods(True) else: - # 未绑店:受「是否只看审核」开关控制 + # 未绑店:受本俱乐部「是否只看审核」开关控制 # 开启 → 有特殊身份看正常(=1),否则看审核专用(=2) # 关闭 → 一律看正常公共商品(与身份无关) use_normal = True - if self._is_zhi_kan_shenhe_enabled(): + if self._is_zhi_kan_shenhe_enabled(club_id): use_normal = has_special data = self._get_public_goods(use_normal) return Response({'code': 200, 'data': data}) - def _is_zhi_kan_shenhe_enabled(self): - """全局开关:未绑店用户是否按身份只看审核专用商品。默认开启以保持历史行为。""" - config = DianpuShangpinShenheShezhi.query.filter(id=1).first() - if not config: - return True - return bool(getattr(config, 'zhi_kan_shenhe', True)) + def _is_zhi_kan_shenhe_enabled(self, club_id): + """本俱乐部开关:未绑店用户是否按身份只看审核专用商品。默认开启。""" + from shop.services.shenhe_config import is_zhi_kan_shenhe_enabled + return is_zhi_kan_shenhe_enabled(club_id) def _has_special_identity(self, user_main): """判断用户是否拥有打手/商家/管事/组长任一身份""" diff --git a/shop/views/shop_product_views.py b/shop/views/shop_product_views.py index 96d84ab..828e0af 100644 --- a/shop/views/shop_product_views.py +++ b/shop/views/shop_product_views.py @@ -237,9 +237,10 @@ class ShopProductModifyView(APIView): # 提取相对路径 tupian_url = full_url.replace(settings.COS_DOMAIN.rstrip('/') + '/', '', 1) - # 审核状态 - audit_config = DianpuShangpinShenheShezhi.query.filter(id=1).first() - shenhe_status = not (audit_config and audit_config.kaiqi_shenhe) + # 审核状态(按店铺所属俱乐部) + from shop.services.shenhe_config import is_kaiqi_shenhe_enabled + shop_club = getattr(dianpu, 'club_id', None) or 'xq' + shenhe_status = not is_kaiqi_shenhe_enabled(shop_club) ShangpinLeixingDianpu.query.create( dianpu_id=dianpu.id, @@ -320,8 +321,9 @@ class ShopProductModifyView(APIView): # 审核重置 if content_changed: - audit_config = DianpuShangpinShenheShezhi.query.filter(id=1).first() - if audit_config and audit_config.kaiqi_shenhe: + from shop.services.shenhe_config import is_kaiqi_shenhe_enabled + shop_club = getattr(dianpu, 'club_id', None) or 'xq' + if is_kaiqi_shenhe_enabled(shop_club): obj.shenhe_zhuangtai = False update_fields.append('shenhe_zhuangtai') @@ -872,8 +874,9 @@ class ShopProductModifyView1(APIView): content_changed = True if content_changed: - audit_config = DianpuShangpinShenheShezhi.query.filter(id=1).first() - if audit_config and audit_config.kaiqi_shenhe: + from shop.services.shenhe_config import is_kaiqi_shenhe_enabled + shop_club = getattr(dianpu, 'club_id', None) or 'xq' + if is_kaiqi_shenhe_enabled(shop_club): product.shenhe_zhuangtai = False update_fields.append('shenhe_zhuangtai') @@ -962,8 +965,9 @@ class ShopProductModifyView1(APIView): return Response({'code': 400, 'msg': '分成金额不能超过商品价格'})''' shenhe_status = True - audit_config = DianpuShangpinShenheShezhi.query.filter(id=1).first() - if audit_config and audit_config.kaiqi_shenhe: + from shop.services.shenhe_config import is_kaiqi_shenhe_enabled + shop_club = getattr(dianpu, 'club_id', None) or 'xq' + if is_kaiqi_shenhe_enabled(shop_club): shenhe_status = False product = Shangpin.query.create(