进行了完整的视图拆分,同步了 merchant_ops 的修改

This commit is contained in:
2026-07-05 10:01:09 +08:00
parent 9d98ac9e83
commit c5ae30507b
46 changed files with 32768 additions and 188172 deletions

File diff suppressed because it is too large Load Diff

128
users/views/__init__.py Normal file
View File

@@ -0,0 +1,128 @@
"""users.views package aggregate entry - re-export all submodule symbols.
Preserves external `from users.views import X` for users/urls.py and jituan/urls.py.
"""
from .auth import (
DashouZhuceView,
GuanshiRegisterView,
GuanshiYaoqingDashouListView,
WechatLoginAndDashouRegisterView,
WechatLoginAndGuanshiRegisterView,
WechatMiniProgramLoginView,
WeixinOfficialCallbackView,
ZuzhangXinxiView,
ZuzhangZhuceView,
)
from .user_info import (
BossBiaoshiView,
DashouGengxinView,
DashouJianquanView,
DashouXinxiAPIView,
DashouZiliaoHuoquView,
GuanshiXinxiView,
HuoQuYaoQingRenView,
LaobanJianquanView,
ShangJiaXinXiView,
ShangjiaNichengGengxinView,
UserInfoUpdateView,
YaoqingmaView,
ZaixianZhuangtaiAPIView,
huoquKehuduanIP,
huoquYonghuDaili,
)
from .shangjia import (
DaShouFaKuanLieBiaoView,
FaKuanJiFenTongJiView,
FaKuanShenSuView,
ShangjiaCufaTongjiView,
ShangjiaFakuanLieBiaoView,
ShangjiaZhuceView,
)
from .tixian import (
FORBIDDEN_STATUS_FOR_WITHDRAW,
GuanshiContactView,
ORDER_STATUS_COMPLETED,
ORDER_STATUS_FAIL,
ORDER_STATUS_REFUNDED,
ShoukuanXinxiShangchuanView,
TixianAssetView,
TixianCallbackV3View,
TixianJiluHuoquViewV2,
TixianQueRenAutoView,
TixianShenqingV3View,
TixianShenqingView,
TixianXinxiHuoquView,
YonghuTixianShenheXiugaiView,
generate_tixian_id,
)
from .admin import (
AdGetOrderList,
AdGetOrderTypes,
AdGuanLiYongHu,
AdKfglView,
AdKftjView,
AdKfxgView,
AdTongYiChuFa,
AdYaoQingDaShou,
AdcjxgView,
AdckyhxqView,
AddtxshView,
AdminFinancialDataView,
AdminLoginView,
AdtixianqkView,
CfGuanLi,
PaihangbangGuanliQueryView,
PaihangbangRiqiliebiaoView,
admin_required,
)
from .chufa import (
ChufaJiluHuoquView,
DashouPermission,
DashouShensuView,
ShangjiaChufaJiluHuoquView,
)
from .kefu import (
KefuCancelDesignationView,
KefuChangeDashouView,
KefuForceCompleteView,
KefuGetDashouDetailView,
KefuGetDashouListView,
KefuGetOrderDetailView,
KefuGetOrderListView,
KefuGetOrderTypesView,
KefuGetShangjiaOrderListView,
KefuLoginView,
KefuMerchantRefundView,
KefuPlatformRefundView,
KefuPunishView,
KefuPunishmentActionView,
KefuPunishmentDetailView,
KefuPunishmentListView,
KefuRecoverOrderView,
KefuRejectRefundView,
KefuRejectSettlementView,
KefuStatsView,
KefuTransferHallView,
KefuUpdateDashouView,
KefuWithdrawActionView,
KefuWithdrawDetailView,
KefuWithdrawListView,
)
from .pay import (
FaKuanHuitiaoView,
FaKuanPayFailView,
FaKuanPayPollView,
FaKuanPayView,
KaohePayCallbackView,
KaohePayFailView,
KaohePayPollView,
KaohePayView,
)

2890
users/views/admin.py Normal file

File diff suppressed because it is too large Load Diff

1936
users/views/auth.py Normal file

File diff suppressed because it is too large Load Diff

830
users/views/chufa.py Normal file
View File

