"""俱乐部商品类型上架与图标(全局类型由集团定义)。""" import logging import os import uuid from django.db import transaction from jituan.constants import CLUB_ID_DEFAULT, DATA_SCOPE_ALL from jituan.models import ClubShangpinLeixingConfig from jituan.services.club_context import ( club_id_for_write, resolve_club_id_from_request, resolve_club_scope, ) 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 = '当前为集团汇总视图,请先在顶栏切换到具体俱乐部后再操作' CLUB_READ_SCOPE_MSG = '请先在顶栏切换到具体小程序/俱乐部后再操作' def club_read_blocked(request): return resolve_club_scope(request) == DATA_SCOPE_ALL 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): if club_read_blocked(request): return { 'club_id': resolve_club_id_from_request(request), 'list': [], 'scope': resolve_club_scope(request), 'scope_error': CLUB_READ_SCOPE_MSG, } 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): if club_read_blocked(request): return { 'club_id': resolve_club_id_from_request(request), 'catalog': [], 'scope_error': CLUB_READ_SCOPE_MSG, } 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, )