"""users.views.shangjia - auto-generated by split script.""" import os import sys import json import re import time import uuid import random import string import requests import hashlib import traceback import decimal import jwt import ipaddress import xmltodict from datetime import datetime, timedelta from decimal import Decimal import defusedxml.ElementTree as ET from django.conf import settings from django.db import models, transaction, IntegrityError, DatabaseError, connection from django.db.models import Q, F, Sum, Count, Case, When, IntegerField, Prefetch from django.core.cache import cache from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import get_object_or_404 from django.utils import timezone from django.http import HttpResponse from django.views import View from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.hashers import make_password from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import permissions, status from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.parsers import MultiPartParser, FormParser, JSONParser from rest_framework.throttling import AnonRateThrottle from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.authentication import JWTAuthentication from gvsdsdk.fluent import db, func, FQ from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss from utils.money import yuan_to_fen from utils.fadan_utils import check_fadan_qiangdan_eligible from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode from users.fadan_fenhong_utils import process_fadan_fenhong from orders.utils import ( update_daily_payout, settle_shangjia_order_guanshi_fenhong ) from backend.utils import ( update_dashou_daily_by_action, update_guanshi_daily_by_action, update_shangjia_daily, update_zuzhang_daily_by_action, verify_kefu_permission ) from rank.utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan from ..models import ( UserBoss, UserDashou, UserShangjia, UserGuanshi, UserZuzhang, UserKefu, AdminProfile, OfficialAccountUser, TixianAutoRecord, TixianShenheJilu, Tixianjilu, Xiugaijilu, RankingRecord ) from users.business_models import User from ..tixian_shenhe_services import ( load_audit_meta_map, refund_balance, sync_audit_and_jilu_status, mark_transfer_success, release_collect_quota_for_record, close_audit_wechat_failed, query_wechat_transfer_bill, check_shijidaozhang_within_limit, check_dashou_trial_blocks_commission_withdraw, WX_STATE_FAIL, WX_STATE_SUCCESS, WX_STATE_WAIT, WX_STATE_CANCELING, ) from ..tixian_shenhe_views import process_audit_collect from .chufa import DashouPermission from orders.models import ( Order, PlatformOrderExt, MerchantOrderExt, CommissionRate, PenaltyRecord, PenaltyEvidenceImage, RefundRecord, PlayerDeliveryImage, Penalty, PenaltyAppealImage ) from products.models import ( ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu ) from config.models import Qunpeizhi from backend.models import MerchantDailyStats from rank.models import ( KaohePayTemp, Chenghao, ShenheJilu, KaoheCishuFeiyong, YonghuChenghao ) from users.models import UserShenheguan from products.utils import update_shangpin_daily_stat from shop.utils import update_dianpu_daily_stat from rank.utils import create_shenhe_jilu_from_temp import logging logger = logging.getLogger(__name__) class ShangjiaZhuceView(APIView): """ 商家注册接口 (通过管理员邀请码) 访问路径: /api/yonghu/shangjiahuce/ 请求方法: POST 权限要求: JWT Token认证通过的用户 请求体: { "inviteCode": "管理员邀请码" } 返回字段说明: - code: 状态码(200成功,其他失败) - message: 提示信息 - data: 商家信息数据,包含以下字段(与前端商家页面完全一致): - nicheng: 商家昵称 - sjyue: 商家余额(字符串,保留两位小数) - fabu: 发布订单总数 - chengjiao: 成交订单总数 - tuikuan: 退款订单总数 - jinridingdan: 今日订单 - jinriliushui: 今日流水(字符串) - jinyuedingdan: 今月订单 - jinyueliushui: 今月流水(字符串) - zhuangtai: 商家状态(1正常) - dianhua: 商家电话 - wechat: 商家微信 """ permission_classes = [IsAuthenticated] def post(self, request): # 1. 获取并验证邀请码参数 yaoqingma = request.data.get('inviteCode') if not yaoqingma: return Response({ 'code': 400, 'message': '邀请码不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) yaoqingma = yaoqingma.strip() if len(yaoqingma) > 100: return Response({ 'code': 400, 'message': '邀请码长度超过限制', 'data': None }, status=status.HTTP_400_BAD_REQUEST) current_user = request.user # 2. 检查用户是否已是商家 try: shangjia_profile = current_user.ShopProfile # 用户已是商家,直接返回现有信息 return self.fanhuiXianyouShangjiaXinxi(shangjia_profile, current_user) except UserShangjia.DoesNotExist: # 用户不是商家,继续注册流程 pass # 2.5 子客服身份互斥:有效客服不可认证商家 from merchant_ops.services.authz import assert_no_active_staff_when_merchant_register try: assert_no_active_staff_when_merchant_register(current_user) except Exception as e: from merchant_ops.services.authz import StaffAuthError if isinstance(e, StaffAuthError): return Response({'code': 403, 'message': e.msg, 'data': None}, status=status.HTTP_403_FORBIDDEN) raise # 3. 根据邀请码查找对应的管理员 try: # 从管理员表查找邀请码 admin_profile = AdminProfile.query.select_related('user').get(yaoqingma=yaoqingma) except AdminProfile.DoesNotExist: return Response({ 'code': 404, 'message': '管理员邀请码无效或不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 4. 核心:在数据库事务中创建商家身份 try: with transaction.atomic(): # 4.1 查询老板扩展表获取老板昵称 boss_nickname = '普通商家' try: boss_profile = current_user.BossProfile if boss_profile.nickname and boss_profile.nickname.strip(): boss_nickname = boss_profile.nickname.strip() except UserBoss.DoesNotExist: # 老板扩展表不存在,使用默认值 pass # 4.2 创建商家扩展表 # 使用老板昵称作为商家昵称的基础 shangjia_nicheng = boss_nickname # 检查昵称是否已存在(安全考虑) if UserShangjia.query.filter(nicheng=shangjia_nicheng).exists(): # 如果存在,添加随机数确保唯一 shangjia_nicheng = f"{boss_nickname}{random.randint(1000, 9999)}" # 4.3 创建商家记录 shop_profile = UserShangjia.query.create( user=current_user, nicheng=shangjia_nicheng, zhuangtai=1, # 正常状态 yue=0.00, # 商家余额 fabu=0, # 发布订单总数 tuikuan=0, # 退款订单总数 chengjiao=0, # 成交订单总数 jinridingdan=0, # 今日订单 jinriliushui=0.00, # 今日流水 jinyuedingdan=0, # 今月订单 jinyueliushui=0.00, # 今月流水 dianhua='', # 电话 wechat='', # 微信 ) except Exception as e: # 事务会自动回滚 error_detail = traceback.format_exc() #print(f"商家注册错误详情: {error_detail}") return Response({ 'code': 500, 'message': f'商家注册过程中发生系统错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 5. 注册成功,返回商家信息 fanhui_data = self.zhuangbeiFanhuiShuju(shop_profile, current_user) return Response({ 'code': 200, 'message': '商家注册成功!', 'data': fanhui_data }) def fanhuiXianyouShangjiaXinxi(self, shangjia_profile, current_user): """处理已是商家的用户,返回其现有信息""" fanhui_data = self.zhuangbeiFanhuiShuju(shangjia_profile, current_user) return Response({ 'code': 200, 'message': '您已是商家,返回当前信息。', 'data': fanhui_data }) def zhuangbeiFanhuiShuju(self, shangjia_profile, current_user): """ 准备返回给前端的商家数据 字段名必须与前端商家页面完全一致 """ data = { # 🟢 商家基本信息 'nicheng': shangjia_profile.nicheng, # 商家昵称 'sjyue': format(shangjia_profile.yue, '.2f'), # 商家余额,保留两位小数 'fabu': shangjia_profile.fabu, # 发布订单总数 'chengjiao': shangjia_profile.chengjiao, # 成交订单总数 'tuikuan': shangjia_profile.tuikuan, # 退款订单总数 'jinridingdan': shangjia_profile.jinridingdan, # 今日订单 'jinriliushui': format(shangjia_profile.jinriliushui, '.2f'), # 今日流水 'jinyuedingdan': shangjia_profile.jinyuedingdan, # 今月订单 'jinyueliushui': format(shangjia_profile.jinyueliushui, '.2f'), # 今月流水 'zhuangtai': shangjia_profile.zhuangtai, # 商家状态 'dianhua': shangjia_profile.dianhua or '', # 电话 'wechat': shangjia_profile.wechat or '', # 微信 # 🟢 可能需要的主表信息 'uid': current_user.UserUID, # 用户ID } return data class FaKuanJiFenTongJiView(APIView): """ 获取罚款与积分处罚统计总览 路径: POST /yonghu/cffktjhq """ permission_classes = [IsAuthenticated, DashouPermission] def post(self, request): try: user_main = request.user yonghuid = user_main.UserUID # ================= 罚款统计 ================= fadan_stats = Penalty.query.filter(PenalizedUserID=yonghuid).aggregate( total=Count('id'), daijiaona=Count('id', filter=Q(Status=1)), shensuzhong=Count('id', filter=Q(Status=3)), pingtai_shenhe=Count('id', filter=Q(Status=5)), yijiaona=Count('id', filter=Q(Status=2)), yibohui=Count('id', filter=Q(Status=4)), ) # ================= 积分处罚统计 ================= jifen_stats = PenaltyRecord.query.filter(PlayerID=yonghuid).aggregate( total=Count('id'), daichuli=Count('id', filter=Q(ApplyStatus=0) | Q(ApplyStatus=3)), yichuli=Count('id', filter=Q(ApplyStatus=1) | Q(ApplyStatus=2)), # 细分 daichuli_0=Count('id', filter=Q(ApplyStatus=0)), shensuzhong_3=Count('id', filter=Q(ApplyStatus=3)), yichufa_1=Count('id', filter=Q(ApplyStatus=1)), yibohui_2=Count('id', filter=Q(ApplyStatus=2)), ) data = { "fakuan": { "total": fadan_stats['total'], "daijiaona": fadan_stats['daijiaona'], "shensuzhong": fadan_stats['shensuzhong'], "pingtai_shenhe": fadan_stats['pingtai_shenhe'], "yijiaona": fadan_stats['yijiaona'], "yibohui": fadan_stats['yibohui'], }, "jifen": { "total": jifen_stats['total'], "daichuli": jifen_stats['daichuli'], "yichuli": jifen_stats['yichuli'], "daichuli_0": jifen_stats['daichuli_0'], "shensuzhong_3": jifen_stats['shensuzhong_3'], "yichufa_1": jifen_stats['yichufa_1'], "yibohui_2": jifen_stats['yibohui_2'], } } return Response({'code': 0, 'msg': '获取统计成功', 'data': data}) except Exception as e: logger.error(f"获取统计异常 {yonghuid}: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙'}, status=500) class DaShouFaKuanLieBiaoView(APIView): """ 打手罚款列表接口 路径:POST /yonghu/dsfklbhq 参数: page: 页码(默认1) page_size: 每页条数(默认10,最大50) zhuangtai: 状态筛选(1待缴纳/2已缴纳/3申诉中/4申诉成功,0或不传表示全部) sousuo_dingdan_id: 订单号模糊搜索 返回:{ list: [{ id, beichufa_id, guanliandingdan_id, chufaliyou, fakuanjine, zhuangtai, yingxiang_qiangdan, shensuliyou, bohuiliyou, zhengju_tupian: [...], // 证据图片(yongtu=1) shensu_tupian: [...], // 申诉图片(yongtu=2) CreateTime, UpdateTime }], has_more, page, page_size } """ permission_classes = [IsAuthenticated, DashouPermission] def post(self, request): try: user_main = request.user yonghuid = user_main.UserUID # 获取分页参数 page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 10)) if page_size > 50: page_size = 50 if page_size < 1: page_size = 10 # 状态筛选 zhuangtai = request.data.get('zhuangtai') # 订单号搜索 sousuo_dingdan_id = request.data.get('sousuo_dingdan_id', '').strip() # 基础查询:当前用户的罚单 qs = Penalty.query.filter(PenalizedUserID=yonghuid) # 状态筛选 if zhuangtai is not None and int(zhuangtai) in [1, 2, 3, 4, 5]: qs = qs.filter(Status=int(zhuangtai)) # 订单号模糊搜索 if sousuo_dingdan_id: qs = qs.filter(RelatedOrderID__icontains=sousuo_dingdan_id) # 按创建时间倒序 qs = qs.order_by('-CreateTime') # 分页 paginator = Paginator(qs, page_size) try: page_obj = paginator.page(page) except Exception: return Response({ 'code': 0, 'msg': '获取成功', 'data': {'list': [], 'has_more': False, 'page': page, 'page_size': page_size} }) records = list(page_obj) fadan_ids = [r.id for r in records] # 批量获取图片,按用途分组 zhengju_map = {} # key: fadan_id, value: [url, ...] shensu_map = {} if fadan_ids: tupians = PenaltyAppealImage.query.filter( Penalty_id__in=fadan_ids ).values('Penalty_id', 'ImageURL', 'Purpose') for t in tupians: fid = t['Penalty_id'] url = t['ImageURL'] if t['Purpose'] == 1: zhengju_map.setdefault(fid, []).append(url) else: shensu_map.setdefault(fid, []).append(url) # 组装返回数据 chufa_list = [] for r in records: rid = r.id item = { 'id': rid, 'beichufa_id': r.PenalizedUserID, 'guanliandingdan_id': r.RelatedOrderID or '', 'chufaliyou': r.Reason or '', 'fakuanjine': str(r.FineAmount), 'zhuangtai': r.Status, 'yingxiang_qiangdan': r.AffectsGrabbing, 'shensuliyou': r.AppealReason or '', 'bohuiliyou': r.RejectReason or '', 'zhengju_tupian': zhengju_map.get(rid, []), # 证据图片 'shensu_tupian': shensu_map.get(rid, []), # 申诉图片 'CreateTime': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '', 'UpdateTime': r.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if r.UpdateTime else '', } chufa_list.append(item) has_more = page_obj.has_next() return Response({ 'code': 0, 'msg': '获取成功', 'data': { 'list': chufa_list, 'has_more': has_more, 'page': page, 'page_size': page_size, } }) except Exception as e: logger.error(f"获取罚款列表异常 {yonghuid if 'yonghuid' in locals() else ''}: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙'}, status=500) def _merchant_penalty_auth(user): """校验商家身份,返回 (yonghuid, None) 或 (None, error_response)。""" try: shangjia_profile = user.ShopProfile if shangjia_profile.zhuangtai != 1: return None, Response({ 'code': 3, 'msg': '商家账号状态异常', 'data': None }, status=status.HTTP_403_FORBIDDEN) except UserShangjia.DoesNotExist: return None, Response({ 'code': 2, 'msg': '用户不是商家身份', 'data': None }, status=status.HTTP_403_FORBIDDEN) return user.UserUID, None def _money_str(val): if val is None: return '0.00' return str(val) class ShangjiaCufaTongjiView(APIView): """ 商家端罚款+积分处罚综合统计(含金额汇总) 路径: POST /yonghu/sjcftjhq """ permission_classes = [IsAuthenticated] def post(self, request): try: yonghuid, err = _merchant_penalty_auth(request.user) if err: return err fadan_agg = Penalty.query.filter(ApplicantID=yonghuid).aggregate( total=Count('id'), total_jine=Sum('FineAmount'), daijiaona=Count('id', filter=Q(Status=1)), daijiaona_jine=Sum('FineAmount', filter=Q(Status=1)), yijiaona=Count('id', filter=Q(Status=2)), yijiaona_jine=Sum('FineAmount', filter=Q(Status=2)), shensuzhong=Count('id', filter=Q(Status=3)), shensuzhong_jine=Sum('FineAmount', filter=Q(Status=3)), pingtai_shenhe=Count('id', filter=Q(Status=5)), pingtai_shenhe_jine=Sum('FineAmount', filter=Q(Status=5)), yibohui=Count('id', filter=Q(Status=4)), yibohui_jine=Sum('FineAmount', filter=Q(Status=4)), shijidaozhang_jine=Sum( 'ApplicantBonusAmount', filter=Q(Status=2, BonusCredited=True), ), ) jifen_agg = PenaltyRecord.query.filter(ApplicantID=yonghuid).aggregate( total=Count('id'), total_jifen=Sum('DeductedPoints'), daichuli=Count('id', filter=Q(ApplyStatus=0) | Q(ApplyStatus=3)), daichuli_jifen=Sum( 'DeductedPoints', filter=Q(ApplyStatus=0) | Q(ApplyStatus=3), ), yichuli=Count('id', filter=Q(ApplyStatus=1) | Q(ApplyStatus=2)), yichuli_jifen=Sum( 'DeductedPoints', filter=Q(ApplyStatus=1) | Q(ApplyStatus=2), ), daichuli_0=Count('id', filter=Q(ApplyStatus=0)), shensuzhong_3=Count('id', filter=Q(ApplyStatus=3)), yichufa_1=Count('id', filter=Q(ApplyStatus=1)), yibohui_2=Count('id', filter=Q(ApplyStatus=2)), ) data = { 'fakuan': { 'total': fadan_agg['total'] or 0, 'total_jine': _money_str(fadan_agg['total_jine']), 'daijiaona': fadan_agg['daijiaona'] or 0, 'daijiaona_jine': _money_str(fadan_agg['daijiaona_jine']), 'yijiaona': fadan_agg['yijiaona'] or 0, 'yijiaona_jine': _money_str(fadan_agg['yijiaona_jine']), 'shensuzhong': fadan_agg['shensuzhong'] or 0, 'shensuzhong_jine': _money_str(fadan_agg['shensuzhong_jine']), 'pingtai_shenhe': fadan_agg['pingtai_shenhe'] or 0, 'pingtai_shenhe_jine': _money_str(fadan_agg['pingtai_shenhe_jine']), 'yibohui': fadan_agg['yibohui'] or 0, 'yibohui_jine': _money_str(fadan_agg['yibohui_jine']), 'shijidaozhang_jine': _money_str(fadan_agg['shijidaozhang_jine']), }, 'jifen': { 'total': jifen_agg['total'] or 0, 'total_jifen': jifen_agg['total_jifen'] or 0, 'daichuli': jifen_agg['daichuli'] or 0, 'daichuli_jifen': jifen_agg['daichuli_jifen'] or 0, 'yichuli': jifen_agg['yichuli'] or 0, 'yichuli_jifen': jifen_agg['yichuli_jifen'] or 0, 'daichuli_0': jifen_agg['daichuli_0'] or 0, 'shensuzhong_3': jifen_agg['shensuzhong_3'] or 0, 'yichufa_1': jifen_agg['yichufa_1'] or 0, 'yibohui_2': jifen_agg['yibohui_2'] or 0, }, } return Response({'code': 0, 'msg': '获取统计成功', 'data': data}) except Exception as e: logger.error(f"商家处罚统计异常: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙'}, status=500) class ShangjiaFakuanLieBiaoView(APIView): """ 商家端金额罚单列表 路径: POST /yonghu/sjfklbhq """ permission_classes = [IsAuthenticated] def post(self, request): try: yonghuid, err = _merchant_penalty_auth(request.user) if err: return err page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 10)) if page_size > 50: page_size = 50 if page_size < 1: page_size = 10 zhuangtai = request.data.get('zhuangtai') sousuo_dingdan_id = str(request.data.get('sousuo_dingdan_id', '')).strip() qs = Penalty.query.filter(ApplicantID=yonghuid) if zhuangtai is not None and int(zhuangtai) in [1, 2, 3, 4, 5]: qs = qs.filter(Status=int(zhuangtai)) if sousuo_dingdan_id: qs = qs.filter(RelatedOrderID__icontains=sousuo_dingdan_id) qs = qs.order_by('-CreateTime') paginator = Paginator(qs, page_size) try: page_obj = paginator.page(page) except Exception: return Response({ 'code': 0, 'msg': '获取成功', 'data': {'list': [], 'has_more': False, 'page': page, 'page_size': page_size}, }) records = list(page_obj) fadan_ids = [r.id for r in records] staff_ids = list({r.ShopStaffUserID for r in records if r.ShopStaffUserID}) staff_name_map = {} if staff_ids: from merchant_ops.models import MerchantStaffMember for m in MerchantStaffMember.query.filter( merchant_id=yonghuid, staff_user_id__in=staff_ids ): staff_name_map[m.staff_user_id] = m.display_name or m.staff_user_id zhengju_map = {} shensu_map = {} if fadan_ids: tupians = PenaltyAppealImage.query.filter(Penalty_id__in=fadan_ids).values( 'id', 'Penalty_id', 'ImageURL', 'Purpose' ) for t in tupians: fid = t['Penalty_id'] img = {'id': t['id'], 'url': t['ImageURL']} if t['Purpose'] == 1: zhengju_map.setdefault(fid, []).append(img) else: shensu_map.setdefault(fid, []).append(img) chufa_list = [] for r in records: rid = r.id chufa_list.append({ 'id': rid, 'beichufa_id': r.PenalizedUserID, 'guanliandingdan_id': r.RelatedOrderID or '', 'chufaliyou': r.Reason or '', 'fakuanjine': str(r.FineAmount), 'zhuangtai': r.Status, 'yingxiang_qiangdan': r.AffectsGrabbing, 'shensuliyou': r.AppealReason or '', 'bohuiliyou': r.RejectReason or '', 'applicant_bonus_amount': str(r.ApplicantBonusAmount or 0), 'applicant_bonus_rate': str(r.ApplicantBonusRate or 0), 'bonus_credited': bool(r.BonusCredited), 'shop_staff_user_id': r.ShopStaffUserID or '', 'staff_display_name': staff_name_map.get(r.ShopStaffUserID or '', ''), 'zhengju_tupian': zhengju_map.get(rid, []), 'shensu_tupian': shensu_map.get(rid, []), 'CreateTime': r.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if r.CreateTime else '', 'UpdateTime': r.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if r.UpdateTime else '', }) return Response({ 'code': 0, 'msg': '获取成功', 'data': { 'list': chufa_list, 'has_more': page_obj.has_next(), 'page': page, 'page_size': page_size, }, }) except Exception as e: logger.error(f"商家罚款列表异常: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙'}, status=500) class FaKuanShenSuView(APIView): """ 罚款申诉提交/修改接口 路径:POST /yonghu/fkss 参数: fadan_id: 罚单ID shensu_liyou: 申诉理由 shensu_tupian_urls: 图片相对URL数组(可空数组,但至少要有理由) 逻辑: - 仅待缴纳状态且无驳回理由的罚单可申诉 - 允许对已有申诉进行修改:先删除旧申诉图片(yongtu=2),再添加新图片 """ permission_classes = [IsAuthenticated, DashouPermission] def post(self, request): try: user_main = request.user yonghuid = user_main.UserUID fadan_id = request.data.get('fadan_id') shensu_liyou = request.data.get('shensu_liyou', '').strip() tupian_urls = request.data.get('shensu_tupian_urls', []) if not fadan_id: return Response({'code': 400, 'msg': '缺少罚款ID'}, status=400) if not shensu_liyou: return Response({'code': 400, 'msg': '请输入申诉理由'}, status=400) # 查询罚单,确保属于当前打手 try: fadan = Penalty.query.get(id=fadan_id, PenalizedUserID=yonghuid) except Penalty.DoesNotExist: return Response({'code': 404, 'msg': '罚单不存在或无权操作'}, status=404) # 待缴纳(1)或平台审核中(5)可申诉;平台审核中仅保存申诉内容,不改变状态 from utils.penalty_status import ( PENALTY_PENDING_PAY, PENALTY_APPEALING, PENALTY_PLATFORM_AUDIT, ) if fadan.Status not in (PENALTY_PENDING_PAY, PENALTY_PLATFORM_AUDIT): return Response({'code': 400, 'msg': '罚单状态不允许申诉'}, status=400) if fadan.Status == PENALTY_PENDING_PAY and fadan.RejectReason: return Response({'code': 400, 'msg': '该罚单申诉已被驳回,不可再次申诉'}, status=400) if not isinstance(tupian_urls, list): return Response({'code': 400, 'msg': '申诉图片格式错误'}, status=400) tupian_urls = [str(u).strip() for u in tupian_urls if str(u).strip()] if len(tupian_urls) > 9: return Response({'code': 400, 'msg': '申诉图片最多9张'}, status=400) if fadan.Status == PENALTY_PENDING_PAY: fadan.Status = PENALTY_APPEALING fadan.AppealReason = shensu_liyou fadan.save() # 删除旧的申诉图片(yongtu=2),仅删除打手自己上传的申诉图 PenaltyAppealImage.query.filter(Penalty=fadan, Purpose=2).delete() # 插入新上传的申诉图片 PenaltyAppealImage.objects.bulk_create([ PenaltyAppealImage( Penalty=fadan, PenalizedUserID=yonghuid, ImageURL=url, Purpose=2, ) for url in tupian_urls ]) return Response({'code': 0, 'msg': '申诉提交成功'}) except Exception as e: logger.error(f"罚款申诉异常 {yonghuid if 'yonghuid' in locals() else ''}: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙'}, status=500)