fix: 俱乐部类型排序隔离;商家派单仅本店已上架类型

单店排序写入 ClubShangpinLeixingConfig;sjspleixing/派单/链接/模板校验本店上架。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQueandCursor committed 2026-08-10 10:48:10 +08:00
1 parent be2892f419
commit c10727c356
7 files changed
+113 -31

No files matched your search

+23 -5
View File
@@ -643,11 +643,26 @@ class SaveProductOrderView(APIView):
except:
return Response({'code': 400, 'msg': 'id或paixu格式错误'})
from jituan.constants import DATA_SCOPE_ALL
from jituan.services.club_context import (
filter_club_char_field,
resolve_club_scope,
)
from jituan.services.club_shangpin_leixing import save_club_leixing_order
club_scoped = resolve_club_scope(request) != DATA_SCOPE_ALL
with transaction.atomic():
if sort_type == 'leixing':
# 更新商品类型的排序
for item in items:
ShangpinLeixing.query.filter(id=item['id']).update(paixu=item['paixu'])
if club_scoped:
# 单店:写 ClubShangpinLeixingConfig.paixu,各俱乐部互不影响
err = save_club_leixing_order(request, items)
if err:
return Response({'code': 400, 'msg': err})
else:
# 集团汇总:仍写全局类型排序(仅集团视图)
for item in items:
ShangpinLeixing.query.filter(id=item['id']).update(paixu=item['paixu'])
else: # zhuanqu
leixing_id = request.data.get('leixing_id')
if not leixing_id:
@@ -656,9 +671,12 @@ class SaveProductOrderView(APIView):
leixing_id = int(leixing_id)
except:
return Response({'code': 400, 'msg': 'leixing_id格式错误'})
# 可选:检查该专区是否属于该类型,但不强制,由前端保证
# 专区本身按 club_id 隔离;更新时再按当前店过滤,防止串店
for item in items:
ShangpinZhuanqu.query.filter(id=item['id']).update(paixu=item['paixu'])
qs = ShangpinZhuanqu.query.filter(id=item['id'], leixing_id=leixing_id)
if club_scoped:
qs = filter_club_char_field(qs, request, field='club_id')
qs.update(paixu=item['paixu'])
return Response({'code': 0, 'msg': '排序保存成功'})
+5 -1
View File
@@ -95,6 +95,11 @@ class ShangjiaGenerateLinkView(APIView):
except ShangpinLeixing.DoesNotExist:
return Response({'code': 400, 'msg': '商品类型不存在或已禁用', 'data': {}}, status=400)
club_id = resolve_club_id_from_request(request)
from jituan.services.club_shangpin_leixing import is_leixing_enabled_for_club
if not is_leixing_enabled_for_club(club_id, shangpin_type_id):
return Response({'code': 400, 'msg': '该商品类型未在本俱乐部上架', 'data': {}}, status=400)
# 查询模板
try:
moban = ShangjiaMoban.query.get(id=moban_id, UserID=user.UserUID, ProductTypeID=shangpin_type_id)
@@ -106,7 +111,6 @@ class ShangjiaGenerateLinkView(APIView):
return Response({'code': 400, 'msg': f'余额不足,当前余额{shangjia.yue}元', 'data': {}}, status=400)
# 打手+管事分成(与普通派单 sjpaifa 同一套算法,打手优先)
club_id = resolve_club_id_from_request(request)
try:
from jituan.services.order_deal import check_merchant_dispatch_block
block = check_merchant_dispatch_block(club_id, user.UserUID)
+6 -1
View File
@@ -266,7 +266,7 @@ class ShangjiaTianjiaMobanView(APIView):
except (ValueError, TypeError):
return Response({'code': 400, 'msg': '标签ID格式错误', 'data': {}}, status=400)
# 8. 验证商品类型是否存在且可用
# 8. 验证商品类型是否存在且可用(且本俱乐部已上架)
try:
shangpin_obj = ShangpinLeixing.query.get(
id=leixing_id,
@@ -275,6 +275,11 @@ class ShangjiaTianjiaMobanView(APIView):
except ShangpinLeixing.DoesNotExist:
return Response({'code': 404, 'msg': '商品类型不存在或已禁用', 'data': {}}, status=404)
from jituan.services.club_context import resolve_club_id_from_request
from jituan.services.club_shangpin_leixing import is_leixing_enabled_for_club
if not is_leixing_enabled_for_club(resolve_club_id_from_request(request), leixing_id):
return Response({'code': 400, 'msg': '该商品类型未在本俱乐部上架', 'data': {}}, status=400)
# 9. 检查同一用户、同一类型下是否已存在相同介绍的模板
if ShangjiaMoban.query.filter(
UserID=yonghu_id,
+43 -1
View File
@@ -49,6 +49,10 @@ def _is_visible(club_id, leixing_id, cfg):
def build_leixing_item(leixing, cfg=None):
if cfg is not None:
paixu = int(cfg.paixu or 0)
else:
paixu = int(leixing.paixu or 0)
item = {
'id': leixing.id,
'jieshao': leixing.jieshao or '',
@@ -57,7 +61,7 @@ def build_leixing_item(leixing, cfg=None):
'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,
'paixu': paixu,
'shenhezhuangtai': leixing.shenhezhuangtai or 1,
'bankuai_id': leixing.bankuai_id,
'club_enabled': True,
@@ -68,6 +72,44 @@ def build_leixing_item(leixing, cfg=None):
return item
def is_leixing_enabled_for_club(club_id, leixing_id):
"""本俱乐部是否已上架该商品类型。"""
club_id = (club_id or '').strip()
if not club_id or leixing_id is None:
return False
cfg = ClubShangpinLeixingConfig.query.filter(
club_id=club_id, leixing_id=int(leixing_id),
).first()
return _is_visible(club_id, int(leixing_id), cfg)
def save_club_leixing_order(request, items):
"""
按本俱乐部写入类型排序(互不影响全局/其他店)。
items: [{id, paixu}, ...]
成功返回 None,失败返回错误文案。
"""
if club_write_blocked(request):
return CLUB_WRITE_SCOPE_MSG
club_id = club_id_for_write(request)
if not club_id:
return '无法解析当前俱乐部'
with transaction.atomic():
for item in items:
leixing_id = int(item['id'])
paixu = int(item['paixu'])
row, _ = ClubShangpinLeixingConfig.query.get_or_create(
club_id=club_id,
leixing_id=leixing_id,
defaults={'is_enabled': True, 'paixu': paixu},
)
if not row.is_enabled:
continue
row.paixu = paixu
row.save(update_fields=['paixu', 'UpdateTime'])
return None
def build_club_leixing_list_for_api(club_id):
"""小程序/抢单池:仅返回本俱乐部已上架的类型,图标优先俱乐部配置。"""
cfg_map = _config_map(club_id)
+5
View File
@@ -63,6 +63,11 @@ def staff_dispatch_order(member, staff_user, data):
except ShangpinLeixing.DoesNotExist:
return False, {'code': 400, 'msg': '商品类型不存在', 'data': None}
club_id = getattr(member, 'club_id', None) or ''
from jituan.services.club_shangpin_leixing import is_leixing_enabled_for_club
if not is_leixing_enabled_for_club(club_id, shangpin_type.id):
return False, {'code': 400, 'msg': '该商品类型未在本俱乐部上架', 'data': None}
if commission_enabled and commission_value_str:
try:
cv = float(commission_value_str)
+4
View File
@@ -106,6 +106,10 @@ def staff_generate_link(member, staff_user, data):
except ShangpinLeixing.DoesNotExist:
return False, {'code': 400, 'msg': '商品类型不存在或已禁用', 'data': {}}
from jituan.services.club_shangpin_leixing import is_leixing_enabled_for_club
if not is_leixing_enabled_for_club(club_id, shangpin_type_id):
return False, {'code': 400, 'msg': '该商品类型未在本俱乐部上架', 'data': {}}
try:
moban = ShangjiaMoban.query.get(
id=moban_id, UserID=merchant_id, ProductTypeID=shangpin_type_id,
+27 -23
View File
@@ -116,21 +116,23 @@ class ShangpinLeixingView(APIView):
return Response({'code': 403, 'msg': '非商家或有效客服,无权获取订单类型', 'data': []})
try:
# 1. 查询审核通过且抢单要求类型为1的商品类型,按排序权重升序
shangpin_queryset = ShangpinLeixing.query.filter(
yaoqiuleixing=1,
shenhezhuangtai=1
).select_related('bankuai').order_by('paixu')
from jituan.services.club_shangpin_leixing import build_club_leixing_list_for_api
# 2. 收集所有涉及到的板块ID
bankuai_ids = shangpin_queryset.values_list('bankuai_id', flat=True).distinct()
# 仅本俱乐部已上架类型(生成链接 / 自定义发单 / 常规单统一入口)
club_id = resolve_club_id_from_request(request)
club_rows = [
r for r in build_club_leixing_list_for_api(club_id)
if int(r.get('yaoqiuleixing') or 0) == 1
and int(r.get('shenhezhuangtai') or 0) == 1
]
# 3. 查询这些板块下的所有称号(每个称号绑定到一个板块)
bankuai_ids = {
r.get('bankuai_id') for r in club_rows if r.get('bankuai_id')
}
chenghao_list = Chenghao.query.filter(
bankuai_id__in=bankuai_ids
).values('id', 'mingcheng', 'texiao_miaoshu', 'bankuai_id')
).values('id', 'mingcheng', 'texiao_miaoshu', 'bankuai_id') if bankuai_ids else []
# 4. 构建 板块ID -> 称号列表 的映射(确保准确关联)
bankuai_chenghao_map = {}
for ch in chenghao_list:
bid = ch['bankuai_id']
@@ -139,21 +141,18 @@ class ShangpinLeixingView(APIView):
bankuai_chenghao_map[bid].append({
'id': ch['id'],
'mingcheng': ch['mingcheng'],
'texiao_json': ch['texiao_miaoshu'] # 前端 chenghao-tag 组件需要
'texiao_json': ch['texiao_miaoshu'],
})
# 5. 组装最终数据
data_list = []
for sp in shangpin_queryset:
item = {
'id': sp.id,
'yaoqiuleixing': sp.yaoqiuleixing,
'huiyuan_id': sp.huiyuan_id,
'jieshao': sp.jieshao,
# 当前类型所属板块的称号列表(无板块则为空数组)
'chenghaoList': bankuai_chenghao_map.get(sp.bankuai_id, [])
}
data_list.append(item)
for sp in club_rows:
data_list.append({
'id': sp['id'],
'yaoqiuleixing': sp.get('yaoqiuleixing'),
'huiyuan_id': sp.get('huiyuan_id'),
'jieshao': sp.get('jieshao') or '',
'chenghaoList': bankuai_chenghao_map.get(sp.get('bankuai_id'), []),
})
return Response({
'code': 200,
@@ -214,13 +213,18 @@ class ShangjiaPaifaView(APIView):
except ValueError:
return Response({'code': 400, 'msg': '价格格式错误'})
# ----- 5. 商品类型 -----
# ----- 5. 商品类型(必须本俱乐部已上架)-----
try:
shangpin_type = ShangpinLeixing.query.get(id=shangpin_type_id)
except ShangpinLeixing.DoesNotExist:
logger.error(f"商品类型不存在: {shangpin_type_id}")
return Response({'code': 400, 'msg': '商品类型不存在'})
from jituan.services.club_shangpin_leixing import is_leixing_enabled_for_club
_dispatch_club = resolve_club_id_from_request(request)
if not is_leixing_enabled_for_club(_dispatch_club, shangpin_type.id):
return Response({'code': 400, 'msg': '该商品类型未在本俱乐部上架'})
# ----- 6. 押金 / 会员模式决定 -----
final_yongjin = None
if commission_enabled and commission_value_str: