1236 lines
48 KiB
Python
1236 lines
48 KiB
Python
import hmac
|
||
import threading
|
||
import traceback
|
||
import uuid
|
||
import hashlib
|
||
import xmltodict
|
||
import time
|
||
import logging
|
||
import requests
|
||
import os
|
||
import secrets
|
||
import random
|
||
import string
|
||
from calendar import monthrange
|
||
from decimal import Decimal
|
||
from datetime import date, datetime, timedelta
|
||
from django.utils import timezone
|
||
from django.conf import settings
|
||
from django.core.exceptions import ObjectDoesNotExist
|
||
from django.core.paginator import Paginator
|
||
from django.db import models, transaction, IntegrityError
|
||
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
|
||
from gvsdsdk.fluent import db, func, FQ
|
||
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
|
||
from django.contrib.auth.hashers import make_password
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
from django.utils.decorators import method_decorator
|
||
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework import status
|
||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
|
||
|
||
# 工具类导入
|
||
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
|
||
from backend.utils import (
|
||
update_dashou_daily_by_action,
|
||
update_shangjia_daily
|
||
)
|
||
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
|
||
from jituan.services.catalog_scope import block_non_group_catalog_scope
|
||
from jituan.services.club_penalty import (
|
||
filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
|
||
forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
|
||
)
|
||
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
|
||
from shop.utils import update_dianpu_daily_stat
|
||
from products.utils import update_shangpin_daily_stat
|
||
from orders.notice_tasks import dingdan_guangbo
|
||
from ..utils import (
|
||
verify_kefu_permission,
|
||
PERMISSION_TO_SHENFEN,
|
||
SHENFEN_PROFILE_MAP,
|
||
has_fadan_view_permission,
|
||
has_merchant_order_permission,
|
||
check_fadan_permission,
|
||
pick_order_id,
|
||
pick_penalty_create_params,
|
||
write_xiugai_log,
|
||
XIUGAI_LEIXING_DASHOU,
|
||
XIUGAI_LEIXING_GUANSHI,
|
||
XIUGAI_LEIXING_SHANGJIA,
|
||
XIUGAI_LEIXING_ZUZHANG,
|
||
XIUGAI_LEIXING_LABEL,
|
||
)
|
||
|
||
# models 集中导入
|
||
## gvsdsdk RBAC 模型(使用 PascalCase 字段名,匹配实际数据库表结构)
|
||
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
|
||
## backend
|
||
from backend.models import WithdrawalDailyStats
|
||
|
||
## users
|
||
from users.models import (
|
||
UserGuanshi, UserBoss, UserZuzhang,
|
||
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
|
||
)
|
||
from users.business_models import User
|
||
|
||
## orders
|
||
from orders.models import (
|
||
CommissionRate, PlayerDeliveryImage, PenaltyRecord,
|
||
OrderPlayerHistory, Order, RefundRecord,
|
||
MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
|
||
)
|
||
|
||
## shop
|
||
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
|
||
|
||
## products
|
||
from products.models import (
|
||
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
|
||
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
|
||
)
|
||
|
||
## config
|
||
from config.models import (
|
||
WithdrawConfig, ShangjiaLianjie, AccountPermission,
|
||
TixianQuotaDefault,
|
||
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
|
||
)
|
||
|
||
## rank
|
||
from rank.models import (
|
||
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
|
||
YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
|
||
)
|
||
|
||
# 序列化器
|
||
from ..serializers import PopupPageSerializer
|
||
|
||
# 全局常量、日志对象
|
||
logger = logging.getLogger('houtai')
|
||
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
|
||
|
||
class GetProductBaseDataView(APIView):
|
||
"""
|
||
获取商品基础数据(类型、专区、会员)
|
||
权限:2200a
|
||
POST /houtai/htsphqjcsx
|
||
参数:username
|
||
返回:
|
||
code:0, data: {
|
||
type_list: [ {id, jieshao, tupian_url, paixu, shenhezhuangtai, product_count}, ...],
|
||
zone_list: [ {id, mingzi, leixing_id, paixu, shenhezhuangtai, product_count}, ...],
|
||
member_list: [ {huiyuan_id, jieshao, jiage}, ...]
|
||
}
|
||
"""
|
||
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': '无权限管理商品'})
|
||
|
||
# 获取所有商品类型(按 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
|
||
|
||
try:
|
||
type_qs = ShangpinLeixing.query.all().order_by('paixu')
|
||
# 批量聚合商品数量,避免循环内 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',
|
||
)
|
||
# 用 Django QS 聚合,避免 Fluent values 链在部分环境下异常
|
||
_type_counts = {
|
||
item['leixing_id']: item['cnt']
|
||
for item in _public_sp.values('leixing_id').annotate(cnt=Count('id'))
|
||
}
|
||
type_list = []
|
||
for t in type_qs:
|
||
product_count = _type_counts.get(t.id, 0)
|
||
type_list.append({
|
||
'id': t.id,
|
||
'jieshao': t.jieshao or '',
|
||
'tupian_url': t.tupian_url or '',
|
||
'paixu': t.paixu,
|
||
'shenhezhuangtai': t.shenhezhuangtai,
|
||
'product_count': product_count,
|
||
})
|
||
|
||
# 获取专区(按俱乐部隔离;集团汇总看全部)
|
||
zone_qs = filter_club_char_field(
|
||
ShangpinZhuanqu.query.all().order_by('paixu'), request, field='club_id'
|
||
)
|
||
_zone_counts = {
|
||
item['zhuanqu_id']: item['cnt']
|
||
for item in filter_club_char_field(
|
||
Shangpin.query.filter(
|
||
dianpu__isnull=True, dianpu_leixing__isnull=True, zhuanqu_id__isnull=False
|
||
),
|
||
request,
|
||
field='club_id',
|
||
).values('zhuanqu_id').annotate(cnt=Count('id'))
|
||
}
|
||
zone_list = []
|
||
for z in zone_qs:
|
||
product_count = _zone_counts.get(z.id, 0)
|
||
zone_list.append({
|
||
'id': z.id,
|
||
'mingzi': z.mingzi or '',
|
||
'leixing_id': z.leixing_id,
|
||
'paixu': z.paixu,
|
||
'shenhezhuangtai': z.shenhezhuangtai,
|
||
'product_count': product_count,
|
||
'club_id': getattr(z, 'club_id', '') or '',
|
||
})
|
||
|
||
# 获取所有会员(只需 ID、介绍、价格)
|
||
member_list = []
|
||
for m in Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage'):
|
||
member_list.append({
|
||
'huiyuan_id': m['huiyuan_id'],
|
||
'jieshao': m['jieshao'] or '',
|
||
'jiage': float(m['jiage'] or 0),
|
||
})
|
||
|
||
# 本俱乐部「只看审核」开关(集团汇总不下发可写状态)
|
||
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 ''
|
||
try:
|
||
from shop.services.shenhe_config import (
|
||
get_or_create_shenhe_config,
|
||
is_zhi_kan_shenhe_enabled,
|
||
)
|
||
get_or_create_shenhe_config(club_id)
|
||
zhi_kan_shenhe = is_zhi_kan_shenhe_enabled(club_id)
|
||
except Exception:
|
||
logging.getLogger(__name__).exception(
|
||
'读取只看审核配置失败 club_id=%s(请确认已 migrate shop 0004)',
|
||
club_id,
|
||
)
|
||
zhi_kan_shenhe = True
|
||
|
||
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,
|
||
}
|
||
})
|
||
except Exception:
|
||
logging.getLogger(__name__).exception('GetProductBaseDataView 失败')
|
||
return Response({'code': 500, 'msg': '获取商品基础数据失败,请稍后重试'})
|
||
|
||
|
||
|
||
class GetProductListView(APIView):
|
||
"""
|
||
获取商品列表(支持筛选、排序)
|
||
权限:2200a
|
||
POST /houtai/hthqsplb
|
||
参数:
|
||
username: 必填
|
||
leixing_id: 类型ID(可选)
|
||
zhuanqu_id: 专区ID(可选)
|
||
keyword: 搜索关键词(商品标题/介绍)
|
||
sales_op: 销量比较符 gte/lte
|
||
sales_value: 销量数值
|
||
stock_op: 库存比较符 gte/lte
|
||
stock_value: 库存数值
|
||
sort_field: 排序字段 zhenshi_xiaoliang / jiage
|
||
sort_order: asc/desc
|
||
page: 页码(默认1)
|
||
page_size: 每页数量(默认1000,前端说不需要分页,但为性能仍保留)
|
||
返回:
|
||
code:0, data: { list: [...], total }
|
||
"""
|
||
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': '无权限管理商品'})
|
||
|
||
# 获取参数
|
||
leixing_id = request.data.get('leixing_id')
|
||
zhuanqu_id = request.data.get('zhuanqu_id')
|
||
keyword = request.data.get('keyword', '').strip()
|
||
sales_op = request.data.get('sales_op', '').strip().lower()
|
||
sales_value = request.data.get('sales_value')
|
||
stock_op = request.data.get('stock_op', '').strip().lower()
|
||
stock_value = request.data.get('stock_value')
|
||
sort_field = request.data.get('sort_field', '') # zhenshi_xiaoliang / jiage
|
||
sort_order = request.data.get('sort_order', 'desc').lower()
|
||
try:
|
||
page = int(request.data.get('page', 1))
|
||
page_size = int(request.data.get('page_size', 1000))
|
||
if page < 1 or page_size < 1:
|
||
raise ValueError
|
||
except:
|
||
return Response({'code': 400, 'msg': '分页参数错误'})
|
||
|
||
# 🔥 公共商品 + 按俱乐部隔离(集团汇总看全部)
|
||
from jituan.services.club_context import filter_club_char_field
|
||
qs = Shangpin.query.filter(
|
||
dianpu__isnull=True, # 所属店铺为空
|
||
dianpu_leixing__isnull=True # 店铺商品类型为空
|
||
)
|
||
qs = filter_club_char_field(qs, request, field='club_id')
|
||
|
||
if leixing_id:
|
||
try:
|
||
leixing_id = int(leixing_id)
|
||
qs = qs.filter(leixing_id=leixing_id)
|
||
except:
|
||
pass
|
||
if zhuanqu_id:
|
||
try:
|
||
zhuanqu_id = int(zhuanqu_id)
|
||
qs = qs.filter(zhuanqu_id=zhuanqu_id)
|
||
except:
|
||
pass
|
||
if keyword:
|
||
qs = qs.filter(Q(biaoqian__icontains=keyword) | Q(jieshao__icontains=keyword))
|
||
|
||
# 销量筛选
|
||
if sales_op and sales_value is not None:
|
||
try:
|
||
sales_val = int(sales_value)
|
||
if sales_op == 'gte':
|
||
qs = qs.filter(zhenshi_xiaoliang__gte=sales_val)
|
||
elif sales_op == 'lte':
|
||
qs = qs.filter(zhenshi_xiaoliang__lte=sales_val)
|
||
except:
|
||
pass
|
||
|
||
# 库存筛选
|
||
if stock_op and stock_value is not None:
|
||
try:
|
||
stock_val = int(stock_value)
|
||
if stock_op == 'gte':
|
||
qs = qs.filter(kucun__gte=stock_val)
|
||
elif stock_op == 'lte':
|
||
qs = qs.filter(kucun__lte=stock_val)
|
||
except:
|
||
pass
|
||
|
||
# 排序
|
||
if sort_field in ('zhenshi_xiaoliang', 'jiage'):
|
||
if sort_order == 'asc':
|
||
qs = qs.order_by(sort_field)
|
||
else:
|
||
qs = qs.order_by(f'-{sort_field}')
|
||
else:
|
||
qs = qs.order_by('-CreateTime') # 默认按创建时间倒序
|
||
|
||
# 分页
|
||
total = qs.count()
|
||
offset = (page - 1) * page_size
|
||
products = qs[offset:offset + page_size]
|
||
|
||
# 构建返回数据
|
||
product_list = []
|
||
for p in products:
|
||
product_list.append({
|
||
'id': p.id,
|
||
'biaoqian': p.biaoqian or '',
|
||
'jiage': str(p.jiage),
|
||
'kucun': p.kucun,
|
||
'zhenshi_xiaoliang': p.zhenshi_xiaoliang,
|
||
'duiwai_xiaoliang': p.duiwai_xiaoliang,
|
||
'tupian_url': p.tupian_url or '',
|
||
'jieshao': p.jieshao or '',
|
||
'club_id': getattr(p, 'club_id', '') or '',
|
||
})
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'list': product_list,
|
||
'total': total,
|
||
}
|
||
})
|
||
|
||
|
||
|
||
|
||
'''class GetProductBaseDataView(APIView):
|
||
"""
|
||
获取商品基础数据(类型、专区、会员)
|
||
权限:2200a
|
||
POST /houtai/htsphqjcsx
|
||
参数:username
|
||
返回:
|
||
code:0, data: {
|
||
type_list: [ {id, jieshao, tupian_url, paixu, shenhezhuangtai, product_count}, ...],
|
||
zone_list: [ {id, mingzi, leixing_id, paixu, shenhezhuangtai, product_count}, ...],
|
||
member_list: [ {huiyuan_id, jieshao, jiage}, ...]
|
||
}
|
||
"""
|
||
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': '无权限管理商品'})
|
||
|
||
# 获取所有商品类型(按 paixu 升序)
|
||
type_qs = ShangpinLeixing.query.all().order_by('paixu')
|
||
# 批量聚合商品数量,避免循环内 N+1 count
|
||
_type_counts = {
|
||
item['leixing_id']: item['cnt']
|
||
for item in Shangpin.query.filter(leixing_id__isnull=False).values('leixing_id').annotate(cnt=Count('id'))
|
||
}
|
||
type_list = []
|
||
for t in type_qs:
|
||
product_count = _type_counts.get(t.id, 0)
|
||
type_list.append({
|
||
'id': t.id,
|
||
'jieshao': t.jieshao or '',
|
||
'tupian_url': t.tupian_url or '',
|
||
'paixu': t.paixu,
|
||
'shenhezhuangtai': t.shenhezhuangtai,
|
||
'product_count': product_count,
|
||
})
|
||
|
||
# 获取所有专区(按 paixu 升序)
|
||
zone_qs = ShangpinZhuanqu.query.all().order_by('paixu')
|
||
_zone_counts = {
|
||
item['zhuanqu_id']: item['cnt']
|
||
for item in Shangpin.query.filter(zhuanqu_id__isnull=False).values('zhuanqu_id').annotate(cnt=Count('id'))
|
||
}
|
||
zone_list = []
|
||
for z in zone_qs:
|
||
product_count = _zone_counts.get(z.id, 0)
|
||
zone_list.append({
|
||
'id': z.id,
|
||
'mingzi': z.mingzi or '',
|
||
'leixing_id': z.leixing_id,
|
||
'paixu': z.paixu,
|
||
'shenhezhuangtai': z.shenhezhuangtai,
|
||
'product_count': product_count,
|
||
})
|
||
|
||
# 获取所有会员(只需 ID、介绍、价格)
|
||
member_list = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage'))
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'type_list': type_list,
|
||
'zone_list': zone_list,
|
||
'member_list': member_list,
|
||
}
|
||
})
|
||
|
||
|
||
class GetProductListView(APIView):
|
||
"""
|
||
获取商品列表(支持筛选、排序)
|
||
权限:2200a
|
||
POST /houtai/hthqsplb
|
||
参数:
|
||
username: 必填
|
||
leixing_id: 类型ID(可选)
|
||
zhuanqu_id: 专区ID(可选)
|
||
keyword: 搜索关键词(商品标题/介绍)
|
||
sales_op: 销量比较符 gte/lte
|
||
sales_value: 销量数值
|
||
stock_op: 库存比较符 gte/lte
|
||
stock_value: 库存数值
|
||
sort_field: 排序字段 zhenshi_xiaoliang / jiage
|
||
sort_order: asc/desc
|
||
page: 页码(默认1)
|
||
page_size: 每页数量(默认1000,前端说不需要分页,但为性能仍保留)
|
||
返回:
|
||
code:0, data: { list: [...], total }
|
||
"""
|
||
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': '无权限管理商品'})
|
||
|
||
# 获取参数
|
||
leixing_id = request.data.get('leixing_id')
|
||
zhuanqu_id = request.data.get('zhuanqu_id')
|
||
keyword = request.data.get('keyword', '').strip()
|
||
sales_op = request.data.get('sales_op', '').strip().lower()
|
||
sales_value = request.data.get('sales_value')
|
||
stock_op = request.data.get('stock_op', '').strip().lower()
|
||
stock_value = request.data.get('stock_value')
|
||
sort_field = request.data.get('sort_field', '') # zhenshi_xiaoliang / jiage
|
||
sort_order = request.data.get('sort_order', 'desc').lower()
|
||
try:
|
||
page = int(request.data.get('page', 1))
|
||
page_size = int(request.data.get('page_size', 1000))
|
||
if page < 1 or page_size < 1:
|
||
raise ValueError
|
||
except:
|
||
return Response({'code': 400, 'msg': '分页参数错误'})
|
||
|
||
# 构建查询集
|
||
qs = Shangpin.query.all()
|
||
|
||
if leixing_id:
|
||
try:
|
||
leixing_id = int(leixing_id)
|
||
qs = qs.filter(leixing_id=leixing_id)
|
||
except:
|
||
pass
|
||
if zhuanqu_id:
|
||
try:
|
||
zhuanqu_id = int(zhuanqu_id)
|
||
qs = qs.filter(zhuanqu_id=zhuanqu_id)
|
||
except:
|
||
pass
|
||
if keyword:
|
||
qs = qs.filter(Q(biaoqian__icontains=keyword) | Q(jieshao__icontains=keyword))
|
||
|
||
# 销量筛选
|
||
if sales_op and sales_value is not None:
|
||
try:
|
||
sales_val = int(sales_value)
|
||
if sales_op == 'gte':
|
||
qs = qs.filter(zhenshi_xiaoliang__gte=sales_val)
|
||
elif sales_op == 'lte':
|
||
qs = qs.filter(zhenshi_xiaoliang__lte=sales_val)
|
||
except:
|
||
pass
|
||
|
||
# 库存筛选
|
||
if stock_op and stock_value is not None:
|
||
try:
|
||
stock_val = int(stock_value)
|
||
if stock_op == 'gte':
|
||
qs = qs.filter(kucun__gte=stock_val)
|
||
elif stock_op == 'lte':
|
||
qs = qs.filter(kucun__lte=stock_val)
|
||
except:
|
||
pass
|
||
|
||
# 排序
|
||
if sort_field in ('zhenshi_xiaoliang', 'jiage'):
|
||
if sort_order == 'asc':
|
||
qs = qs.order_by(sort_field)
|
||
else:
|
||
qs = qs.order_by(f'-{sort_field}')
|
||
else:
|
||
qs = qs.order_by('-CreateTime') # 默认按创建时间倒序
|
||
|
||
# 分页
|
||
total = qs.count()
|
||
offset = (page - 1) * page_size
|
||
products = qs[offset:offset + page_size]
|
||
|
||
# 构建返回数据
|
||
product_list = []
|
||
for p in products:
|
||
product_list.append({
|
||
'id': p.id,
|
||
'biaoqian': p.biaoqian or '',
|
||
'jiage': str(p.jiage),
|
||
'kucun': p.kucun,
|
||
'zhenshi_xiaoliang': p.zhenshi_xiaoliang,
|
||
'tupian_url': p.tupian_url or '',
|
||
'jieshao': p.jieshao or '',
|
||
})
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'list': product_list,
|
||
'total': total,
|
||
}
|
||
})'''
|
||
|
||
|
||
class SaveProductOrderView(APIView):
|
||
"""
|
||
保存商品类型或专区的排序
|
||
权限:2200a
|
||
POST /houtai/htsppaixu
|
||
参数:
|
||
username: 必填
|
||
type: 'leixing' 或 'zhuanqu'
|
||
leixing_id: 当 type='zhuanqu' 时必填(用于限定该类型下的专区)
|
||
items: [ {id, paixu}, ... ] # 每个项目的新排序值(从1开始)
|
||
返回:
|
||
code:0 成功
|
||
"""
|
||
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': '无权限管理商品'})
|
||
|
||
sort_type = request.data.get('type', '').strip().lower()
|
||
if sort_type not in ('leixing', 'zhuanqu'):
|
||
return Response({'code': 400, 'msg': 'type参数必须为leixing或zhuanqu'})
|
||
|
||
items = request.data.get('items')
|
||
if not isinstance(items, list) or len(items) == 0:
|
||
return Response({'code': 400, 'msg': 'items参数无效'})
|
||
|
||
# 校验每个 item 的格式
|
||
for item in items:
|
||
if not isinstance(item, dict) or 'id' not in item or 'paixu' not in item:
|
||
return Response({'code': 400, 'msg': 'items内每项需包含id和paixu'})
|
||
try:
|
||
item['id'] = int(item['id'])
|
||
item['paixu'] = int(item['paixu'])
|
||
if item['paixu'] < 1:
|
||
return Response({'code': 400, 'msg': 'paixu必须为正整数'})
|
||
except:
|
||
return Response({'code': 400, 'msg': 'id或paixu格式错误'})
|
||
|
||
with transaction.atomic():
|
||
if sort_type == 'leixing':
|
||
# 更新商品类型的排序
|
||
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:
|
||
return Response({'code': 400, 'msg': '更新专区排序时必须提供leixing_id'})
|
||
try:
|
||
leixing_id = int(leixing_id)
|
||
except:
|
||
return Response({'code': 400, 'msg': 'leixing_id格式错误'})
|
||
# 可选:检查该专区是否属于该类型,但不强制,由前端保证
|
||
for item in items:
|
||
ShangpinZhuanqu.query.filter(id=item['id']).update(paixu=item['paixu'])
|
||
|
||
return Response({'code': 0, 'msg': '排序保存成功'})
|
||
|
||
|
||
class AddProductView(APIView):
|
||
"""
|
||
添加商品(含图片上传)
|
||
权限:2200a
|
||
POST /houtai/htfbsp
|
||
参数:multipart/form-data,包含所有商品字段及图片文件
|
||
字段说明见代码
|
||
"""
|
||
permission_classes = []
|
||
parser_classes = [MultiPartParser, FormParser]
|
||
|
||
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': '无权限管理商品'})
|
||
|
||
# 获取表单字段
|
||
biaoqian = request.data.get('biaoqian', '').strip()
|
||
jiage_str = request.data.get('jiage')
|
||
kucun_str = request.data.get('kucun')
|
||
duiwai_xiaoliang_str = request.data.get('duiwai_xiaoliang', '0')
|
||
leixing_id_str = request.data.get('leixing_id')
|
||
zhuanqu_id_str = request.data.get('zhuanqu_id')
|
||
yaoqiuleixing_str = request.data.get('yaoqiuleixing', '1')
|
||
huiyuan_id = request.data.get('huiyuan_id', '').strip()
|
||
yongjin_str = request.data.get('yongjin', '0')
|
||
fencheng_type = request.data.get('fencheng_type', 'default')
|
||
ewai_PlayerCommission_str = request.data.get('ewai_PlayerCommission', '0')
|
||
jieshao = request.data.get('jieshao', '').strip()
|
||
xiadan_xuzhi = request.data.get('xiadan_xuzhi', '').strip()
|
||
# 图片文件
|
||
image_file = request.FILES.get('file') # 前端上传时字段名为 'file'
|
||
|
||
# 必填字段校验
|
||
if not biaoqian:
|
||
return Response({'code': 400, 'msg': '商品标题不能为空'})
|
||
try:
|
||
jiage = Decimal(jiage_str)
|
||
if jiage <= 0:
|
||
return Response({'code': 400, 'msg': '价格必须大于0'})
|
||
except:
|
||
return Response({'code': 400, 'msg': '价格格式错误'})
|
||
try:
|
||
kucun = int(kucun_str)
|
||
if kucun < 0:
|
||
return Response({'code': 400, 'msg': '库存不能为负数'})
|
||
except:
|
||
return Response({'code': 400, 'msg': '库存格式错误'})
|
||
try:
|
||
duiwai_xiaoliang = int(duiwai_xiaoliang_str)
|
||
except:
|
||
duiwai_xiaoliang = 0
|
||
try:
|
||
leixing_id = int(leixing_id_str)
|
||
except:
|
||
return Response({'code': 400, 'msg': '商品类型ID无效'})
|
||
try:
|
||
zhuanqu_id = int(zhuanqu_id_str)
|
||
except:
|
||
return Response({'code': 400, 'msg': '商品专区ID无效'})
|
||
try:
|
||
yaoqiuleixing = int(yaoqiuleixing_str)
|
||
if yaoqiuleixing not in (1, 2):
|
||
raise ValueError
|
||
except:
|
||
return Response({'code': 400, 'msg': '抢单要求类型必须为1或2'})
|
||
|
||
# 校验类型和专区的存在性及关联性
|
||
try:
|
||
leixing_obj = ShangpinLeixing.query.get(id=leixing_id)
|
||
except ShangpinLeixing.DoesNotExist:
|
||
return Response({'code': 400, 'msg': '商品类型不存在'})
|
||
try:
|
||
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': '商品专区不存在'})
|
||
|
||
# 处理抢单要求相关字段
|
||
huiyuan_id_val = None
|
||
yongjin_val = None
|
||
if yaoqiuleixing == 1:
|
||
# 会员要求
|
||
if not huiyuan_id:
|
||
# 未传递时,尝试从商品类型中获取默认会员ID
|
||
huiyuan_id_val = leixing_obj.huiyuan_id
|
||
if not huiyuan_id_val:
|
||
return Response({'code': 400, 'msg': '未指定会员且商品类型无默认会员'})
|
||
else:
|
||
huiyuan_id_val = huiyuan_id
|
||
# 验证会员是否存在
|
||
if not Huiyuan.query.filter(huiyuan_id=huiyuan_id_val).exists():
|
||
return Response({'code': 400, 'msg': '指定的会员不存在'})
|
||
else:
|
||
# 佣金要求
|
||
try:
|
||
yongjin_val = Decimal(yongjin_str)
|
||
if yongjin_val < 0:
|
||
return Response({'code': 400, 'msg': '佣金不能为负数'})
|
||
except:
|
||
# 若未传递或无效,尝试从商品类型中获取默认佣金
|
||
if leixing_obj.yongjin is not None:
|
||
yongjin_val = leixing_obj.yongjin
|
||
else:
|
||
return Response({'code': 400, 'msg': '佣金格式错误且类型无默认佣金'})
|
||
|
||
# 处理打手分成方式
|
||
kaioi_ewai = False
|
||
ewai_fencheng = Decimal('0')
|
||
if fencheng_type == 'custom':
|
||
try:
|
||
ewai_fencheng = Decimal(ewai_PlayerCommission_str)
|
||
if ewai_fencheng < 0:
|
||
return Response({'code': 400, 'msg': '自定义分成金额不能为负数'})
|
||
kaioi_ewai = True
|
||
except:
|
||
return Response({'code': 400, 'msg': '自定义分成金额格式错误'})
|
||
|
||
# 图片上传处理
|
||
tupian_url = ''
|
||
if image_file:
|
||
# 验证图片
|
||
valid, msg = validate_image(image_file)
|
||
if not valid:
|
||
return Response({'code': 400, 'msg': f'图片无效:{msg}'})
|
||
# 生成唯一文件名
|
||
ext = image_file.name.split('.')[-1].lower()
|
||
if ext not in ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']:
|
||
return Response({'code': 400, 'msg': '不支持的图片格式'})
|
||
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
|
||
random_str = uuid.uuid4().hex[:8]
|
||
filename = f"shangpin/shangpintupian/{timestamp}_{random_str}.{ext}"
|
||
# 上传到 OSS
|
||
uploaded_url = upload_to_oss(image_file, filename)
|
||
if not uploaded_url:
|
||
return Response({'code': 500, 'msg': '图片上传失败,请稍后重试'})
|
||
tupian_url = filename # 存储相对路径,前端拼接域名
|
||
else:
|
||
return Response({'code': 400, 'msg': '商品图片不能为空'})
|
||
|
||
# 创建商品
|
||
try:
|
||
from jituan.services.club_write import resolve_club_id_for_write
|
||
club_id = resolve_club_id_for_write(request)
|
||
with transaction.atomic():
|
||
product = Shangpin.query.create(
|
||
biaoqian=biaoqian,
|
||
jiage=jiage,
|
||
kucun=kucun,
|
||
leixing_id=leixing_id,
|
||
zhuanqu_id=zhuanqu_id,
|
||
zhenshi_xiaoliang=0, # 初始真实销量为0
|
||
duiwai_xiaoliang=duiwai_xiaoliang,
|
||
jieshao=jieshao,
|
||
xiadan_xuzhi=xiadan_xuzhi,
|
||
tupian_url=tupian_url,
|
||
yaoqiuleixing=yaoqiuleixing,
|
||
huiyuan_id=huiyuan_id_val,
|
||
yongjin=yongjin_val,
|
||
kaioi_ewai_dashou_fencheng=kaioi_ewai,
|
||
ewai_dashou_fencheng=ewai_fencheng,
|
||
paixu=0, # 新商品默认排序最后
|
||
shenhezhuangtai=1, # 正常状态
|
||
club_id=club_id,
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"创建商品失败: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '创建商品失败,请稍后重试'})
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '商品发布成功',
|
||
'data': {
|
||
'id': product.id,
|
||
'tupian_url': tupian_url,
|
||
}
|
||
})
|
||
|
||
|
||
# houtai/views_product.py 中添加
|
||
|
||
class DeleteProductView(APIView):
|
||
"""
|
||
删除商品(物理删除,同时删除OSS图片)
|
||
权限:2200a
|
||
POST /houtai/htscsp
|
||
参数:
|
||
username: 客服/管理员账号
|
||
id: 商品ID
|
||
返回:
|
||
code:0 成功
|
||
"""
|
||
permission_classes = []
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
product_id = request.data.get('id')
|
||
|
||
if not username:
|
||
return Response({'code': 401, 'msg': '缺少username'})
|
||
if not product_id:
|
||
return Response({'code': 400, 'msg': '缺少商品ID'})
|
||
try:
|
||
product_id = int(product_id)
|
||
except:
|
||
return Response({'code': 400, 'msg': '商品ID格式错误'})
|
||
|
||
# 权限校验
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '2200a' not in permissions:
|
||
return Response({'code': 403, 'msg': '无权限管理商品'})
|
||
|
||
# 查询商品是否存在
|
||
try:
|
||
product = Shangpin.query.get(id=product_id)
|
||
except Shangpin.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '商品不存在'})
|
||
|
||
# 先删除OSS上的图片
|
||
tupian_url = product.tupian_url
|
||
if tupian_url:
|
||
# 调用已有的 delete_from_oss 函数
|
||
delete_success = delete_from_oss(tupian_url)
|
||
if not delete_success:
|
||
logger.warning(f"删除商品图片失败: {tupian_url}, 商品ID: {product_id}")
|
||
# 注意:即使图片删除失败,也应继续删除数据库记录,避免数据不一致
|
||
# 但可以记录日志供后续人工处理
|
||
|
||
# 删除数据库记录
|
||
product.delete()
|
||
|
||
logger.info(f"商品删除成功: id={product_id}, 图片路径={tupian_url}, 操作人={username}")
|
||
return Response({'code': 0, 'msg': '商品删除成功'})
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class GetProductDetailView(APIView):
|
||
"""
|
||
获取商品详情(包含商品类型、专区、会员列表)
|
||
权限:2200a
|
||
POST /houtai/htsphqxq
|
||
参数:
|
||
username: 客服/管理员账号
|
||
id: 商品ID
|
||
返回:
|
||
code:0, data: { product, type_list, zone_list, member_list }
|
||
"""
|
||
permission_classes = []
|
||
parser_classes = [JSONParser]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
product_id = request.data.get('id')
|
||
|
||
if not username:
|
||
return Response({'code': 401, 'msg': '缺少username'})
|
||
if not product_id:
|
||
return Response({'code': 400, 'msg': '缺少商品ID'})
|
||
try:
|
||
product_id = int(product_id)
|
||
except ValueError:
|
||
return Response({'code': 400, 'msg': '商品ID格式错误'})
|
||
|
||
# 权限校验
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '2200a' not in permissions:
|
||
return Response({'code': 403, 'msg': '无权限管理商品'})
|
||
|
||
# 查询商品
|
||
try:
|
||
product = Shangpin.query.get(id=product_id)
|
||
except Shangpin.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '商品不存在'})
|
||
|
||
# 查询所有商品类型、专区、会员(用于前端下拉选择)
|
||
type_list = list(ShangpinLeixing.query.all().values(
|
||
'id', 'jieshao', 'tupian_url', 'shenhezhuangtai'
|
||
))
|
||
zone_list = list(ShangpinZhuanqu.query.all().values(
|
||
'id', 'mingzi', 'leixing_id', 'shenhezhuangtai'
|
||
))
|
||
member_list = list(Huiyuan.query.all().values(
|
||
'huiyuan_id', 'jieshao', 'jiage'
|
||
))
|
||
|
||
# 构建商品返回数据
|
||
product_data = {
|
||
'id': product.id,
|
||
'biaoqian': product.biaoqian,
|
||
'jiage': str(product.jiage),
|
||
'kucun': product.kucun,
|
||
'leixing_id': product.leixing_id,
|
||
'zhuanqu_id': product.zhuanqu_id,
|
||
'zhenshi_xiaoliang': product.zhenshi_xiaoliang,
|
||
'duiwai_xiaoliang': product.duiwai_xiaoliang,
|
||
'jieshao': product.jieshao,
|
||
'xiadan_xuzhi': product.xiadan_xuzhi,
|
||
'guize_tupian': product.guize_tupian,
|
||
'yaoqiuleixing': product.yaoqiuleixing,
|
||
'huiyuan_id': product.huiyuan_id,
|
||
'yongjin': str(product.yongjin) if product.yongjin is not None else None,
|
||
'tupian_url': product.tupian_url,
|
||
'kaioi_ewai_PlayerCommission': product.kaioi_ewai_dashou_fencheng,
|
||
'ewai_PlayerCommission': str(product.ewai_dashou_fencheng) if product.ewai_dashou_fencheng is not None else '0',
|
||
'CreateTime': product.CreateTime.isoformat() if product.CreateTime else None,
|
||
'shenhezhuangtai': product.shenhezhuangtai,
|
||
}
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'product': product_data,
|
||
'type_list': type_list,
|
||
'zone_list': zone_list,
|
||
'member_list': member_list,
|
||
}
|
||
})
|
||
|
||
|
||
|
||
class UpdateProductView(APIView):
|
||
"""
|
||
修改商品(支持商品图片和规则图片的更新)
|
||
权限:2200a
|
||
POST /houtai/bcspxg
|
||
"""
|
||
permission_classes = []
|
||
parser_classes = [MultiPartParser, FormParser]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username', '').strip()
|
||
product_id = request.data.get('id')
|
||
|
||
if not username:
|
||
return Response({'code': 401, 'msg': '缺少username'})
|
||
if not product_id:
|
||
return Response({'code': 400, 'msg': '缺少商品ID'})
|
||
try:
|
||
product_id = int(product_id)
|
||
except ValueError:
|
||
return Response({'code': 400, 'msg': '商品ID格式错误'})
|
||
|
||
# 权限校验
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return permissions
|
||
if '2200a' not in permissions:
|
||
return Response({'code': 403, 'msg': '无权限管理商品'})
|
||
|
||
# 使用事务,将 select_for_update 放在事务内
|
||
with transaction.atomic():
|
||
try:
|
||
product = Shangpin.objects.select_for_update().get(id=product_id)
|
||
except Shangpin.DoesNotExist:
|
||
return Response({'code': 404, 'msg': '商品不存在'})
|
||
|
||
# ---------- 1. 基础字段 ----------
|
||
biaoqian = request.data.get('biaoqian', '').strip()
|
||
if not biaoqian:
|
||
return Response({'code': 400, 'msg': '商品标题不能为空'})
|
||
|
||
try:
|
||
jiage = Decimal(str(request.data.get('jiage', 0)))
|
||
if jiage <= 0:
|
||
return Response({'code': 400, 'msg': '价格必须大于0'})
|
||
except:
|
||
return Response({'code': 400, 'msg': '价格格式错误'})
|
||
|
||
try:
|
||
kucun = int(request.data.get('kucun', 0))
|
||
if kucun < 0:
|
||
return Response({'code': 400, 'msg': '库存不能为负数'})
|
||
except:
|
||
return Response({'code': 400, 'msg': '库存格式错误'})
|
||
|
||
try:
|
||
duiwai_xiaoliang = int(request.data.get('duiwai_xiaoliang', 0))
|
||
except:
|
||
duiwai_xiaoliang = 0
|
||
|
||
try:
|
||
leixing_id = int(request.data.get('leixing_id'))
|
||
except:
|
||
return Response({'code': 400, 'msg': '商品类型ID无效'})
|
||
|
||
try:
|
||
zhuanqu_id = int(request.data.get('zhuanqu_id'))
|
||
except:
|
||
return Response({'code': 400, 'msg': '商品专区ID无效'})
|
||
|
||
try:
|
||
yaoqiuleixing = int(request.data.get('yaoqiuleixing', 1))
|
||
if yaoqiuleixing not in (1, 2):
|
||
raise ValueError
|
||
except:
|
||
return Response({'code': 400, 'msg': '抢单要求类型必须为1或2'})
|
||
|
||
try:
|
||
shenhezhuangtai = int(request.data.get('shenhezhuangtai', 1))
|
||
except:
|
||
shenhezhuangtai = 1
|
||
|
||
# 校验类型和专区存在性
|
||
if not ShangpinLeixing.query.filter(id=leixing_id).exists():
|
||
return Response({'code': 400, 'msg': '商品类型不存在'})
|
||
if not ShangpinZhuanqu.query.filter(id=zhuanqu_id, leixing_id=leixing_id).exists():
|
||
return Response({'code': 400, 'msg': '专区不存在或不属于该类型'})
|
||
|
||
# ---------- 2. 会员/佣金 ----------
|
||
huiyuan_id = None
|
||
yongjin = None
|
||
if yaoqiuleixing == 1:
|
||
huiyuan_id = request.data.get('huiyuan_id', '').strip() or None
|
||
if huiyuan_id and not Huiyuan.query.filter(huiyuan_id=huiyuan_id).exists():
|
||
return Response({'code': 400, 'msg': '指定的会员不存在'})
|
||
else:
|
||
try:
|
||
yongjin = Decimal(str(request.data.get('yongjin', 0)))
|
||
if yongjin < 0:
|
||
return Response({'code': 400, 'msg': '佣金不能为负数'})
|
||
except:
|
||
yongjin = Decimal('0')
|
||
|
||
# ---------- 3. 打手分成 ----------
|
||
kaioi_ewai = request.data.get('kaioi_ewai_PlayerCommission') == 'true'
|
||
ewai_fencheng = Decimal('0')
|
||
if kaioi_ewai:
|
||
try:
|
||
ewai_fencheng = Decimal(str(request.data.get('ewai_PlayerCommission', 0)))
|
||
if ewai_fencheng < 0:
|
||
return Response({'code': 400, 'msg': '分成金额不能为负数'})
|
||
except:
|
||
return Response({'code': 400, 'msg': '分成金额格式错误'})
|
||
|
||
jieshao = request.data.get('jieshao', '').strip()
|
||
xiadan_xuzhi = request.data.get('xiadan_xuzhi', '').strip()
|
||
|
||
# ---------- 4. 商品图片更新 ----------
|
||
tupian_url = product.tupian_url
|
||
tupian_file = request.FILES.get('tupian_file')
|
||
if tupian_file:
|
||
valid, msg = validate_image(tupian_file)
|
||
if not valid:
|
||
return Response({'code': 400, 'msg': f'商品图片无效:{msg}'})
|
||
ext = tupian_file.name.split('.')[-1].lower()
|
||
if ext not in ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']:
|
||
return Response({'code': 400, 'msg': '不支持的图片格式'})
|
||
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
|
||
random_str = uuid.uuid4().hex[:8]
|
||
new_filename = f"shangpin/shangpintupian/{timestamp}_{random_str}.{ext}"
|
||
uploaded_url = upload_to_oss(tupian_file, new_filename)
|
||
if not uploaded_url:
|
||
return Response({'code': 500, 'msg': '商品图片上传失败'})
|
||
if product.tupian_url:
|
||
delete_from_oss(product.tupian_url)
|
||
tupian_url = new_filename
|
||
|
||
# ---------- 5. 规则图片更新 ----------
|
||
guize_tupian = product.guize_tupian
|
||
guize_file = request.FILES.get('guize_file')
|
||
if guize_file:
|
||
valid, msg = validate_image(guize_file)
|
||
if not valid:
|
||
return Response({'code': 400, 'msg': f'规则图片无效:{msg}'})
|
||
ext = guize_file.name.split('.')[-1].lower()
|
||
if ext not in ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']:
|
||
return Response({'code': 400, 'msg': '不支持的图片格式'})
|
||
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
|
||
random_str = uuid.uuid4().hex[:8]
|
||
new_filename = f"shangpin/guizetupian/{timestamp}_{random_str}.{ext}"
|
||
uploaded_url = upload_to_oss(guize_file, new_filename)
|
||
if not uploaded_url:
|
||
return Response({'code': 500, 'msg': '规则图片上传失败'})
|
||
if product.guize_tupian:
|
||
delete_from_oss(product.guize_tupian)
|
||
guize_tupian = new_filename
|
||
|
||
# ---------- 6. 更新数据库 ----------
|
||
product.biaoqian = biaoqian
|
||
product.jiage = jiage
|
||
product.kucun = kucun
|
||
product.duiwai_xiaoliang = duiwai_xiaoliang
|
||
product.leixing_id = leixing_id
|
||
product.zhuanqu_id = zhuanqu_id
|
||
product.yaoqiuleixing = yaoqiuleixing
|
||
product.huiyuan_id = huiyuan_id
|
||
product.yongjin = yongjin
|
||
product.kaioi_ewai_dashou_fencheng = kaioi_ewai
|
||
product.ewai_dashou_fencheng = ewai_fencheng
|
||
product.jieshao = jieshao
|
||
product.xiadan_xuzhi = xiadan_xuzhi
|
||
product.tupian_url = tupian_url
|
||
product.guize_tupian = guize_tupian
|
||
product.shenhezhuangtai = shenhezhuangtai
|
||
product.save()
|
||
|
||
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': '服务器错误'})
|
||
|
||
|