feat(jituan): 俱乐部商品类型/考核标签上架配置与抢单池过滤

This commit is contained in:
XingQue
2026-06-26 05:15:50 +08:00
parent 41fa98e8b7
commit 2b9ea6ccec
11 changed files with 642 additions and 38 deletions

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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,
)