@@ -0,0 +1,830 @@
"""users.views.chufa - 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 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 DashouPermission:
"""
打手权限验证类
确保用户是打手且账号状态正常
"""
def has_permission(self, request, view):
user = request.user
# 检查用户是否已认证
if not user.is_authenticated:
return False
try:
# 通过反向关系获取打手扩展信息
dashou_profile = user.DashouProfile
# 检查账号状态是否为1正常
if dashou_profile.zhanghaozhuangtai != 1:
return False
return True
except UserDashou.DoesNotExist:
return False
except Exception as e:
logger.error(f"打手权限验证异常: {e}")
return False
class ChufaJiluHuoquView(APIView):
"""
打手获取处罚记录接口
路径: /yonghu/dshqcfjl
方法: POST
权限: JWT Token + 打手权限验证
"""
permission_classes = [IsAuthenticated, DashouPermission]
def post(self, request):
try:
# 获取当前用户信息
user_main = request.user
yonghuid = user_main.UserUID
# 通过反向关系获取打手扩展信息
try:
dashou_profile = user_main.DashouProfile
# 再次确认账号状态
if dashou_profile.zhanghaozhuangtai != 1:
return Response({
'code': 3,
'msg': '账号已被封禁,无法查看处罚记录',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
except UserDashou.DoesNotExist:
return Response({
'code': 2,
'msg': '用户不是打手身份',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 判断请求类型:统计信息 or 记录列表
qingqiu_tongji = request.data.get('qingqiu_tongji', False)
if qingqiu_tongji:
# 返回统计信息
return self.get_tongji_info(yonghuid)
else:
# 返回处罚记录列表
return self.get_chufa_list(yonghuid, request.data)
except Exception as e:
logger.error(f"获取处罚记录异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True)
return Response({
'code': 99,
'msg': '系统繁忙,请稍后重试',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def get_tongji_info(self, yonghuid):
"""
获取统计信息(总记录、待处理、已处理)
"""
try:
# 使用单个查询获取所有统计信息
stats = PenaltyRecord.query.filter(PlayerID=yonghuid).aggregate(
# 总记录数
zongshu=Count('id'),
# 待处理数状态为0待处罚和3申诉中
daichuli=Count(
Case(
When(Q(ApplyStatus=0) | Q(ApplyStatus=3), then=1),
output_field=IntegerField()
)
),
# 已处理数状态为1已处罚和2已驳回
yichuli=Count(
Case(
When(Q(ApplyStatus=1) | Q(ApplyStatus=2), then=1),
output_field=IntegerField()
)
)
)
# 如果查询结果为None则设置为0
tongji_data = {
'zongshu': stats.get('zongshu', 0) or 0,
'daichuli': stats.get('daichuli', 0) or 0,
'yichuli': stats.get('yichuli', 0) or 0
}
return Response({
'code': 0,
'msg': '获取统计信息成功',
'data': tongji_data
})
except Exception as e:
logger.error(f"获取统计信息异常 - 用户ID: {yonghuid}, 错误: {str(e)}")
return Response({
'code': 1,
'msg': '获取统计信息失败',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def get_chufa_list(self, yonghuid, request_data):
"""
获取处罚记录列表(高性能批量查询)
🔴 只修改图片查询逻辑,字段保持原样
"""
try:
# 获取分页参数
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', 0)
# 🔴 构建查询条件 - 打手端根据PlayerID查询
queryset = PenaltyRecord.query.filter(PlayerID=yonghuid)
if zhuangtai == 0:
# 待处理状态为0待处罚和3申诉中
queryset = queryset.filter(Q(ApplyStatus=0) | Q(ApplyStatus=3))
elif zhuangtai == 1:
# 已处理状态为1已处罚和2已驳回
queryset = queryset.filter(Q(ApplyStatus=1) | Q(ApplyStatus=2))
else:
# 默认返回所有状态
pass
# 按创建时间倒序排列(最新在前)
queryset = queryset.order_by('-CreateTime')
# 使用游标分页,避免传统分页的性能问题
paginator = Paginator(queryset, page_size)
try:
page_obj = paginator.page(page)
except:
# 页码超出范围,返回空列表
return Response({
'code': 0,
'msg': '获取成功',
'data': {
'list': [],
'has_more': False,
'page': page,
'page_size': page_size,
'current_count': 0
}
})
# 获取当前页的记录
chufa_records = list(page_obj)
if not chufa_records:
# 没有数据直接返回
return Response({
'code': 0,
'msg': '获取成功',
'data': {
'list': [],
'has_more': False,
'page': page,
'page_size': page_size,
'current_count': 0
}
})
# 🔴【核心修复】收集所有需要查询图片的组合
dingdan_ids = []
shangjia_ids = []
for record in chufa_records:
if record.OrderID:
dingdan_ids.append(record.OrderID)
if record.ApplicantID: # 商家ID
shangjia_ids.append(record.ApplicantID)
# 去重
dingdan_ids = list(set(dingdan_ids))
shangjia_ids = list(set(shangjia_ids))
# 🔴【核心修复】分别查询两种图片避免if-elif逻辑错误
# 1. 查询证据图片(商家上传的)
zhengju_mapping = {}
if dingdan_ids and shangjia_ids:
# 构建Q对象OrderID在列表中 AND UserID在商家ID列表中
zhengju_q = Q(OrderID__in=dingdan_ids, UserID__in=shangjia_ids)
zhengju_queryset = PenaltyEvidenceImage.query.filter(zhengju_q).values('OrderID', 'UserID', 'ImageURL')
for item in zhengju_queryset:
key = f"{item['OrderID']}_{item['UserID']}"
if key not in zhengju_mapping:
zhengju_mapping[key] = []
if item['ImageURL']:
zhengju_mapping[key].append(item['ImageURL'])
# 2. 查询申诉图片(打手上传的)
shensu_mapping = {}
if dingdan_ids:
# 构建Q对象OrderID在列表中 AND UserID=当前打手ID
shensu_q = Q(OrderID__in=dingdan_ids, UserID=yonghuid)
shensu_queryset = PenaltyEvidenceImage.query.filter(shensu_q).values('OrderID', 'UserID', 'ImageURL')
for item in shensu_queryset:
key = f"{item['OrderID']}_{item['UserID']}"
if key not in shensu_mapping:
shensu_mapping[key] = []
if item['ImageURL']:
shensu_mapping[key].append(item['ImageURL'])
# 🔴【核心修复】组装返回数据 - 字段保持原样!
chufa_list = []
for record in chufa_records:
dingdan_id = record.OrderID
shangjia_id = record.ApplicantID
# 获取证据图片(商家上传的)
zhengju_tupian = []
if dingdan_id and shangjia_id:
key = f"{dingdan_id}_{shangjia_id}"
zhengju_tupian = zhengju_mapping.get(key, [])
# 获取申诉图片(打手上传的)
shensu_tupian = []
if dingdan_id:
key = f"{dingdan_id}_{yonghuid}"
shensu_tupian = shensu_mapping.get(key, [])
# 🔴【字段保持原样!】与前端完全对应!
chufa_data = {
'id': record.id,
'dingdan_id': record.OrderID or '',
'qingqiuid': record.ApplicantID or '', # 商家ID
'cfliyou': record.PenaltyReason or '',
'sqzhuangtai': record.ApplyStatus or 0,
'ssliyou': record.AppealReason or '',
'jifen': record.DeductedPoints or 0,
'CreateTime': record.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if record.CreateTime else '',
'UpdateTime': record.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if record.UpdateTime else '',
'zhengju_tupian': zhengju_tupian, # 商家上传的证据图片
'shensu_tupian': shensu_tupian, # 打手上传的申诉图片
}
chufa_list.append(chufa_data)
# 判断是否还有更多数据
has_more = page_obj.has_next()
# 构建响应数据
response_data = {
'list': chufa_list,
'has_more': has_more,
'page': page,
'page_size': page_size,
'current_count': len(chufa_list)
}
return Response({
'code': 0,
'msg': '获取成功',
'data': response_data
})
except ValueError as e:
logger.error(f"参数错误 - 用户ID: {yonghuid}, 错误: {str(e)}")
return Response({
'code': 4,
'msg': '参数格式错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.error(f"获取处罚记录列表异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True)
return Response({
'code': 5,
'msg': '获取记录列表失败',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class DashouShensuView(APIView):
"""
打手申诉接口
路径: /yonghu/dscfss
方法: POST
权限: JWT Token + 打手权限验证
功能: 打手对处罚进行申诉
"""
permission_classes = [IsAuthenticated, DashouPermission]
def post(self, request):
try:
# 获取当前用户信息
user_main = request.user
yonghuid = user_main.UserUID
# 🔴🆕【关键】验证打手账号状态
try:
dashou_profile = user_main.DashouProfile
if dashou_profile.zhanghaozhuangtai != 1:
return Response({
'code': 3,
'msg': '账号已被封禁,无法申诉',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
except UserDashou.DoesNotExist:
return Response({
'code': 2,
'msg': '用户不是打手身份',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 🔴🆕【关键】获取请求参数
chufa_id = request.data.get('chufa_id') # 处罚记录ID
shensu_liyou = request.data.get('shensu_liyou', '').strip() # 申诉理由
shensu_tupian_urls = request.data.get('shensu_tupian_urls', []) # 申诉图片URL数组
# 验证必填字段
if not chufa_id:
return Response({
'code': 6,
'msg': '处罚记录ID不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
if not shensu_liyou:
return Response({
'code': 7,
'msg': '申诉理由不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证申诉理由长度
if len(shensu_liyou) > 500:
return Response({
'code': 8,
'msg': '申诉理由不能超过500字',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 验证图片数量
if len(shensu_tupian_urls) > 9:
return Response({
'code': 9,
'msg': '最多只能上传9张图片',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 🔴🆕【关键】使用事务确保数据一致性
with transaction.atomic():
# 1. 查询处罚记录
try:
chufa_record = PenaltyRecord.objects.select_for_update().get(
id=chufa_id,
PlayerID=yonghuid # 确保只能申诉自己的处罚记录
)
except PenaltyRecord.DoesNotExist:
return Response({
'code': 10,
'msg': '处罚记录不存在或无权申诉',
'data': None
}, status=status.HTTP_404_NOT_FOUND)
# 2. 验证处罚记录状态必须是待处理状态0
if chufa_record.ApplyStatus != 0:
return Response({
'code': 11,
'msg': '该处罚记录已处理,无法申诉',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 验证订单是否存在(可选,根据需求决定)
# 这里可以根据OrderID去查询订单表但根据需求描述我们已经有了处罚记录
# 处罚记录中包含了OrderID如果OrderID不存在说明订单可能被删除
# 但根据业务逻辑,有处罚记录就应该有订单,所以这里可以跳过这个检查
# 4. 检查是否已有申诉记录(防止重复申诉)
if chufa_record.AppealReason or chufa_record.ApplyStatus == 3:
# 检查是否已经有申诉图片
existing_shensu_tupian = PenaltyEvidenceImage.query.filter(
OrderID=chufa_record.OrderID,
UserID=yonghuid
).exists()
if existing_shensu_tupian:
return Response({
'code': 12,
'msg': '您已经提交过申诉,请等待处理结果',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 5. 更新处罚记录
chufa_record.AppealReason = shensu_liyou
chufa_record.ApplyStatus = 3 # 状态改为申诉中
chufa_record.save()
# 6. 保存申诉图片(如果有)
saved_tupian_urls = []
if shensu_tupian_urls:
for tupian_url in shensu_tupian_urls:
if tupian_url: # 确保URL不为空
chufa_tupian = PenaltyEvidenceImage.query.create(
OrderID=chufa_record.OrderID,
UserID=yonghuid,
ImageURL=tupian_url
)
saved_tupian_urls.append(tupian_url)
# 7. 构建返回数据
response_data = {
'chufa_id': chufa_record.id,
'dingdan_id': chufa_record.OrderID or '',
'shensu_liyou': shensu_liyou,
'shensu_tupian_urls': saved_tupian_urls,
'sqzhuangtai': chufa_record.ApplyStatus,
'UpdateTime': chufa_record.UpdateTime.strftime(
'%Y-%m-%d %H:%M:%S') if chufa_record.UpdateTime else ''
}
# 记录操作日志
logger.info(f"打手申诉成功 - 用户ID: {yonghuid}, 处罚ID: {chufa_id}, 订单ID: {chufa_record.OrderID}")
return Response({
'code': 0,
'msg': '申诉提交成功',
'data': response_data
})
except Exception as e:
logger.error(f"打手申诉异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True)
return Response({
'code': 99,
'msg': '系统繁忙,请稍后重试',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ShangjiaChufaJiluHuoquView(APIView):
"""
商家获取处罚记录接口
路径: /yonghu/sjcfjlhq
方法: POST
权限: JWT Token + 商家权限验证
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 获取当前用户信息
user_main = request.user
yonghuid = user_main.UserUID
# 通过反向关系获取商家扩展信息
try:
shangjia_profile = user_main.ShopProfile
# 再次确认账号状态
if shangjia_profile.zhuangtai != 1:
return Response({
'code': 3,
'msg': '商家账号状态异常,无法查看处罚记录',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
except UserShangjia.DoesNotExist:
return Response({
'code': 2,
'msg': '用户不是商家身份',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 判断请求类型
qingqiu_tongji = request.data.get('qingqiu_tongji', False)
if qingqiu_tongji:
return self.get_tongji_info(yonghuid)
else:
return self.get_chufa_list(yonghuid, request.data)
except Exception as e:
logger.error(f"商家获取处罚记录异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True)
return Response({
'code': 99,
'msg': '系统繁忙,请稍后重试',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def get_tongji_info(self, yonghuid):
"""
获取统计信息
"""
try:
# 🔴 查询条件ApplicantID = 当前商家ID
stats = PenaltyRecord.query.filter(ApplicantID=yonghuid).aggregate(
zongshu=Count('id'),
daichuli=Count(
Case(
When(Q(ApplyStatus=0) | Q(ApplyStatus=3), then=1),
output_field=IntegerField()
)
),
yichuli=Count(
Case(
When(Q(ApplyStatus=1) | Q(ApplyStatus=2), then=1),
output_field=IntegerField()
)
)
)
tongji_data = {
'zongshu': stats.get('zongshu', 0) or 0,
'daichuli': stats.get('daichuli', 0) or 0,
'yichuli': stats.get('yichuli', 0) or 0
}
return Response({
'code': 0,
'msg': '获取统计信息成功',
'data': tongji_data
})
except Exception as e:
logger.error(f"商家获取统计信息异常 - 用户ID: {yonghuid}, 错误: {str(e)}")
return Response({
'code': 1,
'msg': '获取统计信息失败',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def get_chufa_list(self, yonghuid, request_data):
"""
获取处罚记录列表(商家端)
🔴 只修改图片查询逻辑,字段保持原样
"""
try:
# 获取分页参数
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', 0)
# 🔴 查询条件ApplicantID = 当前商家ID
queryset = PenaltyRecord.query.filter(ApplicantID=yonghuid)
if zhuangtai == 0:
queryset = queryset.filter(Q(ApplyStatus=0) | Q(ApplyStatus=3))
elif zhuangtai == 1:
queryset = queryset.filter(Q(ApplyStatus=1) | Q(ApplyStatus=2))
else:
pass
# 按创建时间倒序排列
queryset = queryset.order_by('-CreateTime')
# 分页逻辑
paginator = Paginator(queryset, page_size)
try:
page_obj = paginator.page(page)
except:
return Response({
'code': 0,
'msg': '获取成功',
'data': {
'list': [],
'has_more': False,
'page': page,
'page_size': page_size,
'current_count': 0
}
})
chufa_records = list(page_obj)
if not chufa_records:
return Response({
'code': 0,
'msg': '获取成功',
'data': {
'list': [],
'has_more': False,
'page': page,
'page_size': page_size,
'current_count': 0
}
})
# 🔴【核心修复】收集所有需要查询图片的组合
dingdan_ids = []
dashou_ids = []
for record in chufa_records:
if record.OrderID:
dingdan_ids.append(record.OrderID)
if record.PlayerID: # 打手ID
dashou_ids.append(record.PlayerID)
# 去重
dingdan_ids = list(set(dingdan_ids))
dashou_ids = list(set(dashou_ids))
# 🔴【核心修复】分别查询两种图片避免if-elif逻辑错误
# 1. 查询证据图片(商家自己上传的)
zhengju_mapping = {}
if dingdan_ids:
# 构建Q对象OrderID在列表中 AND UserID=当前商家ID
zhengju_q = Q(OrderID__in=dingdan_ids, UserID=yonghuid)
zhengju_queryset = PenaltyEvidenceImage.query.filter(zhengju_q).values('OrderID', 'UserID', 'ImageURL')
for item in zhengju_queryset:
key = f"{item['OrderID']}_{item['UserID']}"
if key not in zhengju_mapping:
zhengju_mapping[key] = []
if item['ImageURL']:
zhengju_mapping[key].append(item['ImageURL'])
# 2. 查询申诉图片(打手上传的)
shensu_mapping = {}
if dingdan_ids and dashou_ids:
# 构建Q对象OrderID在列表中 AND UserID在打手ID列表中
shensu_q = Q(OrderID__in=dingdan_ids, UserID__in=dashou_ids)
shensu_queryset = PenaltyEvidenceImage.query.filter(shensu_q).values('OrderID', 'UserID', 'ImageURL')
for item in shensu_queryset:
key = f"{item['OrderID']}_{item['UserID']}"
if key not in shensu_mapping:
shensu_mapping[key] = []
if item['ImageURL']:
shensu_mapping[key].append(item['ImageURL'])
# 🔴【核心修复】组装返回数据 - 字段保持原样!
chufa_list = []
for record in chufa_records:
dingdan_id = record.OrderID
dashou_id = record.PlayerID
# 获取证据图片(商家自己上传的)
zhengju_tupian = []
if dingdan_id:
key = f"{dingdan_id}_{yonghuid}"
zhengju_tupian = zhengju_mapping.get(key, [])
# 获取申诉图片(打手上传的)
shensu_tupian = []
if dingdan_id and dashou_id:
key = f"{dingdan_id}_{dashou_id}"
shensu_tupian = shensu_mapping.get(key, [])
# 🔴【字段保持原样!】与前端完全对应!
chufa_data = {
'id': record.id,
'dingdan_id': record.OrderID or '',
'qingqiuid': record.PlayerID or '', # 打手ID
'cfliyou': record.PenaltyReason or '',
'sqzhuangtai': record.ApplyStatus or 0,
'ssliyou': record.AppealReason or '',
'jifen': record.DeductedPoints or 0,
'CreateTime': record.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if record.CreateTime else '',
'UpdateTime': record.UpdateTime.strftime('%Y-%m-%d %H:%M:%S') if record.UpdateTime else '',
'zhengju_tupian': zhengju_tupian, # 商家自己上传的证据图片
'shensu_tupian': shensu_tupian, # 打手上传的申诉图片
}
chufa_list.append(chufa_data)
# 判断是否还有更多数据
has_more = page_obj.has_next()
response_data = {
'list': chufa_list,
'has_more': has_more,
'page': page,
'page_size': page_size,
'current_count': len(chufa_list)
}
return Response({
'code': 0,
'msg': '获取成功',
'data': response_data
})
except ValueError as e:
logger.error(f"商家端参数错误 - 用户ID: {yonghuid}, 错误: {str(e)}")
return Response({
'code': 4,
'msg': '参数格式错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.error(f"商家获取处罚记录列表异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True)
return Response({
'code': 5,
'msg': '获取记录列表失败',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

3794
users/views/kefu.py Normal file

File diff suppressed because it is too large Load Diff

1073
users/views/pay.py Normal file

File diff suppressed because it is too large Load Diff

734
users/views/shangjia.py Normal file
View File

@@ -0,0 +1,734 @@
"""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()
# 插入新上传的申诉图片
for url in tupian_urls:
PenaltyAppealImage.query.create(
Penalty=fadan,
PenalizedUserID=yonghuid,
ImageURL=url,
Purpose=2
)
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)

1418
users/views/tixian.py Normal file

File diff suppressed because it is too large Load Diff

1112
users/views/user_info.py Normal file

File diff suppressed because it is too large Load Diff