Files
Django/config/views/merchant_templates.py
2026-07-04 19:29:53 +08:00

487 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import io
import os
import re
import time
import random
import secrets
import hashlib
import threading
import urllib.parse
from decimal import Decimal, InvalidOperation
from django.conf import settings
from django.db import transaction, connection
from django.db.models import Q, F, Max, Prefetch
from gvsdsdk.fluent import db, func, FQ
from django.core.cache import cache
from django.core.paginator import Paginator
from django.utils import timezone
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, permissions
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.throttling import AnonRateThrottle, SimpleRateThrottle
from utils.oss_utils import upload_to_oss, delete_from_oss, validate_image
from utils.weixin_broadcast import WeixinBroadcastSender
from utils.weixin_token import get_weixin_mini_access_token, is_weixin_token_invalid
# from utils.ip_security import *
from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode
from backend.utils import update_shangjia_daily
from utils.chat_utils import subscribe_merchant_link_chat
from utils.pdd_order_validator import validate_pdd_order_id, validate_cn_mobile
from ..models import (
Gonggao, Lunbo, Tupianpeizhi, Qunpeizhi,
ShangjiaMoban, ShangjiaLianjie, PopupPage, PopupConfig, WithdrawConfig,
MiniappScriptScene, MiniappScriptAutoReply,
)
from users.models import (
AdminProfile, UserDashou, UserBoss,
UserShangjia, UserGuanshi, UserZuzhang
)
from users.business_models import User
from orders.models import CommissionRate, Order, MerchantOrderExt
from orders.notice_tasks import dingdan_guangbo
from rank.models import Chenghao, DingdanBiaoqian
from products.models import ShangpinLeixing, Huiyuangoumai
from ..serializers import PopupConfigSerializer
import traceback
import requests
import logging
logger = logging.getLogger(__name__)
class ShangjiaMobanListView(APIView):
"""
商家订单模板列表接口
POST /peizhi/sjddmblb
权限: JWT认证
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
user = request.user
yonghu_id = user.UserUID
leixing_id = request.data.get('shangpinTypeId')
if not leixing_id:
return Response({'code': 400, 'msg': '商品类型ID不能为空', 'data': {}}, status=400)
get_type = int(request.data.get('getType', 2))
page = int(request.data.get('page', 1))
page_size = int(request.data.get('pageSize', 50))
keyword = request.data.get('keyword', '')
min_price = request.data.get('minPrice', '')
label_id = request.data.get('labelId', None)
if label_id is not None:
try:
label_id = int(label_id)
except (ValueError, TypeError):
label_id = None
query = Q(UserID=yonghu_id, ProductTypeID=leixing_id)
if get_type == 1: # 搜索模式
if keyword:
keyword = str(keyword).strip()
try:
mid = int(keyword)
query &= Q(id=mid) | Q(TemplateDesc__icontains=keyword)
except ValueError:
query &= Q(TemplateDesc__icontains=keyword)
if min_price:
try:
query &= Q(Price__gte=Decimal(str(min_price)))
except:
pass
if label_id:
query &= Q(TitleID=label_id)
mobans = ShangjiaMoban.query.filter(query).order_by('-id')
formatted = []
for moban in mobans:
texiao_json = ''
label_name = ''
if moban.TitleID:
try:
ch = Chenghao.query.get(id=moban.TitleID)
label_name = ch.mingcheng
texiao_json = ch.texiao_miaoshu or ''
except Chenghao.DoesNotExist:
pass
formatted.append({
'mobanId': moban.id,
'shangpinTypeId': moban.ProductTypeID,
'jieshao': moban.TemplateDesc,
'jiage': str(moban.Price),
'fabushuliang': moban.PublishedCount,
'commissionEnabled': moban.CommissionGrabEnabled,
'commissionValue': str(moban.CommissionPrice) if moban.CommissionPrice is not None else None,
'labelId': moban.TitleID,
'labelName': label_name,
'texiaoJson': texiao_json,
'createTime': moban.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if moban.CreateTime else ''
})
return Response({'code': 200, 'msg': '搜索成功', 'data': {'list': formatted, 'total': len(formatted), 'hasMore': False}})
# 分页模式
queryset = ShangjiaMoban.query.filter(query).order_by('-id')
paginator = Paginator(queryset, page_size)
if page < 1 or page > paginator.num_pages:
return Response({'code': 400, 'msg': '页码超出范围', 'data': {}}, status=400)
current_page = paginator.page(page)
formatted = []
for moban in current_page.object_list:
texiao_json = ''
label_name = ''
if moban.TitleID:
try:
ch = Chenghao.query.get(id=moban.TitleID)
label_name = ch.mingcheng
texiao_json = ch.texiao_miaoshu or ''
except Chenghao.DoesNotExist:
pass
formatted.append({
'mobanId': moban.id,
'shangpinTypeId': moban.ProductTypeID,
'jieshao': moban.TemplateDesc,
'jiage': str(moban.Price),
'fabushuliang': moban.PublishedCount,
'commissionEnabled': moban.CommissionGrabEnabled,
'commissionValue': str(moban.CommissionPrice) if moban.CommissionPrice is not None else None,
'labelId': moban.TitleID,
'labelName': label_name,
'texiaoJson': texiao_json,
'createTime': moban.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if moban.CreateTime else ''
})
has_more = current_page.has_next()
if len(formatted) < page_size:
has_more = False
return Response({
'code': 200,
'msg': '获取成功',
'data': {
'list': formatted,
'total': paginator.count,
'hasMore': has_more,
'currentPage': page,
'pageSize': page_size,
'totalPages': paginator.num_pages
}
})
except Exception as e:
logger.error(f"获取模板列表失败: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误', 'data': {}}, status=500)
class ShangjiaTianjiaMobanView(APIView):
"""
商家添加订单模板接口
POST /peizhi/sjtjddmb
权限: JWT认证
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 1. 获取当前商家用户ID
user = request.user
yonghu_id = user.UserUID
# 2. 接收并清洗参数,避免类型错误
leixing_id = request.data.get('shangpinTypeId')
jieshao = request.data.get('jieshao', '').strip() if request.data.get('jieshao') else ''
jiage = request.data.get('jiage', '')
# 佣金相关
commission_enabled = request.data.get('commissionEnabled', False)
commission_value = request.data.get('commissionValue', None)
# 标签ID称号ID
label_id = request.data.get('labelId', None)
# 3. 必填验证
if not leixing_id or not jieshao or not jiage:
logger.warning(f"参数缺失 - leixing_id:{leixing_id}, jieshao:{jieshao}, jiage:{jiage}")
return Response({
'code': 400,
'msg': '缺少必填字段(订单类型、介绍、价格)',
'data': {}
}, status=400)
# 4. 价格格式验证
try:
jiage_decimal = Decimal(str(jiage))
if jiage_decimal <= 0:
return Response({'code': 400, 'msg': '价格必须大于0', 'data': {}}, status=400)
if jiage_decimal > 10000:
return Response({'code': 400, 'msg': '价格不能超过10000元', 'data': {}}, status=400)
except (InvalidOperation, ValueError, TypeError):
return Response({'code': 400, 'msg': '价格格式错误', 'data': {}}, status=400)
# 5. 介绍长度验证
if len(jieshao) > 100:
return Response({'code': 400, 'msg': '订单介绍不能超过100字', 'data': {}}, status=400)
# 6. 佣金验证(若启用)
CommissionGrabEnabled = False
CommissionPrice = None
if commission_enabled:
CommissionGrabEnabled = True
if not commission_value:
return Response({'code': 400, 'msg': '开启佣金时,佣金金额不能为空', 'data': {}}, status=400)
try:
comm_decimal = Decimal(str(commission_value))
if comm_decimal <= 0:
return Response({'code': 400, 'msg': '佣金必须大于0', 'data': {}}, status=400)
CommissionPrice = comm_decimal
except (InvalidOperation, ValueError, TypeError):
return Response({'code': 400, 'msg': '佣金格式错误', 'data': {}}, status=400)
# 7. 标签ID清洗前端可能传数字或字符串
cleaned_label_id = None
if label_id is not None and str(label_id).strip():
try:
cleaned_label_id = int(label_id)
except (ValueError, TypeError):
return Response({'code': 400, 'msg': '标签ID格式错误', 'data': {}}, status=400)
# 8. 验证商品类型是否存在且可用
try:
shangpin_obj = ShangpinLeixing.query.get(
id=leixing_id,
shenhezhuangtai=1 # 审核通过
)
except ShangpinLeixing.DoesNotExist:
return Response({'code': 404, 'msg': '商品类型不存在或已禁用', 'data': {}}, status=404)
# 9. 检查同一用户、同一类型下是否已存在相同介绍的模板
if ShangjiaMoban.query.filter(
UserID=yonghu_id,
ProductTypeID=leixing_id,
TemplateDesc=jieshao
).exists():
return Response({'code': 400, 'msg': '已存在相同介绍的模板', 'data': {}}, status=400)
# 10. 执行数据库创建(事务)
with transaction.atomic():
moban = ShangjiaMoban.query.create(
UserID=yonghu_id,
ProductTypeID=leixing_id,
TemplateDesc=jieshao,
Price=jiage_decimal,
PublishedCount=0,
CommissionGrabEnabled=CommissionGrabEnabled,
CommissionPrice=CommissionPrice,
TitleID=cleaned_label_id
)
# 11. 返回成功数据(字段与前端对齐)
return Response({
'code': 200,
'msg': '模板创建成功',
'data': {
'mobanId': moban.id,
'shangpinTypeId': leixing_id,
'jieshao': moban.TemplateDesc,
'jiage': str(moban.Price),
'fabushuliang': moban.PublishedCount,
'createTime': moban.CreateTime.strftime('%Y-%m-%d %H:%M:%S'),
'commissionEnabled': moban.CommissionGrabEnabled,
'commissionValue': str(moban.CommissionPrice) if moban.CommissionPrice is not None else None,
'labelId': moban.TitleID
}
})
except Exception as e:
# 打印完整错误堆栈到日志
logger.error(f"添加模板失败: {str(e)}", exc_info=True)
return Response({
'code': 500,
'msg': '服务器内部错误,添加模板失败,请联系管理员',
'data': {}
}, status=500)
class ShangjiaShanchuMobanView(APIView):
"""
删除模板
POST /peizhi/sjddmbsc
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
user = request.user
yonghu_id = user.UserUID
moban_id = request.data.get('mobanId')
leixing_id = request.data.get('shangpinTypeId')
if not all([moban_id, leixing_id]):
return Response({'code': 400, 'msg': '参数缺失', 'data': {}}, status=400)
try:
moban = ShangjiaMoban.query.get(id=moban_id, UserID=yonghu_id, ProductTypeID=leixing_id)
except ShangjiaMoban.DoesNotExist:
return Response({'code': 404, 'msg': '模板不存在', 'data': {}}, status=404)
# 强制硬删除
moban.delete()
return Response({'code': 200, 'msg': '删除成功', 'data': {'mobanId': moban_id, 'shangpinTypeId': leixing_id}})
except Exception as e:
logger.error(f"删除模板失败: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误', 'data': {}}, status=500)
class ShangjiaGengxinMobanView(APIView):
"""
商家更新订单模板接口
POST /peizhi/sjddmbgx
权限: JWT认证
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
user = request.user
yonghu_id = user.UserUID
moban_id = request.data.get('mobanId')
leixing_id = request.data.get('shangpinTypeId')
new_jieshao = request.data.get('jieshao', '').strip()
new_jiage = request.data.get('jiage', '').strip()
# 新增字段
commission_enabled = request.data.get('commissionEnabled')
commission_value = request.data.get('commissionValue')
label_id = request.data.get('labelId')
if not all([moban_id, leixing_id]):
return Response({'code': 400, 'msg': '模板ID和商品类型ID不能为空', 'data': {}}, status=400)
# 查询模板
try:
moban = ShangjiaMoban.query.get(
id=moban_id,
UserID=yonghu_id,
ProductTypeID=leixing_id
)
except ShangjiaMoban.DoesNotExist:
return Response({'code': 404, 'msg': '模板不存在或无权修改', 'data': {}}, status=404)
update_fields = []
# 修改介绍
if new_jieshao:
if len(new_jieshao) > 100:
return Response({'code': 400, 'msg': '订单介绍不能超过100字', 'data': {}}, status=400)
if new_jieshao != moban.TemplateDesc:
moban.TemplateDesc = new_jieshao
update_fields.append('TemplateDesc')
# 修改价格
if new_jiage:
try:
jiage_decimal = Decimal(new_jiage)
if jiage_decimal <= 0 or jiage_decimal > 10000:
return Response({'code': 400, 'msg': '价格必须在0.1-10000元之间', 'data': {}}, status=400)
if jiage_decimal != moban.Price:
moban.Price = jiage_decimal
update_fields.append('Price')
except (InvalidOperation, ValueError, TypeError):
return Response({'code': 400, 'msg': '价格格式错误', 'data': {}}, status=400)
# 修改标签
if label_id is not None: # 允许传空字符串或不传传0表示清空
new_label = int(label_id) if label_id else None
if new_label != moban.TitleID:
moban.TitleID = new_label
update_fields.append('TitleID')
# 修改佣金设置
if commission_enabled is not None:
enabled = bool(commission_enabled)
if enabled:
if not commission_value:
return Response({'code': 400, 'msg': '佣金价格不能为空', 'data': {}}, status=400)
try:
comm_decimal = Decimal(commission_value)
if comm_decimal < 0.1 or comm_decimal > 50:
return Response({'code': 400, 'msg': '佣金需在0.1-50元之间', 'data': {}}, status=400)
if moban.CommissionGrabEnabled != True or moban.CommissionPrice != comm_decimal:
moban.CommissionGrabEnabled = True
moban.CommissionPrice = comm_decimal
update_fields.extend(['CommissionGrabEnabled', 'CommissionPrice'])
except (InvalidOperation, ValueError, TypeError):
return Response({'code': 400, 'msg': '佣金格式错误', 'data': {}}, status=400)
else:
if moban.CommissionGrabEnabled != False or moban.CommissionPrice is not None:
moban.CommissionGrabEnabled = False
moban.CommissionPrice = None
update_fields.extend(['CommissionGrabEnabled', 'CommissionPrice'])
if not update_fields:
return Response({'code': 400, 'msg': '没有修改内容', 'data': {}}, status=400)
# 检查介绍唯一性
if 'TemplateDesc' in update_fields:
if ShangjiaMoban.query.filter(
UserID=yonghu_id,
ProductTypeID=leixing_id,
TemplateDesc=moban.TemplateDesc
).exclude(id=moban_id).exists():
return Response({'code': 400, 'msg': '已存在相同介绍的模板', 'data': {}}, status=400)
with transaction.atomic():
moban.save(update_fields=update_fields)
# 返回最新数据
return Response({
'code': 200,
'msg': '模板修改成功',
'data': {
'mobanId': moban.id,
'shangpinTypeId': moban.ProductTypeID,
'jieshao': moban.TemplateDesc,
'jiage': str(moban.Price),
'fabushuliang': moban.PublishedCount,
'commissionEnabled': moban.CommissionGrabEnabled,
'commissionValue': str(moban.CommissionPrice) if moban.CommissionPrice else None,
'labelId': moban.TitleID,
'updateTime': moban.UpdateTime.strftime('%Y-%m-%d %H:%M:%S')
}
})
except Exception as e:
logger = logging.getLogger(__name__)
logger.error(f"修改商家模板失败: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误', 'data': {}}, status=500)