diff --git a/jituan/management/commands/seed_kefu_menu.py b/jituan/management/commands/seed_kefu_menu.py index 382c107..cb0bf74 100644 --- a/jituan/management/commands/seed_kefu_menu.py +++ b/jituan/management/commands/seed_kefu_menu.py @@ -98,6 +98,10 @@ MENU_ROWS = [ 'perm_codes': ['8080a']}, {'page_id': 'miniapp.withdraw-settings', 'name': '提现与收款设置', 'path': '/withdraw/settings', 'parent_id': 'miniapp', 'sort_order': 114, 'perm_codes': ['5500a', '5500b', '5500c']}, + {'page_id': 'miniapp.leixing', 'name': '抢单商品类型', 'path': '/miniapp/leixing', 'parent_id': 'miniapp', 'sort_order': 115, + 'perm_codes': ['8080a']}, + {'page_id': 'miniapp.kaohe-tags', 'name': '抢单考核标签', 'path': '/miniapp/kaohe-tags', 'parent_id': 'miniapp', 'sort_order': 116, + 'perm_codes': ['8080a']}, # 店铺 {'page_id': 'shop', 'name': '店铺管理', 'path': '', 'parent_id': '', 'sort_order': 120}, {'page_id': 'shop.list', 'name': '店铺列表', 'path': '/shop/list', 'parent_id': 'shop', 'sort_order': 121, diff --git a/jituan/migrations/0007_club_catalog_config.py b/jituan/migrations/0007_club_catalog_config.py new file mode 100644 index 0000000..7b8196d --- /dev/null +++ b/jituan/migrations/0007_club_catalog_config.py @@ -0,0 +1,43 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('jituan', '0006_club_huiyuan_trial_fields'), + ] + + operations = [ + migrations.CreateModel( + name='ClubShangpinLeixingConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('club_id', models.CharField(db_index=True, max_length=16)), + ('leixing_id', models.IntegerField(db_index=True, verbose_name='全局商品类型ID')), + ('is_enabled', models.BooleanField(default=True, verbose_name='是否上架')), + ('tupian_url', models.CharField(blank=True, default='', max_length=500, verbose_name='俱乐部专属图标')), + ('paixu', models.IntegerField(default=0, verbose_name='排序权重')), + ('CreateTime', models.DateTimeField(auto_now_add=True)), + ('UpdateTime', models.DateTimeField(auto_now=True)), + ], + options={ + 'db_table': 'club_shangpin_leixing_config', + 'unique_together': {('club_id', 'leixing_id')}, + }, + ), + migrations.CreateModel( + name='ClubKaoheChenghaoConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('club_id', models.CharField(db_index=True, max_length=16)), + ('chenghao_id', models.IntegerField(db_index=True, verbose_name='全局称号ID')), + ('is_enabled', models.BooleanField(default=True, verbose_name='是否在抢单池筛选区展示')), + ('CreateTime', models.DateTimeField(auto_now_add=True)), + ('UpdateTime', models.DateTimeField(auto_now=True)), + ], + options={ + 'db_table': 'club_kaohe_chenghao_config', + 'unique_together': {('club_id', 'chenghao_id')}, + }, + ), + ] diff --git a/jituan/models.py b/jituan/models.py index fae4bbf..67f998d 100644 --- a/jituan/models.py +++ b/jituan/models.py @@ -121,6 +121,34 @@ class ClubHuiyuanPrice(QModel): unique_together = [['club_id', 'huiyuan_id']] +class ClubShangpinLeixingConfig(QModel): + """各俱乐部小程序商品类型上架与图标(全局 ShangpinLeixing 由集团定义)。""" + club_id = models.CharField(max_length=16, db_index=True) + leixing_id = models.IntegerField(db_index=True, verbose_name='全局商品类型ID') + is_enabled = models.BooleanField(default=True, verbose_name='是否上架') + tupian_url = models.CharField(max_length=500, blank=True, default='', verbose_name='俱乐部专属图标') + paixu = models.IntegerField(default=0, verbose_name='排序权重') + CreateTime = models.DateTimeField(auto_now_add=True) + UpdateTime = models.DateTimeField(auto_now=True) + + class Meta: + db_table = 'club_shangpin_leixing_config' + unique_together = [['club_id', 'leixing_id']] + + +class ClubKaoheChenghaoConfig(QModel): + """各俱乐部抢单池标签筛选区上架配置(全局 Chenghao 由集团定义)。""" + club_id = models.CharField(max_length=16, db_index=True) + chenghao_id = models.IntegerField(db_index=True, verbose_name='全局称号ID') + is_enabled = models.BooleanField(default=True, verbose_name='是否在抢单池筛选区展示') + CreateTime = models.DateTimeField(auto_now_add=True) + UpdateTime = models.DateTimeField(auto_now=True) + + class Meta: + db_table = 'club_kaohe_chenghao_config' + unique_together = [['club_id', 'chenghao_id']] + + class ClubLilubiao(QModel): """各俱乐部分红/手续费配置(对应 lilubiao.fadanpingtai)。""" club_id = models.CharField(max_length=16, db_index=True) diff --git a/jituan/services/club_bootstrap.py b/jituan/services/club_bootstrap.py index 5f4974d..681ac41 100644 --- a/jituan/services/club_bootstrap.py +++ b/jituan/services/club_bootstrap.py @@ -100,5 +100,9 @@ def bootstrap_club_from_template( Szjilu.objects.get_or_create(club_id=new_id) copy_display_config(template_id, new_id) + from jituan.services.club_shangpin_leixing import copy_club_shangpin_leixing_config + from jituan.services.club_kaohe_chenghao import copy_club_kaohe_chenghao_config + copy_club_shangpin_leixing_config(template_id, new_id) + copy_club_kaohe_chenghao_config(template_id, new_id) return club diff --git a/jituan/services/club_kaohe_chenghao.py b/jituan/services/club_kaohe_chenghao.py new file mode 100644 index 0000000..d1c9741 --- /dev/null +++ b/jituan/services/club_kaohe_chenghao.py @@ -0,0 +1,129 @@ +"""俱乐部抢单池标签筛选区配置(全局考核称号由集团定义)。""" +from jituan.constants import CLUB_ID_DEFAULT +from jituan.models import ClubKaoheChenghaoConfig +from jituan.services.club_context import ( + club_id_for_write, + resolve_club_id_from_request, + resolve_club_scope, +) +from jituan.constants import DATA_SCOPE_ALL +from rank.models import Chenghao + +CLUB_WRITE_SCOPE_MSG = '当前为集团汇总视图,请先在顶栏切换到具体俱乐部后再操作' + + +def club_write_blocked(request): + return resolve_club_scope(request) == DATA_SCOPE_ALL + + +def _config_map(club_id): + return { + r.chenghao_id: r + for r in ClubKaoheChenghaoConfig.query.filter(club_id=club_id) + } + + +def _is_visible(club_id, chenghao_id, cfg): + if cfg is not None: + return bool(cfg.is_enabled) + return club_id == CLUB_ID_DEFAULT + + +def filter_chenghao_ids_for_pool(club_id, chenghao_ids): + """抢单池标签筛选:仅保留本俱乐部已上架的称号 ID。""" + if not chenghao_ids: + return [] + cfg_map = _config_map(club_id) + visible = [] + for cid in chenghao_ids: + cfg = cfg_map.get(cid) + if _is_visible(club_id, cid, cfg): + visible.append(cid) + return visible + + +def build_club_kaohe_admin_list(request, bankuai_id=None): + club_id = resolve_club_id_from_request(request) + cfg_map = _config_map(club_id) + qs = Chenghao.objects.filter(leixing='dashou') + if bankuai_id: + qs = qs.filter(bankuai_id=bankuai_id) + enabled = [] + for ch in qs.order_by('id'): + cfg = cfg_map.get(ch.id) + if not _is_visible(club_id, ch.id, cfg): + continue + enabled.append({ + 'id': ch.id, + 'mingcheng': ch.mingcheng, + 'bankuai_id': ch.bankuai_id, + 'kaioi_jinpai': bool(ch.kaioi_jinpai), + 'club_enabled': True, + }) + return { + 'club_id': club_id, + 'list': enabled, + 'scope': resolve_club_scope(request), + } + + +def build_club_kaohe_catalog(request, bankuai_id=None): + club_id = resolve_club_id_from_request(request) + cfg_map = _config_map(club_id) + qs = Chenghao.objects.filter(leixing='dashou') + if bankuai_id: + qs = qs.filter(bankuai_id=bankuai_id) + catalog = [] + for ch in qs.order_by('mingcheng'): + cfg = cfg_map.get(ch.id) + if _is_visible(club_id, ch.id, cfg): + continue + catalog.append({ + 'id': ch.id, + 'mingcheng': ch.mingcheng, + 'bankuai_id': ch.bankuai_id, + }) + return {'club_id': club_id, 'catalog': catalog} + + +def enable_chenghao_for_club(request, chenghao_id): + if club_write_blocked(request): + return None, CLUB_WRITE_SCOPE_MSG + club_id = club_id_for_write(request) + if not Chenghao.objects.filter(id=chenghao_id, leixing='dashou').exists(): + return None, '考核标签不存在' + row, _ = ClubKaoheChenghaoConfig.query.get_or_create( + club_id=club_id, + chenghao_id=chenghao_id, + defaults={'is_enabled': True}, + ) + row.is_enabled = True + row.save(update_fields=['is_enabled', 'UpdateTime']) + return {'chenghao_id': chenghao_id}, None + + +def disable_chenghao_for_club(request, chenghao_id): + if club_write_blocked(request): + return None, CLUB_WRITE_SCOPE_MSG + club_id = club_id_for_write(request) + row, _ = ClubKaoheChenghaoConfig.query.get_or_create( + club_id=club_id, + chenghao_id=chenghao_id, + defaults={'is_enabled': False}, + ) + row.is_enabled = False + row.save(update_fields=['is_enabled', 'UpdateTime']) + return {'chenghao_id': chenghao_id}, None + + +def copy_club_kaohe_chenghao_config(template_club_id, new_club_id): + for row in ClubKaoheChenghaoConfig.query.filter(club_id=template_club_id): + if ClubKaoheChenghaoConfig.query.filter( + club_id=new_club_id, chenghao_id=row.chenghao_id, + ).exists(): + continue + ClubKaoheChenghaoConfig.query.create( + club_id=new_club_id, + chenghao_id=row.chenghao_id, + is_enabled=row.is_enabled, + ) diff --git a/jituan/services/club_shangpin_leixing.py b/jituan/services/club_shangpin_leixing.py new file mode 100644 index 0000000..721d2bb --- /dev/null +++ b/jituan/services/club_shangpin_leixing.py @@ -0,0 +1,192 @@ +"""俱乐部商品类型上架与图标(全局类型由集团定义)。""" +import logging +import os +import uuid + +from django.db import transaction + +from jituan.constants import CLUB_ID_DEFAULT +from jituan.models import ClubShangpinLeixingConfig +from jituan.services.club_context import ( + club_id_for_write, + resolve_club_id_from_request, + resolve_club_scope, +) +from jituan.constants import DATA_SCOPE_ALL +from products.models import ShangpinLeixing +from utils.oss_utils import delete_from_oss, upload_to_oss, validate_image + +logger = logging.getLogger(__name__) + +CLUB_WRITE_SCOPE_MSG = '当前为集团汇总视图,请先在顶栏切换到具体俱乐部后再操作' + + +def club_write_blocked(request): + return resolve_club_scope(request) == DATA_SCOPE_ALL + + +def _config_map(club_id): + return { + r.leixing_id: r + for r in ClubShangpinLeixingConfig.query.filter(club_id=club_id) + } + + +def _resolve_tupian(leixing, cfg): + if cfg and cfg.tupian_url: + return cfg.tupian_url + return leixing.tupian_url or '' + + +def _is_visible(club_id, leixing_id, cfg): + if cfg is not None: + return bool(cfg.is_enabled) + return club_id == CLUB_ID_DEFAULT + + +def build_leixing_item(leixing, cfg=None): + item = { + 'id': leixing.id, + 'jieshao': leixing.jieshao or '', + 'tupian_url': _resolve_tupian(leixing, cfg), + 'global_tupian_url': leixing.tupian_url or '', + 'yaoqiuleixing': leixing.yaoqiuleixing, + 'huiyuan_id': leixing.huiyuan_id, + 'yongjin': float(leixing.yongjin) if leixing.yongjin else None, + 'paixu': (cfg.paixu if cfg and cfg.paixu else leixing.paixu) or 0, + 'shenhezhuangtai': leixing.shenhezhuangtai or 1, + 'bankuai_id': leixing.bankuai_id, + 'club_enabled': True, + 'icon_source': 'club' if cfg and cfg.tupian_url else 'global', + } + return item + + +def build_club_leixing_list_for_api(club_id): + """小程序/抢单池:仅返回本俱乐部已上架的类型,图标优先俱乐部配置。""" + cfg_map = _config_map(club_id) + rows = [] + for lx in ShangpinLeixing.query.exclude(shenhezhuangtai=2): + cfg = cfg_map.get(lx.id) + if not _is_visible(club_id, lx.id, cfg): + continue + rows.append(build_leixing_item(lx, cfg)) + rows.sort(key=lambda x: (-int(x['paixu'] or 0), x['id'])) + return rows + + +def build_club_leixing_admin_list(request): + club_id = resolve_club_id_from_request(request) + cfg_map = _config_map(club_id) + enabled = [] + for lx in ShangpinLeixing.query.exclude(shenhezhuangtai=2).order_by('-paixu', 'id'): + cfg = cfg_map.get(lx.id) + if not _is_visible(club_id, lx.id, cfg): + continue + item = build_leixing_item(lx, cfg) + item['club_enabled'] = True + enabled.append(item) + enabled.sort(key=lambda x: (-int(x['paixu'] or 0), x['id'])) + return { + 'club_id': club_id, + 'list': enabled, + 'scope': resolve_club_scope(request), + } + + +def build_club_leixing_catalog(request): + club_id = resolve_club_id_from_request(request) + cfg_map = _config_map(club_id) + catalog = [] + for lx in ShangpinLeixing.query.exclude(shenhezhuangtai=2).order_by('-paixu', 'id'): + cfg = cfg_map.get(lx.id) + if _is_visible(club_id, lx.id, cfg): + continue + catalog.append({ + 'id': lx.id, + 'jieshao': lx.jieshao or '', + 'tupian_url': lx.tupian_url or '', + 'bankuai_id': lx.bankuai_id, + }) + return {'club_id': club_id, 'catalog': catalog} + + +def enable_leixing_for_club(request, leixing_id, paixu=0): + if club_write_blocked(request): + return None, CLUB_WRITE_SCOPE_MSG + club_id = club_id_for_write(request) + try: + lx = ShangpinLeixing.query.get(id=leixing_id) + except ShangpinLeixing.DoesNotExist: + return None, '商品类型不存在' + if lx.shenhezhuangtai == 2: + return None, '该类型为审核专用,不可上架' + + row, _ = ClubShangpinLeixingConfig.query.get_or_create( + club_id=club_id, + leixing_id=leixing_id, + defaults={'is_enabled': True, 'paixu': int(paixu or lx.paixu or 0)}, + ) + row.is_enabled = True + if paixu: + row.paixu = int(paixu) + row.save(update_fields=['is_enabled', 'paixu', 'UpdateTime']) + return {'leixing_id': leixing_id}, None + + +def disable_leixing_for_club(request, leixing_id): + if club_write_blocked(request): + return None, CLUB_WRITE_SCOPE_MSG + club_id = club_id_for_write(request) + row, _ = ClubShangpinLeixingConfig.query.get_or_create( + club_id=club_id, + leixing_id=leixing_id, + defaults={'is_enabled': False}, + ) + row.is_enabled = False + row.save(update_fields=['is_enabled', 'UpdateTime']) + return {'leixing_id': leixing_id}, None + + +def upload_leixing_icon(request, leixing_id, file_obj): + if club_write_blocked(request): + return None, CLUB_WRITE_SCOPE_MSG + club_id = club_id_for_write(request) + if not ShangpinLeixing.query.filter(id=leixing_id).exists(): + return None, '商品类型不存在' + ok, msg = validate_image(file_obj) + if not ok: + return None, msg + + ext = os.path.splitext(file_obj.name)[1].lower() or '.png' + if ext not in ('.png', '.jpg', '.jpeg', '.webp'): + ext = '.png' + oss_path = f'club/{club_id}/miniapp/shangpinleixing/{leixing_id}_{uuid.uuid4().hex}{ext}' + + row, _ = ClubShangpinLeixingConfig.query.get_or_create( + club_id=club_id, + leixing_id=leixing_id, + defaults={'is_enabled': club_id == CLUB_ID_DEFAULT}, + ) + if row.tupian_url and row.tupian_url != oss_path: + delete_from_oss(row.tupian_url) + if not upload_to_oss(file_obj, oss_path): + return None, '图片上传失败' + row.tupian_url = oss_path + row.save(update_fields=['tupian_url', 'UpdateTime']) + return {'leixing_id': leixing_id, 'tupian_url': oss_path}, None + + +def copy_club_shangpin_leixing_config(template_club_id, new_club_id): + for row in ClubShangpinLeixingConfig.query.filter(club_id=template_club_id): + if ClubShangpinLeixingConfig.query.filter( + club_id=new_club_id, leixing_id=row.leixing_id, + ).exists(): + continue + ClubShangpinLeixingConfig.query.create( + club_id=new_club_id, + leixing_id=row.leixing_id, + is_enabled=row.is_enabled, + tupian_url='', # 新俱乐部需自行上传图标,不复制 OSS 路径 + paixu=row.paixu, + ) diff --git a/jituan/urls.py b/jituan/urls.py index fe55b13..bdd3541 100644 --- a/jituan/urls.py +++ b/jituan/urls.py @@ -51,6 +51,17 @@ from jituan.views import ( ClubWechatLoginView, ) +from jituan.views_club_catalog import ( + ClubKaoheChenghaoCatalogView, + ClubKaoheChenghaoDisableView, + ClubKaoheChenghaoEnableView, + ClubKaoheChenghaoListView, + ClubShangpinLeixingCatalogView, + ClubShangpinLeixingDisableView, + ClubShangpinLeixingEnableView, + ClubShangpinLeixingIconView, + ClubShangpinLeixingListView, +) from jituan.views_identity_tag import ( IdentityTagBindListView, IdentityTagBindView, @@ -110,5 +121,14 @@ urlpatterns = [ path('houtai/hthycatalog', ClubMemberCatalogView.as_view(), name='jituan_member_catalog'), path('houtai/hthysj', ClubMemberEnableView.as_view(), name='jituan_member_enable'), path('houtai/hthyxj', ClubMemberDisableView.as_view(), name='jituan_member_disable'), + path('houtai/club-leixing-list', ClubShangpinLeixingListView.as_view(), name='jituan_club_leixing_list'), + path('houtai/club-leixing-catalog', ClubShangpinLeixingCatalogView.as_view(), name='jituan_club_leixing_catalog'), + path('houtai/club-leixing-enable', ClubShangpinLeixingEnableView.as_view(), name='jituan_club_leixing_enable'), + path('houtai/club-leixing-disable', ClubShangpinLeixingDisableView.as_view(), name='jituan_club_leixing_disable'), + path('houtai/club-leixing-icon', ClubShangpinLeixingIconView.as_view(), name='jituan_club_leixing_icon'), + path('houtai/club-kaohe-list', ClubKaoheChenghaoListView.as_view(), name='jituan_club_kaohe_list'), + path('houtai/club-kaohe-catalog', ClubKaoheChenghaoCatalogView.as_view(), name='jituan_club_kaohe_catalog'), + path('houtai/club-kaohe-enable', ClubKaoheChenghaoEnableView.as_view(), name='jituan_club_kaohe_enable'), + path('houtai/club-kaohe-disable', ClubKaoheChenghaoDisableView.as_view(), name='jituan_club_kaohe_disable'), path('club/list', ClubListView.as_view(), name='jituan_club_list'), ] diff --git a/jituan/views_club_catalog.py b/jituan/views_club_catalog.py new file mode 100644 index 0000000..c6ba68d --- /dev/null +++ b/jituan/views_club_catalog.py @@ -0,0 +1,194 @@ +"""俱乐部商品类型 / 考核标签筛选配置 — 后台 API。""" +from rest_framework.parsers import FormParser, JSONParser, MultiPartParser +from rest_framework.response import Response +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated + +from backend.view import verify_kefu_permission + + +class ClubShangpinLeixingListView(APIView): + """POST /jituan/houtai/club-leixing-list""" + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + if not phone: + return Response({'code': 401, 'msg': '缺少客服账号'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限管理小程序商品类型'}) + from jituan.services.club_shangpin_leixing import build_club_leixing_admin_list + payload = build_club_leixing_admin_list(request) + return Response({'code': 0, 'data': payload}) + + +class ClubShangpinLeixingCatalogView(APIView): + """POST /jituan/houtai/club-leixing-catalog""" + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + if not phone: + return Response({'code': 401, 'msg': '缺少客服账号'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + from jituan.services.club_shangpin_leixing import build_club_leixing_catalog + return Response({'code': 0, 'data': build_club_leixing_catalog(request)}) + + +class ClubShangpinLeixingEnableView(APIView): + """POST /jituan/houtai/club-leixing-enable""" + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + leixing_id = request.data.get('leixing_id') + if not phone or not leixing_id: + return Response({'code': 400, 'msg': '参数不完整'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + from jituan.services.club_shangpin_leixing import enable_leixing_for_club + data, err = enable_leixing_for_club(request, int(leixing_id), request.data.get('paixu')) + if err: + return Response({'code': 400, 'msg': err}) + return Response({'code': 0, 'msg': '上架成功', 'data': data}) + + +class ClubShangpinLeixingDisableView(APIView): + """POST /jituan/houtai/club-leixing-disable""" + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + leixing_id = request.data.get('leixing_id') + if not phone or not leixing_id: + return Response({'code': 400, 'msg': '参数不完整'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + from jituan.services.club_shangpin_leixing import disable_leixing_for_club + data, err = disable_leixing_for_club(request, int(leixing_id)) + if err: + return Response({'code': 400, 'msg': err}) + return Response({'code': 0, 'msg': '已下架', 'data': data}) + + +class ClubShangpinLeixingIconView(APIView): + """POST /jituan/houtai/club-leixing-icon""" + permission_classes = [IsAuthenticated] + parser_classes = [MultiPartParser, FormParser, JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + leixing_id = request.data.get('leixing_id') + file_obj = request.FILES.get('file') + if not phone or not leixing_id or not file_obj: + return Response({'code': 400, 'msg': '参数不完整'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + from jituan.services.club_shangpin_leixing import upload_leixing_icon + data, err = upload_leixing_icon(request, int(leixing_id), file_obj) + if err: + return Response({'code': 400, 'msg': err}) + return Response({'code': 0, 'msg': '上传成功', 'data': data}) + + +class ClubKaoheChenghaoListView(APIView): + """POST /jituan/houtai/club-kaohe-list""" + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + if not phone: + return Response({'code': 401, 'msg': '缺少客服账号'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + bankuai_id = request.data.get('bankuai_id') + from jituan.services.club_kaohe_chenghao import build_club_kaohe_admin_list + payload = build_club_kaohe_admin_list(request, bankuai_id=bankuai_id) + return Response({'code': 0, 'data': payload}) + + +class ClubKaoheChenghaoCatalogView(APIView): + """POST /jituan/houtai/club-kaohe-catalog""" + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + if not phone: + return Response({'code': 401, 'msg': '缺少客服账号'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + bankuai_id = request.data.get('bankuai_id') + from jituan.services.club_kaohe_chenghao import build_club_kaohe_catalog + return Response({'code': 0, 'data': build_club_kaohe_catalog(request, bankuai_id=bankuai_id)}) + + +class ClubKaoheChenghaoEnableView(APIView): + """POST /jituan/houtai/club-kaohe-enable""" + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + chenghao_id = request.data.get('chenghao_id') + if not phone or not chenghao_id: + return Response({'code': 400, 'msg': '参数不完整'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + from jituan.services.club_kaohe_chenghao import enable_chenghao_for_club + data, err = enable_chenghao_for_club(request, int(chenghao_id)) + if err: + return Response({'code': 400, 'msg': err}) + return Response({'code': 0, 'msg': '上架成功', 'data': data}) + + +class ClubKaoheChenghaoDisableView(APIView): + """POST /jituan/houtai/club-kaohe-disable""" + permission_classes = [IsAuthenticated] + parser_classes = [JSONParser] + + def post(self, request): + phone = (request.data.get('phone') or request.data.get('username') or '').strip() + chenghao_id = request.data.get('chenghao_id') + if not phone or not chenghao_id: + return Response({'code': 400, 'msg': '参数不完整'}) + kefu, permissions = verify_kefu_permission(request, phone) + if kefu is None: + return permissions + if '8080a' not in permissions: + return Response({'code': 403, 'msg': '无权限'}) + from jituan.services.club_kaohe_chenghao import disable_chenghao_for_club + data, err = disable_chenghao_for_club(request, int(chenghao_id)) + if err: + return Response({'code': 400, 'msg': err}) + return Response({'code': 0, 'msg': '已下架', 'data': data}) diff --git a/orders/views.py b/orders/views.py index f4e769e..9b7f9c9 100644 --- a/orders/views.py +++ b/orders/views.py @@ -5784,27 +5784,13 @@ class DashouHuoquLeixingView(APIView): try: logger.info(f"打手获取商品类型请求 - 用户ID: {request.user.UserUID}") - # 排除审核专用类型(shenhezhuangtai=2),兼容历史数据 shenhezhuangtai 为空或 0 - leixing_queryset = ShangpinLeixing.query.exclude( - shenhezhuangtai=2 - ).order_by('-paixu', 'id') + from jituan.services.club_context import resolve_effective_club_id + from jituan.services.club_shangpin_leixing import build_club_leixing_list_for_api - # 🔴 构建返回数据列表 - leixing_list = [] - for leixing in leixing_queryset: - leixing_list.append({ - "id": leixing.id, - "jieshao": leixing.jieshao or "", - "tupian_url": leixing.tupian_url or "", - "yaoqiuleixing": leixing.yaoqiuleixing, - "huiyuan_id": leixing.huiyuan_id, - "yongjin": float(leixing.yongjin) if leixing.yongjin else None, - "paixu": leixing.paixu or 0, - "shenhezhuangtai": leixing.shenhezhuangtai or 1 - }) + club_id = resolve_effective_club_id(request, request.user) + leixing_list = build_club_leixing_list_for_api(club_id) - # 🔴 记录日志 - logger.info(f"返回商品类型数据:{len(leixing_list)}条,用户: {request.user.UserUID}") + logger.info(f"返回商品类型数据:{len(leixing_list)}条,俱乐部:{club_id} 用户: {request.user.UserUID}") # 🔴 返回成功响应 # 注意:保持与前端原数据格式完全一致 diff --git a/products/views.py b/products/views.py index 68ebdd3..26fa336 100644 --- a/products/views.py +++ b/products/views.py @@ -88,25 +88,20 @@ class ShangpinHuoquView(APIView): try: logger.info("收到商品数据获取请求") - # 1. 查询商品类型(只查询正常状态的类型,并排序) - # 正常运营:只显示 shenhezhuangtai=2 的商品类型 - leixing_queryset = ShangpinLeixing.query.filter( - shenhezhuangtai=1 # 只取正常状态 - ).order_by('-paixu', 'id') # 先按paixu降序,再按id升序 + from jituan.services.club_context import resolve_effective_club_id + from jituan.services.club_shangpin_leixing import build_club_leixing_list_for_api - # 注意:审核期间使用下面这行代码(先注释掉) - '''leixing_queryset = ShangpinLeixing.query.filter( - shenhezhuangtai=2 # 审核期间:只取审核专用状态 - ).order_by('-paixu', 'id')''' - - shangpinleixing_data = [] - for leixing in leixing_queryset: - shangpinleixing_data.append({ - "id": leixing.id, - "jieshao": leixing.jieshao, - "tupian_url": leixing.tupian_url, - "paixu": leixing.paixu - }) + club_id = resolve_effective_club_id(request, getattr(request, 'user', None)) + leixing_rows = build_club_leixing_list_for_api(club_id) + shangpinleixing_data = [ + { + 'id': row['id'], + 'jieshao': row['jieshao'], + 'tupian_url': row['tupian_url'], + 'paixu': row['paixu'], + } + for row in leixing_rows + ] # 2. 查询商品专区(只查询可见类型下的专区,并排序) # 先获取可见的类型ID列表 diff --git a/rank/views.py b/rank/views.py index 323b23e..3eb7c35 100644 --- a/rank/views.py +++ b/rank/views.py @@ -76,9 +76,18 @@ class BankuaiBiaoqianView(APIView): return Response({'code': 0, 'data': {'biaoqian_list': []}}) # 获取该板块下所有类型的标签(leixing可以为dashou/shangjia等,这里先返回所有) - chenghao_list = Chenghao.objects.filter(bankuai=bankuai) + chenghao_list = Chenghao.objects.filter(bankuai=bankuai, leixing='dashou') data = [] + from jituan.services.club_context import resolve_effective_club_id + from jituan.services.club_kaohe_chenghao import filter_chenghao_ids_for_pool + + club_id = resolve_effective_club_id(request, request.user) + allowed_ids = set( + filter_chenghao_ids_for_pool(club_id, list(chenghao_list.values_list('id', flat=True))) + ) for ch in chenghao_list: + if ch.id not in allowed_ids: + continue texiao_json = {} if ch.texiao_miaoshu: try: