diff --git a/.gitignore b/.gitignore
index 4524e33..af3e880 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@ db.sqlite3
/venv/
/env/
.env
-_secrets.py
+app_secrets.py
staticfiles/
media/
*.log
diff --git a/_secrets.py b/_secrets.py
deleted file mode 100644
index 51f2992..0000000
--- a/_secrets.py
+++ /dev/null
@@ -1,73 +0,0 @@
-"""
-阿龙电竞 - 密钥与敏感配置文件
-此文件包含所有敏感信息,不应提交到版本控制。
-部署时请根据实际环境修改对应值。
-"""
-
-# ==================== Django 核心 ====================
-SECRET_KEY = 'django-insecure-9+xu)m%#!#qblgby5sq!sjyg5nj9lir78+&*f$28$459#yktej'
-
-# ==================== 数据库 ====================
-DATABASE_NAME = 'xaio_cheng_xu'
-DATABASE_USER = 'root'
-DATABASE_PASSWORD = 'sajksh.sdfGH3YUge.wjkd+'
-DATABASE_HOST = '172.17.0.15'
-DATABASE_PORT = '3306'
-
-# ==================== Redis ====================
-REDIS_HOST = '172.19.0.2'
-REDIS_PORT = 6379
-REDIS_PASSWORD = 'Dujieduze5.3'
-REDIS_DB_CELERY = 0
-REDIS_DB_RESULT = 1
-
-# ==================== 腾讯云 COS ====================
-COS_SECRET_ID = 'AKIDU6WikZBcz96xesxpVM3XoslS3TjogLDx'
-COS_SECRET_KEY = 'qcJzVqLdkeKyXwKckjV5SCmncFfrqdu2'
-COS_REGION = 'ap-shanghai'
-COS_BUCKET = 'xingque999qygwuyq-1404472910'
-COS_APPID = '1404472910'
-
-# ==================== 微信小程序 ====================
-WEIXIN_APPID = 'wx0e4be86faac4a8d1'
-WEIXIN_SECRET = '3c3673cf052571dea9e3bea88e198434'
-
-# ==================== 微信支付 V2 ====================
-WEIXIN_MCHID = '1746545364'
-WEIXIN_SHANGHUMIYAO = 'Dujieduze53631383771411017633490'
-WEIXIN_CERT_PATH = '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.1/tuikuanzhengshu/apiclient_cert.pem'
-WEIXIN_KEY_PATH = '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.1/tuikuanzhengshu/apiclient_key.pem'
-
-# ==================== 微信服务号 ====================
-WEIXIN_OFFICIAL_APPID = 'wxd400adf1911c534f'
-WEIXIN_OFFICIAL_SECRET = '19c946cf02ac6ef13f40333fc8029b92'
-WEIXIN_OFFICIAL_TOKEN = '78324hjwewehrui3yferhf34'
-WEIXIN_OFFICIAL_ENCODING_AES_KEY = 'NimrG1Fmq2ydplHJrXeg34NqbCJerltAGVx9xJ5WBx2'
-WEIXIN_TEMPLATE_ID = 'jZ73G_0KbmFK864WVUr0QFNnqdH-ILBp_hUPMiwx4YY'
-
-# ==================== 微信支付 V3 ====================
-WECHAT_PAY_V3_MCHID = '1746545364'
-WECHAT_PAY_V3_APPID = 'wx0e4be86faac4a8d1'
-WECHAT_PAY_V3_API_V3_KEY = 'dujie4637rufjd98ijuyrgcvbvbn4789'
-WECHAT_PAY_V3_PRIVATE_KEY_PATH = '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/huizhifuzhengshu/apiclient_key.pem'
-WECHAT_PAY_V3_CERT_SERIAL_NO = '13D0CECB42C48DE78FF35A8A72EF4341FE769FE9'
-WECHAT_PAY_V3_PLATFORM_CERT_DIR = '/opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/huizhifuzhengshu/pub_key.pem'
-
-# ==================== GoEasy IM ====================
-GOEASY_APPKEY = 'BC-32e2991c69bc47e1b538e73aaed37fc1'
-GOEASY_SECRET = 'A469ABAA3cc34D64'
-
-# ==================== 跨平台互通 ====================
-CROSS_PLATFORM_TOKEN = 'jdjdjshwjqk'
-CROSS_PLATFORM_SYNC_TOKEN = 'xK9mP2vL8qW5rT7y'
-PARTNER_BASE_URL = 'https://ios808.top/hqhd'
-AGENT_SHANGJIA_ID = '9106027'
-CROSS_PLATFORM_MEMBER_ID = 'V00QQI'
-CROSS_PLATFORM_WHITELIST = ['5438898']
-
-# ==================== 特殊管事ID ====================
-SPECIAL_GUANSHI_IDS = [
- '2295516',
- '9106027',
- '2244062',
-]
diff --git a/a_long_dianjing/settings.py b/a_long_dianjing/settings.py
index 3e86917..60bb888 100644
--- a/a_long_dianjing/settings.py
+++ b/a_long_dianjing/settings.py
@@ -1,6 +1,5 @@
"""
Django settings for a_long_dianjing project.
-开发环境配置 - 参照旧项目结构精简
"""
from pathlib import Path
from datetime import timedelta
@@ -8,19 +7,17 @@ import os
# 从密钥配置文件导入敏感信息
try:
- import _secrets
+ import app_secrets
except ImportError:
- raise ImportError("缺少 secrets.py 密钥配置文件,请参照项目说明创建")
+ raise ImportError("缺少 appapp_secrets.py 密钥配置文件,请参照项目说明创建")
-# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
-# Quick-start development settings - unsuitable for production
-SECRET_KEY = _secrets.SECRET_KEY
-DEBUG = False # 开发环境为 True
-ALLOWED_HOSTS = ['localhost', '127.0.0.1','www.abas.asia'] # 根据开发前端地址可添加
+SECRET_KEY = app_secrets.SECRET_KEY
+DEBUG = False
+ALLOWED_HOSTS = ['localhost', '127.0.0.1', 'www.abas.asia']
-# Application definition
+# ==================== 应用配置 ====================
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
@@ -28,34 +25,30 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
- # 第三方应用 (根据 requirements.txt 已安装的)
'rest_framework',
'rest_framework_simplejwt',
'corsheaders',
'django_celery_results',
- # 你的自定义应用 (按已创建的拼音命名)
'celery',
'yonghu',
'dingdan',
'shangpin',
'peizhi',
'houtai',
- # 'games', # 【待确认】你提到 games 还没创建,暂不添加
'utils',
'shangdian',
'dengji',
]
MIDDLEWARE = [
- 'corsheaders.middleware.CorsMiddleware', # 跨域中间件放最前
+ 'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware', # 默认开启,如API无需可注释
+ 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
- # 'guanapi.middleware.OpLogMiddleware', # 【待确认】这是旧项目的自定义中间件,新项目需要吗?
]
ROOT_URLCONF = 'a_long_dianjing.urls'
@@ -77,57 +70,40 @@ TEMPLATES = [
WSGI_APPLICATION = 'a_long_dianjing.wsgi.application'
-# Database
-# 根据你提供的信息配置 (本地开发,端口3307)
+# ==================== 数据库 ====================
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
- 'NAME': _secrets.DATABASE_NAME,
- 'USER': _secrets.DATABASE_USER,
- 'HOST': _secrets.DATABASE_HOST,
- 'PASSWORD': _secrets.DATABASE_PASSWORD,
- 'PORT': _secrets.DATABASE_PORT,
+ 'NAME': app_secrets.DATABASE_NAME,
+ 'USER': app_secrets.DATABASE_USER,
+ 'HOST': app_secrets.DATABASE_HOST,
+ 'PASSWORD': app_secrets.DATABASE_PASSWORD,
+ 'PORT': app_secrets.DATABASE_PORT,
'OPTIONS': {
'charset': 'utf8mb4',
- 'init_command': "SET time_zone='+08:00'", # 保持与旧项目一致
+ 'init_command': "SET time_zone='+08:00'",
}
}
}
-# Password validation
+# ==================== 密码验证 ====================
AUTH_PASSWORD_VALIDATORS = [
- {
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
- },
+ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
+ {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
+ {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
+ {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
-# Internationalization
-LANGUAGE_CODE = 'zh-Hans' # 使用中文
+# ==================== 国际化 ====================
+LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
-USE_TZ = False # 保持与旧项目一致,不使用UTC时区
+USE_TZ = False
-# Static files (CSS, JavaScript, Images)
STATIC_URL = 'static/'
-# 开发环境静态文件目录,可参照旧项目添加 MEDIA 配置
-# MEDIA_URL = 'media/'
-# MEDIA_ROOT = BASE_DIR / 'media'
-
-# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
-# ==================== 以下为第三方库及自定义配置 ====================
-# settings.py
-# 修改SIMPLE_JWT配置
+# ==================== JWT 配置 ====================
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=36500),
'REFRESH_TOKEN_LIFETIME': timedelta(days=36500),
@@ -143,7 +119,6 @@ SIMPLE_JWT = {
'LEEWAY': 0,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
- # 修改这里:使用yonghuid而不是id
'USER_ID_FIELD': 'yonghuid',
'USER_ID_CLAIM': 'yonghuid',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
@@ -153,16 +128,13 @@ SIMPLE_JWT = {
'JTI_CLAIM': 'jti',
}
-# 添加自定义认证后端
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
]
-# 必须指定用户模型(即使它不继承AbstractBaseUser)
AUTH_USER_MODEL = 'yonghu.UserMain'
-
-# DRF配置(也非常简单)
+# ==================== DRF 配置 ====================
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
@@ -171,48 +143,27 @@ REST_FRAMEWORK = {
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_THROTTLE_CLASSES': [
- 'rest_framework.throttling.AnonRateThrottle', # 匿名用户限制
- 'rest_framework.throttling.UserRateThrottle', # 登录用户限制
+ 'rest_framework.throttling.AnonRateThrottle',
+ 'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
- 'anon': '100/minute', # 匿名用户每分钟100次
- 'user': '1000/minute', # 登录用户每分钟1000次
+ 'anon': '100/minute',
+ 'user': '1000/minute',
},
- # 🔥 关键:使用自定义异常处理器
'EXCEPTION_HANDLER': 'utils.exception_handler.custom_exception_handler',
-
- # 🔥 生产环境只返回JSON,不返回HTML(更安全)
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
],
}
+# ==================== H5 域名 ====================
+H5_DOMAIN = 'https://www.abas.asia:666'
-
-
-
-
-
-
-# settings.py
-# H5静态页面域名配置
-H5_DOMAIN = 'https://www.abas.asia:666' # 生产环境
-# H5_DOMAIN = 'https://test-h5.yourdomain.com' # 测试环境
-
-
-
-
-
-
-
-
-
-# CORS settings
+# ==================== CORS 配置 ====================
CORS_ALLOWED_ORIGINS = [
- "http://localhost:8080", # 假设前端开发服务器地址
+ "http://localhost:8080",
"http://127.0.0.1:8080",
"https://www.abas.asia",
- #"http://192.168.240.1:5173/",
"https://www.abas.asia:666",
"https://www.abas.asia:1234",
"https://www.abas.asia:963",
@@ -220,126 +171,73 @@ CORS_ALLOWED_ORIGINS = [
"http://localhost:5173",
"http://localhost:3000",
"http://localhost:3001",
-
-
- #"www.abas.asia",
]
CORS_ALLOW_CREDENTIALS = True
-CORS_ALLOW_METHODS = (
- "GET",
- "POST",
- "PUT",
- "PATCH",
- "DELETE",
- "OPTIONS",
-)
+CORS_ALLOW_METHODS = ("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
CORS_ALLOW_HEADERS = [
- "accept",
- "accept-encoding",
- "authorization",
- "content-type",
- "dnt",
- "origin",
- "user-agent",
- "x-csrftoken",
- "x-requested-with",
+ "accept", "accept-encoding", "authorization", "content-type",
+ "dnt", "origin", "user-agent", "x-csrftoken", "x-requested-with",
]
-# 【待确认】自定义用户模型,你需要在 yonghu/models.py 中创建 class Yonghu
-#AUTH_USER_MODEL = 'yonghu.Yonghu'
-
-
-
-
-
-
-# OSS配置
-COS_SECRET_ID = _secrets.COS_SECRET_ID
-COS_SECRET_KEY = _secrets.COS_SECRET_KEY
-COS_REGION = _secrets.COS_REGION
-COS_BUCKET = _secrets.COS_BUCKET
-COS_SCHEME = 'https' # 使用HTTPS
+# ==================== 腾讯云 COS ====================
+COS_SECRET_ID = app_secrets.COS_SECRET_ID
+COS_SECRET_KEY = app_secrets.COS_SECRET_KEY
+COS_REGION = app_secrets.COS_REGION
+COS_BUCKET = app_secrets.COS_BUCKET
+COS_SCHEME = 'https'
COS_DOMAIN = f'https://{COS_BUCKET}.cos.{COS_REGION}.myqcloud.com'
-COS_APPID = _secrets.COS_APPID
-COS_STS_DURATION = 7200 # STS临时凭证有效期(秒)
+COS_APPID = app_secrets.COS_APPID
+COS_STS_DURATION = 7200
-# ==================== 业务相关配置 (值先空着,结构可保留) ====================
+# ==================== 微信小程序 & 服务号 ====================
+WEIXIN_APPID = app_secrets.WEIXIN_APPID
+WEIXIN_SECRET = app_secrets.WEIXIN_SECRET
+WEIXIN_MCHID = app_secrets.WEIXIN_MCHID
+WEIXIN_SHANGHUMIYAO = app_secrets.WEIXIN_SHANGHUMIYAO
+WEIXIN_OFFICIAL_APPID = app_secrets.WEIXIN_OFFICIAL_APPID
+WEIXIN_OFFICIAL_SECRET = app_secrets.WEIXIN_OFFICIAL_SECRET
+WEIXIN_OFFICIAL_TOKEN = app_secrets.WEIXIN_OFFICIAL_TOKEN
+WEIXIN_OFFICIAL_ENCODING_AES_KEY = app_secrets.WEIXIN_OFFICIAL_ENCODING_AES_KEY
+WEIXIN_TEMPLATE_ID = app_secrets.WEIXIN_TEMPLATE_ID
+WEIXIN_TEMPLATE_MAX_PER_MINUTE = 950
+WEIXIN_TEMPLATE_BATCH_SIZE = 100
+WEIXIN_CERT_PATH = app_secrets.WEIXIN_CERT_PATH
+WEIXIN_KEY_PATH = app_secrets.WEIXIN_KEY_PATH
-# 微信小程序配置
-WEIXIN_APPID = _secrets.WEIXIN_APPID
-WEIXIN_SECRET = _secrets.WEIXIN_SECRET
-WEIXIN_MCHID = _secrets.WEIXIN_MCHID
-WEIXIN_SHANGHUMIYAO = _secrets.WEIXIN_SHANGHUMIYAO
-# 服务号配置
-WEIXIN_OFFICIAL_APPID = _secrets.WEIXIN_OFFICIAL_APPID
-WEIXIN_OFFICIAL_SECRET = _secrets.WEIXIN_OFFICIAL_SECRET
-WEIXIN_OFFICIAL_TOKEN = _secrets.WEIXIN_OFFICIAL_TOKEN
-WEIXIN_OFFICIAL_ENCODING_AES_KEY = _secrets.WEIXIN_OFFICIAL_ENCODING_AES_KEY
-WEIXIN_TEMPLATE_ID = _secrets.WEIXIN_TEMPLATE_ID
-
-# 🆕【新增】模板消息配置
-WEIXIN_TEMPLATE_MAX_PER_MINUTE = 950 # 每分钟最大发送量(留50缓冲)
-WEIXIN_TEMPLATE_BATCH_SIZE = 100 # 每批发送数量
-
-WEIXIN_CERT_PATH = _secrets.WEIXIN_CERT_PATH
-WEIXIN_KEY_PATH = _secrets.WEIXIN_KEY_PATH
-
-WEIXIN_NOTIFY_URL = 'https://www.abas.asia/hqhd/dingdan/pay-notify/' # 支付回调地址,如:http://你的域名/api/pay/callback/
-# settings.py 或相应配置文件中
-WEIXIN_PAY_DESCRIPTION = "网络服务" # 支付描述
-# 押金充值相关配置
-YAJIN_PAY_DESCRIPTION = "打手押金充值" # 押金支付描述
-YAJIN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/yajinhuitiao" # 押金支付回调地址
-
-# 积分充值相关配置
-JIFEN_PAY_DESCRIPTION = "打手积分补充" # 积分支付描述
-JIFEN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/jifenhuitiao" # 积分支付回调地址
-
-# 会员充值相关配置
-HUIYUAN_PAY_DESCRIPTION = "打手会员充值" # 会员支付描述
-HUIYUAN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/huiyuanhuitiao" # 会员支付回调地址
-
-
-# 考核支付配置
+# ==================== 微信支付回调地址 ====================
+WEIXIN_NOTIFY_URL = 'https://www.abas.asia/hqhd/dingdan/pay-notify/'
+WEIXIN_PAY_DESCRIPTION = "网络服务"
+YAJIN_PAY_DESCRIPTION = "打手押金充值"
+YAJIN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/yajinhuitiao"
+JIFEN_PAY_DESCRIPTION = "打手积分补充"
+JIFEN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/jifenhuitiao"
+HUIYUAN_PAY_DESCRIPTION = "打手会员充值"
+HUIYUAN_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/huiyuanhuitiao"
KAOHE_PAY_DESCRIPTION = '考核金牌-缴纳考核费'
-KAOHE_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/kh_huitiao' # 替换为实际回调地址
-KAOHE_PAY_DEFAULT_FEE = 5.00 # 未配置费用时的默认金额
-
-
-# settings.py
-SHANGJIA_CZ_PREFIX = "CzSJ" # 商家充值订单前缀
+KAOHE_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/kh_huitiao'
+KAOHE_PAY_DEFAULT_FEE = 5.00
+SHANGJIA_CZ_PREFIX = "CzSJ"
SHANGJIA_CZ_PAY_DESCRIPTION = "阿龙电竞-商家余额充值"
SHANGJIA_CZ_PAY_NOTIFY_URL = "https://www.abas.asia/hqhd/shangpin/sjhuitiao"
SHANGJIA_CZ_MIN_AMOUNT = 1
SHANGJIA_CZ_MAX_AMOUNT = 10000
+FAKUAN_PAY_DESCRIPTION = '罚款缴纳'
+FAKUAN_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/fk_huitiao'
-
-
-# 罚款缴纳微信支付配置
-FAKUAN_PAY_DESCRIPTION = '罚款缴纳' # 支付描述
-FAKUAN_PAY_NOTIFY_URL = 'https://www.abas.asia/hqhd/yonghu/fk_huitiao' # 微信支付回调地址(根据实际域名调整)
-
-
-
-
-# 订单ID前缀配置
+# ==================== 订单ID前缀 ====================
ORDER_PREFIX = {
- 'yajin': 'CzYJ', # 押金订单前缀
- 'jifen': 'CzJF', # 积分订单前缀
- 'huiyuan': 'CzHY', # 会员订单前缀
+ 'yajin': 'CzYJ',
+ 'jifen': 'CzJF',
+ 'huiyuan': 'CzHY',
}
-
-
-
-
-# 腾讯云即时通讯 (IM) - 开发环境可暂不配置
+# ==================== 腾讯云 IM ====================
TENCENT_IM_CONFIG = {
- 'SDK_APP_ID': 0, # 整数
+ 'SDK_APP_ID': 0,
'SECRET_KEY': '',
}
-# 阿里云短信 - 开发环境可暂不配置
+# ==================== 阿里云短信 ====================
ALIYUN_SMS_CONFIG = {
'ACCESS_KEY_ID': '',
'ACCESS_KEY_SECRET': '',
@@ -348,59 +246,27 @@ ALIYUN_SMS_CONFIG = {
'REGION_ID': 'cn-hangzhou',
}
-# HashID (用于加密ID,旧项目有,新项目需要吗?)
-# HASHID_FIELD_SALT = 'a_long_dianjing_2025_salt' # 【待确认】请设置你的盐值
-
-
- # settings.py 尾部添加
-# settings.py
-# settings.py
-# settings.py 中添加配置
-GOEASY_APPKEY = _secrets.GOEASY_APPKEY
-GOEASY_SECRET = _secrets.GOEASY_SECRET
-GOEASY_REST_URL = 'https://rest-hz.goeasy.io/v2/im/message' # 正式环境
-
-
-
-
-
-
-
-
-
-
-
-"""
-阿龙电竞 - Django设置文件中的Celery配置部分
-请在您的settings.py中添加以下配置
-"""
-
-# ==================== Celery配置 ====================
-# Redis作为消息代理和结果后端
-REDIS_HOST = _secrets.REDIS_HOST
-REDIS_PORT = _secrets.REDIS_PORT
-REDIS_PASSWORD = _secrets.REDIS_PASSWORD
-REDIS_DB_CELERY = _secrets.REDIS_DB_CELERY
-REDIS_DB_RESULT = _secrets.REDIS_DB_RESULT
-
+# ==================== GoEasy IM ====================
+GOEASY_APPKEY = app_secrets.GOEASY_APPKEY
+GOEASY_SECRET = app_secrets.GOEASY_SECRET
+GOEASY_REST_URL = 'https://rest-hz.goeasy.io/v2/im/message'
+# ==================== Celery 配置 ====================
+REDIS_HOST = app_secrets.REDIS_HOST
+REDIS_PORT = app_secrets.REDIS_PORT
+REDIS_PASSWORD = app_secrets.REDIS_PASSWORD
+REDIS_DB_CELERY = app_secrets.REDIS_DB_CELERY
+REDIS_DB_RESULT = app_secrets.REDIS_DB_RESULT
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/2',
'TIMEOUT': 300,
- 'OPTIONS': {
- # 不需要 CLIENT_CLASS
- }
+ 'OPTIONS': {}
}
}
-
-
-
-
-# 构建Redis连接字符串
if REDIS_PASSWORD:
CELERY_BROKER_URL = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB_CELERY}'
CELERY_RESULT_BACKEND = f'redis://:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB_RESULT}'
@@ -408,120 +274,56 @@ else:
CELERY_BROKER_URL = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB_CELERY}'
CELERY_RESULT_BACKEND = f'redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB_RESULT}'
-# 任务接受的内容类型
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
-
-# 时区设置
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_ENABLE_UTC = True
-
-
-
-# settings.py 末尾添加(在其他 Celery 配置之后)
CELERY_IMPORTS = ('dingdan.tongzhi_tasks',)
-
-
-
-# 任务超时设置
-CELERY_TASK_TIME_LIMIT = 300 # 任务最大执行时间300秒
-CELERY_TASK_SOFT_TIME_LIMIT = 240 # 软超时240秒
-
-# Worker并发设置
-CELERY_WORKER_CONCURRENCY = 4 # 根据服务器CPU核心数调整
+CELERY_TASK_TIME_LIMIT = 300
+CELERY_TASK_SOFT_TIME_LIMIT = 240
+CELERY_WORKER_CONCURRENCY = 4
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
-
-# 任务确认设置
CELERY_TASK_ACKS_LATE = True
CELERY_WORKER_DISABLE_RATE_LIMITS = True
-
-# 队列配置
CELERY_TASK_DEFAULT_QUEUE = 'default'
CELERY_TASK_QUEUES = {
- 'default': {
- 'exchange': 'default',
- 'routing_key': 'default',
- },
- 'order_tasks': {
- 'exchange': 'order_tasks',
- 'routing_key': 'order_tasks',
- },
- 'periodic_tasks': {
- 'exchange': 'periodic_tasks',
- 'routing_key': 'periodic_tasks',
- },
+ 'default': {'exchange': 'default', 'routing_key': 'default'},
+ 'order_tasks': {'exchange': 'order_tasks', 'routing_key': 'order_tasks'},
+ 'periodic_tasks': {'exchange': 'periodic_tasks', 'routing_key': 'periodic_tasks'},
}
# ==================== 定时任务时间配置 ====================
-# 所有时间配置都在这里,方便修改
+ORDER_EXPIRE_SECONDS = 48 * 60 * 60 # 48小时 = 172800秒
+DAILY_TASK_HOUR = 0
+DAILY_TASK_MINUTE = 30
+MONTHLY_TASK_DAY = 1
+MONTHLY_TASK_HOUR = 0
+MONTHLY_TASK_MINUTE = 0
+ORDER_CHECK_INTERVAL = 5
-# 订单超时时间(秒) - 48小时
-ORDER_EXPIRE_SECONDS = 48000 * 60 * 60 # 172800秒
-
-# 每日任务执行时间(24小时制)
-DAILY_TASK_HOUR = 0 # 0点
-DAILY_TASK_MINUTE = 30 # 0分
-
-# 每月任务执行时间
-MONTHLY_TASK_DAY = 1 # 每月1日
-MONTHLY_TASK_HOUR = 0 # 0点
-MONTHLY_TASK_MINUTE = 0 # 0分
-
-# 订单检查频率(分钟)
-ORDER_CHECK_INTERVAL = 5 # 每5分钟检查一次
-
-
-
-
-
-
-
-
-
-
-
-
-# ==================== 跨平台订单互通配置 ====================
-CROSS_PLATFORM_TOKEN = _secrets.CROSS_PLATFORM_TOKEN
-PARTNER_BASE_URL = _secrets.PARTNER_BASE_URL
-
-# 对方平台各接口路径(可根据实际情况调整,但双方需保持一致)
-PARTNER_SYNC_ORDER_URL = '/dingdan/partner_sync_order' # 接收派单
-PARTNER_CHECK_CLAIM_URL = '/dingdan/partner_check_claim' # 抢单询问
-PARTNER_NOTIFY_CLAIM_URL = '/dingdan/partner_notify_claim' # 抢单成功通知
-PARTNER_REFUND_NOTIFY_URL = '/dingdan/partner_refund_notify' # 退款通知
-
-AGENT_SHANGJIA_ID = _secrets.AGENT_SHANGJIA_ID
-CROSS_PLATFORM_MEMBER_ID = _secrets.CROSS_PLATFORM_MEMBER_ID
-
-# 默认游戏类型ID(当对方未传递时使用)
+# ==================== 跨平台订单互通 ====================
+CROSS_PLATFORM_TOKEN = app_secrets.CROSS_PLATFORM_TOKEN
+CROSS_PLATFORM_SYNC_TOKEN = app_secrets.CROSS_PLATFORM_SYNC_TOKEN
+PARTNER_BASE_URL = app_secrets.PARTNER_BASE_URL
+PARTNER_SYNC_ORDER_URL = '/dingdan/partner_sync_order'
+PARTNER_CHECK_CLAIM_URL = '/dingdan/partner_check_claim'
+PARTNER_NOTIFY_CLAIM_URL = '/dingdan/partner_notify_claim'
+PARTNER_REFUND_NOTIFY_URL = '/dingdan/partner_refund_notify'
+AGENT_SHANGJIA_ID = app_secrets.AGENT_SHANGJIA_ID
+CROSS_PLATFORM_MEMBER_ID = app_secrets.CROSS_PLATFORM_MEMBER_ID
CROSS_PLATFORM_GAME_TYPE_ID = 1
+CROSS_PLATFORM_WHITELIST = app_secrets.CROSS_PLATFORM_WHITELIST
+SPECIAL_GUANSHI_IDS = app_secrets.SPECIAL_GUANSHI_IDS
-CROSS_PLATFORM_WHITELIST = _secrets.CROSS_PLATFORM_WHITELIST
-
-
-
-
-
-SPECIAL_GUANSHI_IDS = _secrets.SPECIAL_GUANSHI_IDS
-
-
-
-
-
-
-
+# ==================== 微信支付 V3 ====================
WECHAT_PAY_V3_CONFIG = {
- 'MCHID': _secrets.WECHAT_PAY_V3_MCHID,
- 'APPID': _secrets.WECHAT_PAY_V3_APPID,
- 'API_V3_KEY': _secrets.WECHAT_PAY_V3_API_V3_KEY,
- 'PRIVATE_KEY_PATH': _secrets.WECHAT_PAY_V3_PRIVATE_KEY_PATH,
- 'CERT_SERIAL_NO': _secrets.WECHAT_PAY_V3_CERT_SERIAL_NO,
- 'PLATFORM_CERT_DIR': _secrets.WECHAT_PAY_V3_PLATFORM_CERT_DIR,
+ 'MCHID': app_secrets.WECHAT_PAY_V3_MCHID,
+ 'APPID': app_secrets.WECHAT_PAY_V3_APPID,
+ 'API_V3_KEY': app_secrets.WECHAT_PAY_V3_API_V3_KEY,
+ 'PRIVATE_KEY_PATH': app_secrets.WECHAT_PAY_V3_PRIVATE_KEY_PATH,
+ 'CERT_SERIAL_NO': app_secrets.WECHAT_PAY_V3_CERT_SERIAL_NO,
+ 'PLATFORM_CERT_DIR': app_secrets.WECHAT_PAY_V3_PLATFORM_CERT_DIR,
'NOTIFY_URL': 'https://www.abas.asia/hqhd/yonghu/tixiancallback',
'TRANSFER_SCENE_ID': '1005',
}
-
-
-CROSS_PLATFORM_SYNC_TOKEN = _secrets.CROSS_PLATFORM_SYNC_TOKEN
\ No newline at end of file
diff --git a/dingdan/views.py b/dingdan/views.py
index bbe3a4a..3dee938 100644
--- a/dingdan/views.py
+++ b/dingdan/views.py
@@ -1,11494 +1,8146 @@
-import os
-from dingdan.utils import sync_order_to_partners
-from django.db.models import F
-from django.shortcuts import render
-from rest_framework.permissions import AllowAny # 新增这行
-from rest_framework.throttling import AnonRateThrottle
-from dingdan.utils import sync_order_to_partners
-# dingdan/views.py
-import traceback
-import time
-import random
-import hashlib
-import xml.etree.ElementTree as ET
-import requests
-import uuid
-from decimal import Decimal
-from django.db import transaction
-from django.conf import settings
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import permissions, status
-from tencentcloud.common.profile.client_profile import ClientProfile
-from tencentcloud.common.profile.http_profile import HttpProfile
-
-from shangpin.models import Shangpin, ShangpinLeixing, Huiyuangoumai
-from dingdan.models import Dingdan, DingdanPingtai, Lilubiao, Pingfen, DingdanShangjia, Chufajilu, Liaotian, Chufatupian
-from utils.weixin_broadcast import WeixinBroadcastSender
-from yonghu.models import UserMain, UserDashou, UserShangjia
-
-from shangdian.utils import calculate_pingtai_and_dianpu_shouyi,validate_shangpin_and_dianpu
-from shangpin.utils import update_shangpin_daily_stat
-from shangdian.utils import update_dianpu_daily_stat # 注意:您之前写的店铺统计方法名是 update_dianpu_daily_stat
-
-from houtai.utils import update_dashou_daily_by_action
-
-# dingdan/views.py (CreateOrderView)
-
-
-# dingdan/views.py (WechatPayNotifyView)
-
-import threading
-import hashlib
-import xml.etree.ElementTree as ET
-from decimal import Decimal
-from django.db import transaction, models
-from django.http import HttpResponse
-from rest_framework.views import APIView
-from rest_framework.permissions import AllowAny
-from rest_framework.throttling import AnonRateThrottle
-import logging
-from rest_framework import permissions
-
-from shangpin.models import Shangpin
-
-from yonghu.models import UserMain, UserBoss
-
-
-from django.db import transaction
-from django.http import HttpResponse
-from rest_framework.views import APIView
-
-from rest_framework.throttling import AnonRateThrottle
-from rest_framework.permissions import AllowAny
-
-from peizhi.models import Szjilu
-
-logger = logging.getLogger(__name__)
-
-
-
-# dingdan/views.py
-import threading
-import hashlib
-import logging
-import traceback
-import xml.etree.ElementTree as ET
-from decimal import Decimal
-from django.db import transaction, models
-from django.http import HttpResponse
-from django.conf import settings
-from rest_framework.views import APIView
-from rest_framework.permissions import AllowAny
-from rest_framework.throttling import AnonRateThrottle
-
-# 假设这些模型已正确导入
-from dingdan.models import Dingdan, DingdanPingtai
-from shangpin.models import Shangpin
-from yonghu.models import UserMain, UserBoss
-from peizhi.models import Szjilu
-
-
-logger = logging.getLogger(__name__)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-# dingdan/views.py (PaymentFailView)
-
-class PaymentFailView(APIView):
- """
- 前端支付失败接口 - 处理用户主动取消或支付失败
- 只允许删除状态为9(未支付)的订单
- """
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- try:
- dingdanid = request.data.get('dingdanid')
- if not dingdanid:
- return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None})
-
- with transaction.atomic():
- # 锁定订单防止并发
- dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=dingdanid).first()
- if not dingdan:
- return Response({'code': 2, 'msg': '订单不存在', 'data': None})
-
- # 只允许删除未支付订单(状态9)
- if dingdan.zhuangtai != 9:
- return Response({'code': 3, 'msg': '只能删除未支付的订单', 'data': None})
-
- # 删除扩展表和主表
- DingdanPingtai.objects.filter(dingdan=dingdan).delete()
- dingdan.delete()
-
- return Response({'code': 0, 'msg': '订单已删除', 'data': None})
-
- except Exception as e:
- return Response({'code': 99, 'msg': f'删除订单失败: {str(e)}', 'data': None})
-
-
-
-
-
-import string
-import requests
-import xmltodict
-import logging
-from decimal import Decimal
-from django.db import transaction, models
-from django.core.cache import cache
-from django.conf import settings
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import permissions, status
-from rest_framework.parsers import JSONParser
-
-from yonghu.models import UserMain, UserBoss
-from shangpin.models import Shangpin
-from dingdan.models import Dingdan, DingdanPingtai
-from peizhi.models import Szjilu
-
-logger = logging.getLogger(__name__)
-
-
-from dingdan.utils import update_daily_income
-import logging
-import traceback
-
-logger = logging.getLogger(__name__)
-
-class WechatPayNotifyView(APIView):
- """微信支付回调接口 - 接收微信官方支付结果通知(含详细调试日志)"""
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
-
- def post(self, request):
- # 记录原始请求数据(调试用)
- xml_data = request.body.decode('utf-8')
-
- # 最外层异常捕获,确保任何错误都能被记录并返回适当响应
- try:
- # 1. 解析 XML
- try:
- root = ET.fromstring(xml_data)
- except ET.ParseError as e:
- logger.error(f"XML解析失败: {e}")
- return self._gen_response_xml('FAIL', 'XML解析失败')
-
- # 2. 获取关键参数
- return_code = root.find('return_code').text if root.find('return_code') is not None else ''
- result_code = root.find('result_code').text if root.find('result_code') is not None else ''
- out_trade_no = root.find('out_trade_no').text if root.find('out_trade_no') is not None else ''
- transaction_id = root.find('transaction_id').text if root.find('transaction_id') is not None else ''
-
- # 3. 签名验证
- if not self._verify_signature(xml_data):
- logger.warning("签名验证失败")
- return self._gen_response_xml('FAIL', '签名验证失败')
-
- # 4. 根据支付结果处理
- if return_code == 'SUCCESS' and result_code == 'SUCCESS':
- self._handle_success(out_trade_no, transaction_id, root)
- else:
- logger.info(f"支付失败或异常,return_code={return_code}, result_code={result_code}")
- self._handle_failure(out_trade_no)
-
- # 返回微信要求的成功响应(必须返回SUCCESS,即使内部处理失败也要返回,否则微信会重复通知)
- return self._gen_response_xml('SUCCESS', 'OK')
-
- except Exception as e:
- # 捕获任何未处理的异常,记录完整堆栈
- logger.error(f"回调处理发生未捕获异常: {str(e)}")
- logger.error(traceback.format_exc())
- return self._gen_response_xml('FAIL', f'服务器内部错误: {str(e)}')
-
- def _verify_signature(self, xml_data):
- """微信支付签名验证(MD5)"""
- try:
- root = ET.fromstring(xml_data)
- sign = root.find('sign').text
- # 构建待签名字符串(排除sign字段)
- data = {child.tag: child.text for child in root if child.tag != 'sign'}
- # 按key排序
- sorted_keys = sorted(data.keys())
- stringA = '&'.join([f"{k}={data[k]}" for k in sorted_keys])
- stringSignTemp = f"{stringA}&key={settings.WEIXIN_SHANGHUMIYAO}"
- calc_sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
- logger.debug(f"计算签名: {calc_sign}, 微信签名: {sign}")
- return calc_sign == sign
- except Exception as e:
- logger.error(f"签名验证过程异常: {e}")
- logger.error(traceback.format_exc())
- return False
-
- def _gen_response_xml(self, return_code, return_msg):
- """生成返回给微信的XML响应"""
- xml = f"""
-
-
-"""
- logger.info(f"返回给微信的响应: {xml}")
- return HttpResponse(xml, content_type='application/xml')
-
- @transaction.atomic
- def _handle_success(self, out_trade_no, transaction_id, root):
- """处理支付成功"""
- logger.info(f"进入 _handle_success, out_trade_no={out_trade_no}, transaction_id={transaction_id}")
-
- # 锁定订单防止并发
- try:
- dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=out_trade_no).first()
- except Exception as e:
- logger.error(f"查询订单异常: {e}")
- raise
-
- if not dingdan:
- logger.error(f"订单不存在: {out_trade_no}")
- return
-
- logger.info(f"订单当前状态: {dingdan.zhuangtai}")
-
- # 幂等处理:只有状态为9(未支付)才处理,已处理则跳过
- if dingdan.zhuangtai != 9:
- logger.info(f"订单 {out_trade_no} 已处理过,状态={dingdan.zhuangtai},跳过")
- return
-
- # 保存微信交易号
- if transaction_id:
- dingdan.wechat_transaction_id = transaction_id
- logger.info(f"保存微信交易号: {transaction_id}")
- else:
- logger.warning("微信未返回transaction_id")
-
- # 更新订单状态
- if dingdan.zhiding_id and dingdan.zhiding_id.strip():
- dingdan.zhuangtai = 7
- logger.info("指定单,状态设为7")
- else:
- dingdan.zhuangtai = 1
- logger.info("普通单,状态设为1")
-
- dingdan.save()
-
- logger.info(f"订单状态已更新为 {dingdan.zhuangtai}")
-
- # 删除同一用户同一商品的其他未支付订单(改为逻辑删除,状态25)
- try:
- abandoned_count = Dingdan.objects.filter(
- user1_id=dingdan.user1_id,
- shangpin_id=dingdan.shangpin_id,
- zhuangtai=9
- ).exclude(dingdan_id=out_trade_no).update(zhuangtai=25)
- logger.info(f"将 {abandoned_count} 个未支付订单标记为已废弃(25)")
- except Exception as e:
- logger.error(f"标记其他未支付订单异常: {e}")
-
- # 更新老板扩展表
- try:
- laoban_id = None
- if hasattr(dingdan, 'pingtai_kuozhan'):
- laoban_id = dingdan.pingtai_kuozhan.laoban_id
- if laoban_id:
- user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
- if user_main and hasattr(user_main, 'boss_profile'):
- boss = user_main.boss_profile
- boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
- boss.alldingdan = (boss.alldingdan or 0) + 1
- boss.save()
- logger.info(f"更新老板 {laoban_id} 数据成功")
- except Exception as e:
- logger.error(f"更新老板数据异常: {e}")
-
- # 更新商品库存
- try:
- if dingdan.shangpin_id:
- Shangpin.objects.filter(id=dingdan.shangpin_id).update(
- kucun=F('kucun') - 1
- )
- logger.info(f"商品 {dingdan.shangpin_id} 库存减1")
- except Exception as e:
- logger.error(f"更新商品库存异常: {e}")
-
-
-
-
- # ========== 新增:商品和店铺每日统计 ==========
- try:
- # 商品每日统计(只要有商品ID就执行)
- if dingdan.shangpin_id:
- # 从扩展表获取已计算好的收益值(下单时已存储)
- pingtai_shouyi = Decimal('0.00')
- dianpu_shouyi = Decimal('0.00')
- if hasattr(dingdan, 'pingtai_kuozhan'):
- pingtai_shouyi = dingdan.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
- dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
-
- update_shangpin_daily_stat(
- shangpin_id=dingdan.shangpin_id,
- caozuo_leixing=1, # 1 = 下单
- dingdan_jiage=dingdan.jine,
- pingtai_shouyi=pingtai_shouyi,
- dianpu_zongshouyi=dianpu_shouyi
- )
- logger.info(f"商品统计更新完成,商品ID: {dingdan.shangpin_id}")
-
- # 店铺每日统计(仅当存在店铺ID时执行)
- dianpu_id = None
- if hasattr(dingdan, 'pingtai_kuozhan'):
- dianpu_id = dingdan.pingtai_kuozhan.dianpu_id
- if dianpu_id:
- dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
- update_dianpu_daily_stat(
- dianpu_id=dianpu_id,
- caozuo_leixing=1, # 1 = 下单
- dingdan_jiage=dingdan.jine,
- dianpu_shouyi=dianpu_shouyi
- )
- logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}")
- except Exception as e:
- logger.error(f"商品/店铺统计更新失败: {e}")
- logger.error(traceback.format_exc())
- # 统计失败不影响主流程
- # =============================================
-
-
-
-
-
-
-
-
- # 更新收支记录(总累计)
- self._update_szjilu(dingdan.jine)
-
- # 更新每日收入统计
- update_daily_income(dingdan.jine)
-
- # 跨平台同步
- sync_order_to_partners(dingdan.dingdan_id)
-
- #🟢【7. 新增】支付成功后的广播通知(异步,不阻塞主流程)
- from dingdan.tongzhi_tasks import dingdan_guangbo
-
- # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用)
- order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self._get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': (dingdan.jieshao or '')[:50],
- }
- dingdan_guangbo.delay(order_info) # 瞬间返回,不阻塞请求
-
- # 异步广播通知
- #self._broadcast_async(dingdan)
-
- logger.info(f"订单 {out_trade_no} 支付成功处理完成")
-
- @transaction.atomic
- def _handle_failure(self, out_trade_no):
- """处理支付失败"""
- logger.info(f"进入 _handle_failure, out_trade_no={out_trade_no}")
-
- try:
- dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=out_trade_no).first()
- if not dingdan:
- logger.warning(f"订单不存在: {out_trade_no}")
- return
-
- logger.info(f"订单当前状态: {dingdan.zhuangtai}")
-
- # 仅当订单状态为9(未支付)时才标记失败,不直接删除
- if dingdan.zhuangtai == 9:
- dingdan.zhuangtai = 14 # 自定义支付失败状态
- dingdan.save()
- logger.info(f"订单 {out_trade_no} 支付失败,标记为14")
- else:
- logger.info(f"订单 {out_trade_no} 状态非9,不处理")
- except Exception as e:
- logger.error(f"处理支付失败异常: {e}")
- logger.error(traceback.format_exc())
-
- def _update_szjilu(self, jine):
- """更新收支记录表(ID=1的记录)"""
- try:
- jine_decimal = Decimal(str(jine))
- with transaction.atomic():
- szjilu, created = Szjilu.objects.select_for_update().get_or_create(
- id=1,
- defaults={
- 'zongsy': Decimal('0.00'),
- 'zongls': Decimal('0.00'),
- 'zongzc': Decimal('0.00'),
- 'jrzc': Decimal('0.00'),
- 'jrls': Decimal('0.00')
- }
- )
- if created:
- logger.info("创建ID=1的收支记录")
- szjilu.zongsy += jine_decimal
- szjilu.zongls += jine_decimal
- szjilu.jrls += jine_decimal
- szjilu.save()
- logger.info(f"收支记录更新成功,增加金额: {jine}")
- except Exception as e:
- logger.error(f"更新收支记录失败: {e}")
-
- def _broadcast_async(self, dingdan):
- """异步广播通知"""
- try:
- def task():
- try:
- from utils.weixin_broadcast import WeixinBroadcastSender # 请根据实际路径修改
- order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self._get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': (dingdan.jieshao or '')[:50],
- }
- logger.info(f"准备广播订单: {order_info}")
- sender = WeixinBroadcastSender()
- result = sender.broadcast_order(order_info)
- if result.get('success'):
- logger.info(f"订单广播完成: {result.get('msg')}")
- else:
- logger.warning(f"订单广播异常: {result.get('msg')}")
- except Exception as e:
- logger.error(f"广播任务异常: {e}")
- logger.error(traceback.format_exc())
-
- thread = threading.Thread(target=task)
- thread.daemon = True
- thread.start()
- logger.info("广播线程已启动")
- except Exception as e:
- logger.error(f"启动广播线程异常: {e}")
-
- def _get_game_type_name(self, leixing_id):
- try:
- from shangpin.models import ShangpinLeixing
- gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
- return gt.jieshao if gt else '游戏订单'
- except Exception as e:
- logger.error(f"获取游戏类型名称异常: {e}")
- return '游戏订单'
-
-
-
-
-from django.core.cache import cache
-class PaymentVerifyView(APIView):
- """
- 订单支付成功复查接口 - 前端支付成功后调用,确认订单状态
- 当订单状态为9时,主动查询微信支付状态,若已支付则更新本地状态
- 需要token认证
- """
- permission_classes = [permissions.IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- try:
- # ---------- 1. 获取参数 ----------
- dingdanid = request.data.get('dingdanid')
- if not dingdanid:
- return Response({'code': 1, 'msg': '订单ID不能为空'})
-
- # ---------- 2. 查询订单 ----------
- dingdan = Dingdan.objects.filter(dingdan_id=dingdanid).first()
- if not dingdan:
- return Response({'code': 2, 'msg': '订单不存在'})
-
- # ---------- 3. 验证订单归属(安全) ----------
- current_user = request.user
- user_identifier = f"Boss{current_user.yonghuid}"
- if dingdan.user1_id != user_identifier:
- logger.warning(f"用户 {current_user.yonghuid} 尝试复查他人订单 {dingdanid}")
- return Response({'code': 403, 'msg': '无权访问'})
-
- # ---------- 4. 如果订单状态已是1/7,直接返回成功 ----------
- if dingdan.zhuangtai in [1, 7]:
- return Response({
- 'code': 0,
- 'msg': '订单状态正常',
- 'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai}
- })
-
- # ---------- 5. 如果状态为9,则主动查询微信支付状态 ----------
- if dingdan.zhuangtai == 9:
- # 使用分布式锁防止并发处理同一订单
- lock_key = f"payment_verify_lock_{dingdanid}"
- if not cache.add(lock_key, "locked", timeout=10):
- # 已有其他进程在处理,让前端稍后重试
- return Response({'code': 1, 'msg': '订单正在处理中,请稍后'})
-
- try:
- # 调用微信订单查询接口
- trade_state, transaction_id = self._query_wechat_payment(dingdanid)
-
- if trade_state == 'SUCCESS':
- # 支付成功,执行本地更新
- self._process_payment_success(dingdan, transaction_id, current_user.yonghuid)
- # 重新获取订单以获取最新状态
- dingdan.refresh_from_db()
- return Response({
- 'code': 0,
- 'msg': '支付成功',
- 'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai}
- })
- elif trade_state in ['NOTPAY', 'USERPAYING']:
- # 用户尚未支付或支付中
- return Response({'code': 1, 'msg': '支付中', 'data': {'zhuangtai': 9}})
- else:
- # 其他状态:CLOSED, REVOKED, PAYERROR 等,视为支付失败
- return Response({'code': 2, 'msg': '支付失败'})
-
- except Exception as e:
- logger.error(f"查询微信支付异常: {e}", exc_info=True)
- return Response({'code': 1, 'msg': '查询失败,请稍后重试'})
- finally:
- cache.delete(lock_key)
- else:
- # ---------- 6. 其他异常状态 ----------
- status_msg = {
- 2: '订单已取消',
- 3: '订单已完成',
- 4: '退款审核中',
- 5: '已退款',
- 6: '退款失败',
- 8: '结算中',
- 0: '订单已作废',
- }.get(dingdan.zhuangtai, '订单状态异常')
- return Response({'code': 3, 'msg': status_msg, 'data': {'zhuangtai': dingdan.zhuangtai}})
-
- except Exception as e:
- logger.error(f"订单复查接口异常: {e}", exc_info=True)
- return Response({'code': 99, 'msg': f'系统错误: {str(e)}'})
-
- def _process_payment_success(self, dingdan, transaction_id, operator_id):
- """
- 支付成功后的原子操作(与微信回调逻辑完全一致)
- 在事务内完成所有状态更新和数据一致性操作
- """
- with transaction.atomic():
- # 重新锁定订单,防止并发更新
- dingdan = Dingdan.objects.select_for_update().get(pk=dingdan.pk)
- if dingdan.zhuangtai != 9:
- # 订单状态已被其他进程更新,直接返回(幂等)
- return
-
- # 更新订单状态
- dingdan.zhuangtai = 7 if dingdan.zhiding_id and dingdan.zhiding_id.strip() else 1
- dingdan.wechat_transaction_id = transaction_id
- dingdan.clkf = operator_id # 可选:记录操作人ID
- dingdan.save()
-
- # 删除同一用户同一商品的其他未支付订单(逻辑删除,状态25)
- Dingdan.objects.filter(
- user1_id=dingdan.user1_id,
- shangpin_id=dingdan.shangpin_id,
- zhuangtai=9
- ).exclude(dingdan_id=dingdan.dingdan_id).update(zhuangtai=25)
-
- # 更新老板扩展表
- laoban_id = dingdan.pingtai_kuozhan.laoban_id if hasattr(dingdan, 'pingtai_kuozhan') else None
- if laoban_id:
- user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
- if user_main and hasattr(user_main, 'boss_profile'):
- boss = user_main.boss_profile
- boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
- boss.alldingdan = (boss.alldingdan or 0) + 1
- boss.save()
-
- # 更新商品库存
- if dingdan.shangpin_id:
- Shangpin.objects.filter(id=dingdan.shangpin_id).update(
- kucun=F('kucun') - 1
- )
-
-
-
- # ========== 新增:商品和店铺每日统计 ==========
- try:
- # 商品每日统计(只要有商品ID就执行)
- if dingdan.shangpin_id:
- # 从扩展表获取已计算好的收益值(下单时已存储)
- pingtai_shouyi = Decimal('0.00')
- dianpu_shouyi = Decimal('0.00')
- if hasattr(dingdan, 'pingtai_kuozhan'):
- pingtai_shouyi = dingdan.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
- dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
-
- update_shangpin_daily_stat(
- shangpin_id=dingdan.shangpin_id,
- caozuo_leixing=1, # 1 = 下单
- dingdan_jiage=dingdan.jine,
- pingtai_shouyi=pingtai_shouyi,
- dianpu_zongshouyi=dianpu_shouyi
- )
- logger.info(f"商品统计更新完成,商品ID: {dingdan.shangpin_id}")
-
- # 店铺每日统计(仅当存在店铺ID时执行)
- dianpu_id = None
- if hasattr(dingdan, 'pingtai_kuozhan'):
- dianpu_id = dingdan.pingtai_kuozhan.dianpu_id
- if dianpu_id:
- dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
- update_dianpu_daily_stat(
- dianpu_id=dianpu_id,
- caozuo_leixing=1, # 1 = 下单
- dingdan_jiage=dingdan.jine,
- dianpu_shouyi=dianpu_shouyi
- )
- logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}")
- except Exception as e:
- logger.error(f"商品/店铺统计更新失败: {e}")
- logger.error(traceback.format_exc())
- # 统计失败不影响主流程
- # =============================================
-
-
-
-
-
-
-
-
- # 更新收支记录
- self._update_szjilu(dingdan.jine)
-
- # 更新每日收入统计
- update_daily_income(dingdan.jine)
-
- # 跨平台同步(复查成功也需要通知)
- sync_order_to_partners(dingdan.dingdan_id)
-
- def _query_wechat_payment(self, out_trade_no):
- """
- 调用微信支付订单查询接口,返回 (trade_state, transaction_id)
- 若查询失败,抛出异常
- """
- appid = settings.WEIXIN_APPID
- mch_id = settings.WEIXIN_MCHID
- key = settings.WEIXIN_SHANGHUMIYAO
- nonce_str = self._generate_nonce_str()
-
- params = {
- 'appid': appid,
- 'mch_id': mch_id,
- 'out_trade_no': out_trade_no,
- 'nonce_str': nonce_str,
- }
-
- # 生成签名
- sorted_keys = sorted(params.keys())
- stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys])
- stringSignTemp = f"{stringA}&key={key}"
- sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
- params['sign'] = sign
-
- xml_data = self._dict_to_xml(params)
- url = 'https://api.mch.weixin.qq.com/pay/orderquery'
- response = requests.post(url, data=xml_data, timeout=10)
- result = xmltodict.parse(response.content)['xml']
-
- if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
- return result.get('trade_state'), result.get('transaction_id')
- else:
- err_msg = result.get('return_msg', '未知错误')
- raise Exception(f"微信订单查询失败: {err_msg}")
-
- def _update_szjilu(self, jine):
- """更新收支记录表(ID=1的记录)"""
- try:
- jine_decimal = Decimal(str(jine))
- with transaction.atomic():
- szjilu, created = Szjilu.objects.select_for_update().get_or_create(
- id=1,
- defaults={
- 'zongsy': Decimal('0.00'),
- 'zongls': Decimal('0.00'),
- 'zongzc': Decimal('0.00'),
- 'jrzc': Decimal('0.00'),
- 'jrls': Decimal('0.00')
- }
- )
- if created:
- logger.info("创建ID=1的收支记录")
- szjilu.zongsy += jine_decimal
- szjilu.zongls += jine_decimal
- szjilu.jrls += jine_decimal
- szjilu.save()
- logger.info(f"收支记录更新成功,增加金额: {jine}")
- except Exception as e:
- logger.error(f"更新收支记录失败: {e}")
- # 不抛出异常,因为收支记录不是核心流程,但记录错误供排查
-
- def _generate_nonce_str(self):
- """生成随机字符串,长度32"""
- return ''.join(random.choices(string.ascii_letters + string.digits, k=32))
-
- def _dict_to_xml(self, data):
- """将字典转换为XML字符串"""
- xml = ['']
- for k, v in data.items():
- xml.append(f'<{k}>{v}{k}>')
- xml.append('')
- return ''.join(xml)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-import os
-from django.db.models import F
-from django.shortcuts import render
-from rest_framework.permissions import AllowAny # 新增这行
-from rest_framework.throttling import AnonRateThrottle
-# dingdan/views.py
-import time
-import random
-import hashlib
-import xml.etree.ElementTree as ET
-import requests
-import uuid
-from decimal import Decimal
-from django.db import transaction
-from django.conf import settings
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import permissions, status
-from tencentcloud.common.profile.client_profile import ClientProfile
-from tencentcloud.common.profile.http_profile import HttpProfile
-
-from shangpin.models import Shangpin, ShangpinLeixing, Huiyuangoumai
-from dingdan.models import Dingdan, DingdanPingtai, Lilubiao, Pingfen,Chufatupian, DingdanShangjia, Chufajilu, Liaotian
-from yonghu.models import UserMain, UserDashou, UserShangjia
-
-
-class CreateOrderView(APIView):
- """
- 创建订单并调用微信支付接口
- """
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 获取前端传递的字段
- shangpin_id = request.data.get('shangpin_id')
- nicheng = request.data.get('nicheng', '').strip()
- beizhu = request.data.get('beizhu', '').strip()
- zhiding = request.data.get('zhiding', '').strip()
- jine = request.data.get('jine', 0)
-
- # 2. 验证必填字段
- if not shangpin_id:
- return Response({'code': 1, 'msg': '商品ID不能为空', 'data': None})
-
- if not nicheng:
- return Response({'code': 2, 'msg': '游戏昵称不能为空', 'data': None})
-
- if len(nicheng) > 50:
- return Response({'code': 3, 'msg': '游戏昵称过长', 'data': None})
-
- # 3. 查询商品信息
- shangpin = Shangpin.objects.filter(id=shangpin_id).first()
- if not shangpin:
- return Response({'code': 4, 'msg': '商品不存在', 'data': None})
-
-
- is_valid, err_msg, _ = validate_shangpin_and_dianpu(shangpin_id)
- if not is_valid:
- return Response({'code': 5, 'msg': err_msg, 'data': None})
-
- # 检查库存
- if hasattr(shangpin, 'kucun') and shangpin.kucun <= 0:
- return Response({'code': 5, 'msg': '商品库存不足', 'data': None})
-
- # 获取商品价格
- shangpin_jiage = getattr(shangpin, 'jiage', 0)
- if not shangpin_jiage:
- return Response({'code': 6, 'msg': '商品价格异常', 'data': None})
- #获取商品图片
- shangpin_tupian = getattr(shangpin, 'tupian_url', None)
-
- # 4. 验证指定打手(如果存在)
- zhiding_id_value = None
- if zhiding:
- # 查询主表中是否存在该打手
- zhiding_user = UserMain.objects.filter(
- yonghuid=zhiding,
- #user_type='dashou'
- ).first()
-
- if not zhiding_user:
- return Response({'code': 7, 'msg': '指定打手不存在', 'data': None})
-
- # 查询打手扩展表
- dashou_profile = UserDashou.objects.filter(user=zhiding_user).first()
-
- if not dashou_profile:
- return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None})
-
- # 检查在线状态
- if getattr(dashou_profile, 'zhanghaozhuangtai', 0) == 0:
- return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None})
-
- zhiding_id_value = zhiding
-
- # 5. 生成订单ID
- timestamp = int(time.time())
- timestamp_ns = int(time.time_ns() // 1000) % 1000000
- random_num = random.randint(0, 99)
- dingdanid = f"PT{timestamp:011d}{timestamp_ns:06d}{random_num:02d}"
-
- # 6. 查询利率并计算分成
- # 查询打手利率(字符'1')
- dashou_lilu = Lilubiao.objects.filter(fadanpingtai='1').first()
-
-
- # 🔥 新增:判断是否使用商品配置的额外分成
- if shangpin.kaioi_ewai_dashou_fencheng and shangpin.ewai_dashou_fencheng is not None:
- # 使用商品指定的额外分成金额
- dashou_fencheng = shangpin.ewai_dashou_fencheng
- else:
- # 按原有逻辑计算打手分成
- dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.lilu))
-
-
-
- # 查询管事利率(字符'2')
- #guanshi_lilu = Lilubiao.objects.filter(fadanpingtai='2').first()
-
- if not dashou_lilu: #or not guanshi_lilu:
- return Response({'code': 9, 'msg': '系统利率配置不完整', 'data': None})
-
- # 计算打手分成
- #dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.lilu))
-
-
-
- # 开始数据库事务
- with transaction.atomic():
- # 获取当前用户信息
- current_user = request.user
- laoban_yonghuid = getattr(current_user, 'yonghuid', '')
- user_openid = getattr(current_user, 'openid', '')
-
- if not user_openid:
- return Response({'code': 10, 'msg': '用户openid不存在', 'data': None})
-
- # 创建订单主表数据
- order_data = {
- 'dingdan_id': dingdanid,
- 'zhuangtai': 9, # 未付款状态
- 'jine': shangpin_jiage,
- 'fadan_pingtai':1,
- 'dashou_fencheng': dashou_fencheng,
- 'zhiding_id': zhiding_id_value,
- 'shangpin_id': shangpin_id,
- 'yaoqiuleixing': getattr(shangpin, 'yaoqiuleixing', None),
- 'huiyuan_id': getattr(shangpin, 'huiyuan_id', ''),
- 'yongjin': getattr(shangpin, 'yongjin', 0),
- 'jieshao': getattr(shangpin, 'jieshao', ''),
- 'beizhu': beizhu,
- 'nicheng': nicheng,
- 'tupian':shangpin_tupian,
- 'user1_id':f"Boss{request.user.yonghuid}",
- 'leixing_id':shangpin.leixing_id
- }
-
- # 创建订单记录
-
- dingdan = Dingdan.objects.create(**order_data)
-
-
-
-
- # CreateOrderView.post 事务内部,替换原有的扩展表创建代码
-
- dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong = calculate_pingtai_and_dianpu_shouyi(
- shangpin_id=shangpin_id,
- dingdan_jiage=shangpin_jiage,
- dashou_fencheng=dashou_fencheng
- )
-
- pingtaikuozhan_data = {
- 'dingdan': dingdan,
- 'laoban_id': laoban_yonghuid,
- 'pingtai_shouyi': pingtai_shouyi,
- # ---------- 新增上级分红快照 ----------
- 'shangji_dianpu_id': shangji_dianpu_id,
- 'shangji_fenhong': shangji_fenhong if shangji_fenhong else Decimal('0.00'),
- }
- if dianpu_id:
- pingtaikuozhan_data['dianpu_id'] = dianpu_id
- pingtaikuozhan_data['dianpu_shouyi'] = dianpu_shouyi
- else:
- pingtaikuozhan_data['dianpu_shouyi'] = Decimal('0.00')
-
- DingdanPingtai.objects.create(**pingtaikuozhan_data)
-
-
-
-
- # 计算收益
- '''dianpu_id, pingtai_shouyi, dianpu_shouyi = calculate_pingtai_and_dianpu_shouyi(
- shangpin_id=shangpin_id,
- dingdan_jiage=shangpin_jiage,
- dashou_fencheng=dashou_fencheng
- )
-
- # 构建扩展表数据,平台收益必定保存,店铺收益和店铺ID仅当dianpu_id存在时才赋值
- pingtaikuozhan_data = {
- 'dingdan': dingdan,
- 'laoban_id': laoban_yonghuid,
- 'pingtai_shouyi': pingtai_shouyi,
- }
- if dianpu_id:
- pingtaikuozhan_data['dianpu_id'] = dianpu_id
- pingtaikuozhan_data['dianpu_shouyi'] = dianpu_shouyi
- else:
- # 无店铺时,店铺收益字段保持默认(0.00),也可显式设为0
- pingtaikuozhan_data['dianpu_shouyi'] = Decimal('0.00')
-
- DingdanPingtai.objects.create(**pingtaikuozhan_data)'''
-
-
-
-
- # 7. 生成微信支付参数(向微信官方发送请求)
- pay_params = self.generate_wechat_pay_params(
- dingdanid=dingdanid,
- jine=shangpin_jiage,
- beizhu=getattr(settings, 'WEIXIN_PAY_DESCRIPTION'),
- user_ip=self.get_client_ip(request),
- openid=user_openid
- )
-
- if not pay_params:
- # 手动抛出异常触发事务回滚
- raise Exception('支付参数生成失败')
-
- # 8. 返回成功响应
- response_data = {
- 'code': 0,
- 'msg': '订单创建成功',
- 'data': {
- 'dingdanid': dingdanid,
- 'payParams': pay_params
- }
- }
-
- return Response(response_data)
-
- except Exception as e:
- # 这里会自动回滚事务
- return Response({'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None})
-
- def get_client_ip(self, request):
- """获取客户端真实IP地址"""
- x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
- if x_forwarded_for:
- ip = x_forwarded_for.split(',')[0]
- else:
- ip = request.META.get('REMOTE_ADDR')
- return ip
-
- def generate_wechat_pay_params(self, dingdanid, jine, beizhu, user_ip, openid):
- """
- 调用微信支付统一下单接口,生成支付参数
- 完全按照您提供的正确示例代码编写
- """
- try:
- # 微信支付配置
- APPID = getattr(settings, 'WEIXIN_APPID', '')
- MCHID = getattr(settings, 'WEIXIN_MCHID', '')
- KEY = getattr(settings, 'WEIXIN_SHANGHUMIYAO', '')
- NOTIFY_URL = getattr(settings, 'WEIXIN_NOTIFY_URL', '')
-
- if not all([APPID, MCHID, KEY, NOTIFY_URL]):
- raise Exception('微信支付配置不完整')
-
- # 生成随机字符串
- import string
- nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
-
- # 金额(单位:分)
- total_fee = int(float(jine) * 100)
-
- # 交易类型
- trade_type = 'JSAPI'
-
- # 生成签名
- sign_data = {
- 'appid': APPID,
- 'mch_id': MCHID,
- 'nonce_str': nonce_str,
- 'body': beizhu,
- 'out_trade_no': dingdanid,
- 'total_fee': total_fee,
- 'spbill_create_ip': user_ip,
- 'notify_url': NOTIFY_URL,
- 'trade_type': trade_type,
- 'openid': openid
- }
-
- # 按照参数名ASCII码从小到大排序
- sorted_sign_data = sorted(sign_data.items(), key=lambda x: x[0])
-
- # 拼接字符串
- sign_string = '&'.join([f"{key}={value}" for key, value in sorted_sign_data if value])
- sign_string += f'&key={KEY}'
-
- # MD5签名
- sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper()
-
- # 构造XML数据
- xml_data = f"""
-{APPID}
-{beizhu}
-{MCHID}
-{nonce_str}
-{NOTIFY_URL}
-{openid}
-{dingdanid}
-{user_ip}
-{total_fee}
-{trade_type}
-{sign}
-"""
-
- # 调用微信统一下单接口
- response = requests.post(
- 'https://api.mch.weixin.qq.com/pay/unifiedorder',
- data=xml_data.encode('utf-8'),
- headers={'Content-Type': 'application/xml'},
- timeout=10
- )
-
- # 解析返回的XML
- root = ET.fromstring(response.content)
-
- return_code = root.find('return_code').text
- result_code = root.find('result_code').text
-
- if return_code == 'SUCCESS' and result_code == 'SUCCESS':
- prepay_id = root.find('prepay_id').text
-
- # 生成前端支付参数
- timestamp = str(int(time.time()))
- nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
- package = f'prepay_id={prepay_id}'
-
- # 生成支付签名
- pay_sign_data = {
- 'appId': APPID,
- 'timeStamp': timestamp,
- 'nonceStr': nonce_str,
- 'package': package,
- 'signType': 'MD5'
- }
-
- sorted_pay_sign_data = sorted(pay_sign_data.items(), key=lambda x: x[0])
- pay_sign_string = '&'.join([f"{key}={value}" for key, value in sorted_pay_sign_data])
- pay_sign_string += f'&key={KEY}'
- pay_sign = hashlib.md5(pay_sign_string.encode('utf-8')).hexdigest().upper()
-
- # 返回支付参数(格式与前端完全匹配)
- result = {
- 'timeStamp': timestamp,
- 'nonceStr': nonce_str,
- 'package': package,
- 'signType': 'MD5',
- 'paySign': pay_sign
- }
-
- return result
- else:
- # 获取详细的错误信息
- err_code = root.find('err_code')
- err_code_des = root.find('err_code_des')
- return_msg = root.find('return_msg')
-
- error_details = f"return_code: {return_code}, "
- if err_code is not None:
- error_details += f"err_code: {err_code.text}, "
- if err_code_des is not None:
- error_details += f"err_code_des: {err_code_des.text}, "
- if return_msg is not None:
- error_details += f"return_msg: {return_msg.text}"
-
- raise Exception(f"微信支付下单失败: {error_details}")
-
- except Exception as e:
- raise e
-
-
-
-
-
-'''# dingdan/views.py
-import xml.etree.ElementTree as ET
-from django.db import transaction
-from django.http import HttpResponse
-from rest_framework.views import APIView
-from rest_framework import permissions
-
-from shangpin.models import Shangpin
-
-from yonghu.models import UserMain, UserBoss
-
-# views/pay_views.py - WechatPayNotifyView完整代码
-import xml.etree.ElementTree as ET
-from django.db import transaction
-from django.http import HttpResponse
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-from rest_framework.throttling import AnonRateThrottle
-from rest_framework.permissions import AllowAny
-
-from peizhi.models import Szjilu
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-import threading
-from utils.weixin_broadcast import WeixinBroadcastSender
-
-class WechatPayNotifyView(APIView):
- """微信支付回调接口 - 接收微信官方支付结果通知"""
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
-
- def post(self, request):
- try:
- # 1. 解析微信回调的XML数据
- xml_data = request.body.decode('utf-8')
- root = ET.fromstring(xml_data)
-
- # 2. 获取回调参数
- return_code = root.find('return_code').text
- result_code = root.find('result_code').text
- out_trade_no = root.find('out_trade_no').text # 订单ID
-
- # 3. 验证签名(重要安全步骤)
- if not self.verify_signature(request.body):
- response_xml = self.generate_response_xml('FAIL', '签名验证失败')
- return HttpResponse(response_xml, content_type='application/xml')
-
- # 4. 检查支付结果
- if return_code == 'SUCCESS' and result_code == 'SUCCESS':
- # 支付成功,处理订单
- self.handle_payment_success(out_trade_no)
- response_xml = self.generate_response_xml('SUCCESS', 'OK')
- return HttpResponse(response_xml, content_type='application/xml')
- else:
- # 支付失败,删除订单
- self.handle_payment_failure(out_trade_no)
- response_xml = self.generate_response_xml('SUCCESS', 'OK')
- return HttpResponse(response_xml, content_type='application/xml')
-
- except Exception as e:
- logger.error(f"支付回调处理异常: {str(e)}", exc_info=True)
- response_xml = self.generate_response_xml('FAIL', f'处理失败: {str(e)}')
- return HttpResponse(response_xml, content_type='application/xml')
-
- def verify_signature(self, xml_data):
- """验证微信回调签名"""
- try:
- # 这里需要实现微信支付签名验证逻辑
- # 根据你的微信支付配置实现具体验证
- return True # 暂时返回True,你需要根据实际情况实现
- except:
- return False
-
- def generate_response_xml(self, return_code, return_msg):
- """生成返回给微信的XML响应"""
- return f"""
-
-
-"""
-
- @transaction.atomic
- def handle_payment_success(self, dingdan_id):
- """处理支付成功的订单"""
- try:
- # 1. 查询订单信息
- dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
- if not dingdan:
- raise Exception('订单不存在')
-
- # 2. 检查订单状态是否为9(未支付)
- if dingdan.zhuangtai != 9:
- return
-
- # 3. 查询订单扩展表获取老板ID
- pingtaikuozhan = DingdanPingtai.objects.filter(dingdan=dingdan).first()
- if not pingtaikuozhan:
- raise Exception('订单扩展表不存在')
-
- laoban_id = pingtaikuozhan.laoban_id
-
- # 4. 根据是否指定打手更新订单状态
- if dingdan.zhiding_id and dingdan.zhiding_id.strip():
- dingdan.zhuangtai = 7
- else:
- dingdan.zhuangtai = 1
-
- dingdan.save()
-
- # 5. 更新老板扩展表
- user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
- if user_main:
- boss_profile = UserBoss.objects.filter(user=user_main).first()
- if boss_profile:
- boss_profile.zonge = (boss_profile.zonge or 0) + (dingdan.jine or 0)
- boss_profile.alldingdan = (boss_profile.alldingdan or 0) + 1
- boss_profile.save()
-
- # 6. 更新商品库存和销量
- shangpin_id = dingdan.shangpin_id
- if shangpin_id:
- shangpin = Shangpin.objects.filter(id=shangpin_id).first()
- if shangpin:
- current_kucun = getattr(shangpin, 'kucun', 0)
- if current_kucun > 0:
- shangpin.kucun = current_kucun - 1
- shangpin.save()
-
- jine = dingdan.jine
- self.update_szjilu(jine)
-
-
-
- 🟢【7. 新增】支付成功后的广播通知(异步,不阻塞主流程)
- from dingdan.tongzhi_tasks import dingdan_guangbo
-
- # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用)
- """order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self._get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': (dingdan.jieshao or '')[:50],
- }
- dingdan_guangbo.delay(order_info)""" # 瞬间返回,不阻塞请求
-
- # 🟢【7. 新增】支付成功后的广播通知(异步,不阻塞主流程)
- #self.broadcast_order_notification_async(dingdan)
-
- except Exception as e:
- raise Exception(f'处理支付成功订单失败: {str(e)}')
-
- def broadcast_order_notification_async(self, dingdan):
- """异步广播订单通知给所有关注用户"""
- try:
- def broadcast_task():
- try:
- # 构建订单信息
- order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self.get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': dingdan.jieshao[:50] if dingdan.jieshao else '新订单'
- }
-
- # 调用广播器
- sender = WeixinBroadcastSender()
- result = sender.broadcast_order(order_info)
-
- if result['success']:
- logger.info(f"【支付回调】订单广播完成: {result['msg']}")
- else:
- logger.warning(f"【支付回调】订单广播异常: {result['msg']}")
-
- except Exception as e:
- logger.error(f"【支付回调】广播任务异常: {str(e)}")
-
- # 启动异步线程
- thread = threading.Thread(target=broadcast_task)
- thread.daemon = True
- thread.start()
-
- except Exception as e:
- logger.error(f"启动广播线程异常: {str(e)}")
-
- # 🗑️【重要】原 send_notification_to_dashou 和 send_notification_to_all_dashous 方法不再需要
- # 可以注释掉或删除,因为现在统一用 broadcast_order_notification_async
-
- def get_game_type_name(self, leixing_id):
- """获取游戏类型名称(保持不变)"""
- try:
- game_type = ShangpinLeixing.objects.filter(id=leixing_id).first()
- return game_type.jieshao if game_type else '游戏订单'
- except Exception:
- return '游戏订单'
-
- def update_szjilu(self, jine):
- """更新收支记录表(保持不变)"""
- try:
- from decimal import Decimal
- jine_decimal = Decimal(str(jine)) if not isinstance(jine, Decimal) else jine
-
- with transaction.atomic():
- try:
- szjilu = Szjilu.objects.select_for_update().get(id=1)
- except Szjilu.DoesNotExist:
- szjilu = Szjilu.objects.create(
- id=1,
- zongsy=Decimal('0.00'),
- zongls=Decimal('0.00'),
- zongzc=Decimal('0.00'),
- jrzc=Decimal('0.00'),
- jrls=Decimal('0.00')
- )
- logger.info(f"创建ID=1的收支记录")
-
- szjilu.zongsy = szjilu.zongsy + jine_decimal
- szjilu.zongls = szjilu.zongls + jine_decimal
- szjilu.jrls = szjilu.jrls + jine_decimal
- szjilu.save()
-
- logger.info(f"收支记录更新成功: 订单金额={jine}")
-
- except Exception as e:
- logger.error(f"更新收支记录失败: {str(e)}")
-
- @transaction.atomic
- def handle_payment_failure(self, dingdan_id):
- """处理支付失败的订单(删除订单)(保持不变)"""
- try:
- dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
- if not dingdan:
- return
-
- DingdanPingtai.objects.filter(dingdan=dingdan).delete()
- dingdan.delete()
-
- except Exception as e:
- raise Exception(f'删除支付失败订单失败: {str(e)}')'''
-
-
-
-
-
-# dingdan/views.py
-'''class PaymentFailView(APIView):
- """
- 前端支付失败接口 - 处理用户主动取消或支付失败
- 需要token认证
- """
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 获取前端传递的字段
- dingdanid = request.data.get('dingdanid')
-
-
- # 2. 验证必填字段
- if not dingdanid:
- return Response({
- 'code': 1,
- 'msg': '订单ID不能为空',
- 'data': None
- })
-
- # 3. 查询订单
- dingdan = Dingdan.objects.filter(dingdan_id=dingdanid).first()
- if not dingdan:
- return Response({
- 'code': 2,
- 'msg': '订单不存在',
- 'data': None
- })
-
- # 4. 删除订单数据
- with transaction.atomic():
- # 删除订单扩展表
- DingdanPingtai.objects.filter(dingdan=dingdan).delete()
-
- # 删除订单主表
- dingdan.delete()
-
- # 5. 返回成功响应(与前端格式匹配)
- return Response({
- 'code': 0,
- 'msg': '订单已删除',
- 'data': None
- })
-
- except Exception as e:
- return Response({
- 'code': 99,
- 'msg': f'删除订单失败: {str(e)}',
- 'data': None
- })'''
-
-
-# dingdan/views.py
-'''class PaymentVerifyView(APIView):
- """
- 订单支付成功复查接口 - 前端支付成功后调用,确认订单状态
- 需要token认证
- """
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 获取前端传递的订单ID
-
- dingdanid = request.data.get('dingdanid')
-
-
- # 2. 验证必填字段
- if not dingdanid:
- return Response({
- 'code': 1,
- 'msg': '订单ID不能为空',
- 'data': None
- })
-
- # 3. 查询订单
- dingdan = Dingdan.objects.filter(dingdan_id=dingdanid).first()
- if not dingdan:
- return Response({
- 'code': 2,
- 'msg': '订单不存在',
- 'data': None
- })
-
- # 4. 检查订单状态
- zhuangtai = dingdan.zhuangtai
-
- if zhuangtai in [1, 7]:
- # 订单状态正常(1:待接单, 7:已指定打手待接单)
- return Response({
- 'code': 0,
- 'msg': '订单状态正常',
- 'data': {
- 'dingdanid': dingdanid,
- 'zhuangtai': zhuangtai
- }
- })
- else:
- # 订单状态异常
- error_msg = '订单状态异常,请联系管理员或客服'
-
- # 可以根据具体状态给出更详细的提示
- if zhuangtai == 9:
- error_msg = '订单未支付,请重新支付'
- elif zhuangtai == 2:
- error_msg = '订单已取消'
- elif zhuangtai == 3:
- error_msg = '订单已完成'
- # 其他状态...
-
- return Response({
- 'code': 3,
- 'msg': error_msg,
- 'data': {
- 'dingdanid': dingdanid,
- 'zhuangtai': zhuangtai
- }
- })
-
- except Exception as e:
- return Response({
- 'code': 99,
- 'msg': f'订单复查失败: {str(e)}',
- 'data': None
- })'''
-
-
-
-from yonghu.models import UserMain
-
-# dingdan/views.py
-
-from rest_framework.views import APIView
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.response import Response
-from django.db.models import Q
-
-from .models import Dingdan, DingdanPingtai
-
-
-
-
-
-
-class DingdanHuoquView(APIView):
- """订单获取接口"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 获取当前登录用户
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except Exception as e:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- })
-
- # 获取前端参数
- try:
- data = request.data
- zhuangtai_list = data.get('zhuangtai_list', [])
- page = int(data.get('page', 1))
- page_size = int(data.get('page_size', 10))
- except Exception as e:
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- })
-
- # 参数验证
- if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
- return Response({
- 'code': 3,
- 'msg': '状态列表不能为空',
- 'data': None
- })
-
- if page < 1:
- page = 1
- if page_size <= 0:
- page_size = 10
-
- try:
- # 查询扩展表,获取用户的所有订单ID
- pingtai_orders = DingdanPingtai.objects.filter(laoban_id=yonghuid)
- pingtai_order_ids = list(pingtai_orders.values_list('dingdan__dingdan_id', flat=True))
-
- if not pingtai_order_ids:
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': {
- 'list': [],
- 'has_more': False,
- 'total': 0
- }
- })
-
- # 查询主表
- status_query = Q()
- if zhuangtai_list != [1, 2, 3, 4, 5, 6, 7, 8]:
- status_query = Q(zhuangtai__in=zhuangtai_list)
-
- query = Q(dingdan_id__in=pingtai_order_ids) & status_query
- orders = Dingdan.objects.filter(query).order_by('-create_time')
- #total_orders = orders.count()
-
- # 手动分页逻辑
- start_index = (page - 1) * page_size
- end_index = start_index + page_size
-
- # 获取当前页的数据
- current_page_orders = orders[start_index:end_index]
-
- # 构建返回数据
- order_list = []
- for order in current_page_orders:
- order_data = {
- 'dingdanId': order.dingdan_id,
- 'zhuangtai': order.zhuangtai,
- 'jine': float(order.jine) if order.jine else 0.0,
- 'tupian': order.tupian or '',
- 'jieshao': order.jieshao or '',
- 'beizhu': order.beizhu or '',
- 'nicheng': order.nicheng or '',
- 'chuangjianshijian': order.create_time.strftime('%Y-%m-%d %H:%M:%S')
- }
- order_list.append(order_data)
-
-
- # 判断是否还有更多数据
- current_page_count = len(current_page_orders)
- has_more = current_page_count == page_size
-
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': {
- 'list': order_list,
- 'has_more': has_more,
- #'total': total_orders
- }
- })
-
- except Exception as e:
- return Response({
- 'code': 4,
- 'msg': '服务器内部错误',
- 'data': None
- })
-
-
-# dingdan/views.py
-
-from rest_framework.views import APIView
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.response import Response
-from django.db.models import Q
-import json
-
-from .models import Dingdan, DingdanPingtai, Dashoutupian
-
-
-class DingdanXiangqingView(APIView):
- """订单详情接口"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 获取当前登录用户
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except Exception as e:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- })
-
- # 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdanId', '')
- except Exception as e:
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- })
-
- # 参数验证
- if not dingdan_id:
- return Response({
- 'code': 3,
- 'msg': '订单ID不能为空',
- 'data': None
- })
-
- try:
- # 1. 验证订单是否属于当前用户
- # 查询平台订单扩展表,检查laoban_id是否等于当前用户
- pingtai_kuozhan = DingdanPingtai.objects.filter(
- laoban_id=yonghuid,
- dingdan__dingdan_id=dingdan_id
- ).first()
-
- if not pingtai_kuozhan:
- # 如果不是平台订单,检查商家订单
- from .models import DingdanShangjia
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- shangjia_id=yonghuid,
- dingdan__dingdan_id=dingdan_id
- ).first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 4,
- 'msg': '订单不存在或无权限查看',
- 'data': None
- })
-
- # 2. 查询订单主表
- dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
-
- if not dingdan:
- return Response({
- 'code': 5,
- 'msg': '订单不存在',
- 'data': None
- })
-
- # 3. 获取订单信息
- zhuangtai = dingdan.zhuangtai or 0
- zhiding_id = dingdan.zhiding_id or ''
- jiedan_dashou_id = dingdan.jiedan_dashou_id or ''
- dashou_liuyan = dingdan.dashou_liuyan or ''
-
- # 4. 查询打手交付图片
- dashoutupian_records = Dashoutupian.objects.filter(
- dingdan_id=dingdan_id
- ).order_by('create_time')[:9] # 最多取9条
-
- dashoujiaofu_list = []
- for record in dashoutupian_records:
- if record.tupian:
- dashoujiaofu_list.append(record.tupian)
-
- # 5. 构建返回数据(字段名与前端对应)
- response_data = {
- 'zhuangtai': zhuangtai, # 订单状态
- 'zhiding': zhiding_id, # 指定打手ID
- 'dashou': jiedan_dashou_id, # 服务打手ID
- 'dashouliuyan': dashou_liuyan, # 打手留言
- 'dashoujiaofu': dashoujiaofu_list # 打手交付图片列表
- }
-
- # 6. 返回成功响应
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': response_data
- })
-
- except Exception as e:
- print(f"订单详情查询失败: {str(e)}")
- return Response({
- 'code': 6,
- 'msg': '服务器内部错误',
- 'data': None
- })
-
-
-
-
-
-
-class JiedanView(APIView):
- """结单接口"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 获取当前登录用户
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except Exception as e:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- })
-
- # 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdanId', '')
- pingfen = int(data.get('pingfen', 0)) # 评分,默认0
- liuyan = data.get('liuyan', '') # 留言,默认空
- except Exception as e:
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- })
-
- # 参数验证
- if not dingdan_id:
- return Response({
- 'code': 3,
- 'msg': '订单ID不能为空',
- 'data': None
- })
-
- # 验证评分范围
- if pingfen < 0 or pingfen > 5:
- return Response({
- 'code': 4,
- 'msg': '评分必须在0-5分之间',
- 'data': None
- })
-
- # 使用事务确保数据一致性
- try:
- with transaction.atomic():
- # 1. 验证订单是否存在且属于当前用户
- pingtai_kuozhan = DingdanPingtai.objects.filter(
- laoban_id=yonghuid,
- dingdan__dingdan_id=dingdan_id
- ).select_related('dingdan').first()
-
- if not pingtai_kuozhan:
- return Response({
- 'code': 5,
- 'msg': '订单不存在或无权限操作',
- 'data': None
- })
-
- dingdan = pingtai_kuozhan.dingdan
-
- # 2. 验证订单状态(必须是结算中 - 状态8)
- if dingdan.zhuangtai != 8:
- return Response({
- 'code': 6,
- 'msg': '订单不在结算状态,无法结单',
- 'data': None
- })
-
- # 3. 获取订单相关数据
- dashou_fencheng = dingdan.dashou_fencheng or Decimal('0.00') # 打手分成
- jiedan_dashou_id = dingdan.jiedan_dashou_id or '' # 接单打手ID
- shangpin_id = dingdan.shangpin_id # 商品ID
-
- # 4. 更新平台扩展表的老板评价(如果有留言)
- if liuyan:
- pingtai_kuozhan.laoban_pingjia = liuyan
- pingtai_kuozhan.save()
-
- # 5. 如果有评分,更新评分表
- if pingfen > 0 and jiedan_dashou_id:
- # 使用filter查询,更健壮
- pingfen_records = Pingfen.objects.filter(dashou=jiedan_dashou_id)
-
- if pingfen_records.exists():
- # 已有记录,更新
- pingfen_record = pingfen_records.first()
- pingfen_record.zongfen += Decimal(str(pingfen))
- pingfen_record.cishu += 1
- pingfen_record.save()
- else:
- # 新记录,创建
- Pingfen.objects.create(
- dashou=jiedan_dashou_id,
- zongfen=Decimal(str(pingfen)),
- cishu=1
- )
-
- # 6. 更新商品真实销量(如果有商品ID)
- if shangpin_id:
- shangpin_records = Shangpin.objects.filter(id=shangpin_id)
-
- if shangpin_records.exists():
- shangpin = shangpin_records.first()
- # 检查字段是否存在
- if hasattr(shangpin, 'zhenshi_xiaoliang'):
- shangpin.zhenshi_xiaoliang = (shangpin.zhenshi_xiaoliang or 0) + 1
- shangpin.save()
-
- # ========== 新增:跨平台我方派单且有对方信息,直接通知对方并跳过本地结算 ==========
- if (dingdan.is_cross == 1
- and dingdan.partner_order_id
- and dingdan.partner_club_id):
- # 更新订单状态为已完成
- dingdan.zhuangtai = 3
- dingdan.clkf = current_user.phone if hasattr(current_user, 'phone') else yonghuid
- dingdan.save()
-
- # 事务提交后异步通知对方平台
- transaction.on_commit(
- lambda: self._notify_partner_jiedan(
- my_dingdan_id=dingdan.dingdan_id,
- partner_club_id=dingdan.partner_club_id,
- partner_order_id=dingdan.partner_order_id,
- new_status=3,
- current_user=current_user
- )
- )
-
- # 直接返回,跳过后续所有本地打手结算、评价、统计等
- return Response({
- 'code': 0,
- 'msg': '结单成功(已通知对方平台)',
- 'data': None
- })
- # ====================================================================
-
- # 7. 更新打手扩展表(如果有打手ID和分成)
- if jiedan_dashou_id and dashou_fencheng > 0:
- # 查询打手用户
- dashou_user = UserMain.objects.filter(yonghuid=jiedan_dashou_id).first()
-
- if dashou_user:
- # 查询打手扩展表
- dashou_kuozhan = UserDashou.objects.filter(user=dashou_user).first()
-
- if dashou_kuozhan:
- # 更新四个金额字段
- dashou_kuozhan.zonge = (dashou_kuozhan.zonge or Decimal('0.00')) + dashou_fencheng
- dashou_kuozhan.yue = (dashou_kuozhan.yue or Decimal('0.00')) + dashou_fencheng
- dashou_kuozhan.jinrishouyi = (dashou_kuozhan.jinrishouyi or Decimal(
- '0.00')) + dashou_fencheng
- dashou_kuozhan.jinyueshouyi = (dashou_kuozhan.jinyueshouyi or Decimal(
- '0.00')) + dashou_fencheng
-
- # 成交总量加1
- dashou_kuozhan.chengjiaozongliang = (dashou_kuozhan.chengjiaozongliang or 0) + 1
-
- # 状态改为数字1
- dashou_kuozhan.zhuangtai = 1
-
- dashou_kuozhan.save()
-
- # 8. 更新订单状态为已完成(状态3)
- dingdan.zhuangtai = 3
- dingdan.save()
-
- # 🔥 新增:调用打手每日统计(成交)
- try:
- update_dashou_daily_by_action(
- yonghuid=jiedan_dashou_id,
- amount=dashou_fencheng,
- action=2 # 2 表示成交(结算)
- )
- logger.info(f"打手 {jiedan_dashou_id} 每日统计更新成功")
- except Exception as e:
- logger.error(f"打手每日统计更新失败: {e}")
- # 不影响主流程
-
-
- # ========== 新增:商品和店铺每日统计 ==========
- try:
- # 商品每日统计(只要有商品ID就执行)
- if dingdan.shangpin_id:
- # 从扩展表获取已计算好的收益值(下单时已存储)
- pingtai_shouyi = Decimal('0.00')
- dianpu_shouyi = Decimal('0.00')
- if hasattr(dingdan, 'pingtai_kuozhan'):
- pingtai_shouyi = dingdan.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
- dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
-
- update_shangpin_daily_stat(
- shangpin_id=dingdan.shangpin_id,
- caozuo_leixing=2, # 2 = 结单
- dingdan_jiage=dingdan.jine,
- pingtai_shouyi=pingtai_shouyi,
- dianpu_zongshouyi=dianpu_shouyi
- )
- logger.info(f"商品统计更新完成,商品ID: {dingdan.shangpin_id}")
-
- # 店铺每日统计(仅当存在店铺ID时执行)
-
-
- # ========== 修改为 ==========
- # 店铺每日统计(包含上级分红)
- dianpu_id = None
- shangji_id = None
- shangji_fen = Decimal('0.00')
- if hasattr(dingdan, 'pingtai_kuozhan'):
- dianpu_id = dingdan.pingtai_kuozhan.dianpu_id
- shangji_id = dingdan.pingtai_kuozhan.shangji_dianpu_id
- shangji_fen = dingdan.pingtai_kuozhan.shangji_fenhong or Decimal('0.00')
- if dianpu_id:
- dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
- update_dianpu_daily_stat(
- dianpu_id=dianpu_id,
- caozuo_leixing=2, # 结算
- dingdan_jiage=dingdan.jine,
- dianpu_shouyi=dianpu_shouyi,
- shangji_dianpu_id=shangji_id, # 新增:上级店铺ID
- shangji_fenhong=shangji_fen # 新增:该单给上级的分红
- )
- logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}, 上级ID: {shangji_id}, 上级分红: {shangji_fen}")
-
-
-
-
- '''dianpu_id = None
- if hasattr(dingdan, 'pingtai_kuozhan'):
- dianpu_id = dingdan.pingtai_kuozhan.dianpu_id
- if dianpu_id:
- dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
- update_dianpu_daily_stat(
- dianpu_id=dianpu_id,
- caozuo_leixing=2, # 2 = 结单
- dingdan_jiage=dingdan.jine,
- dianpu_shouyi=dianpu_shouyi
- )
- logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}")'''
- except Exception as e:
- logger.error(f"商品/店铺统计更新失败: {e}")
- logger.error(traceback.format_exc())
- # 统计失败不影响主流程
- # =============================================
-
- # 12. 更新平台收益流水(仅在平台发单时)
- if dingdan.fadan_pingtai == 1:
- try:
- from dingdan.utils import update_platform_profit # 导入函数
- update_platform_profit(dingdan_id)
- except Exception as e:
- # 平台收益更新失败不影响主流程,但记录日志
- logger.error(f"强制结单后更新平台收益失败: {str(e)}")
-
- # 9. 返回成功响应
- return Response({
- 'code': 0,
- 'msg': '结单成功',
- 'data': None
- })
-
- except Exception as e:
-
- # 事务会自动回滚
- return Response({
- 'code': 7,
- 'msg': '结单失败,服务器内部错误',
- 'data': None
- })
-
-
- def _notify_partner_jiedan(self, my_dingdan_id, partner_club_id, partner_order_id, new_status, current_user):
- """
- 通知对方俱乐部我方订单已完成
- 参照 CrossPlatformForceCompleteView._notify_partner_order_status 实现
- """
- try:
- # 1. 获取我方俱乐部ID
- from .models import CrossPlatformOrderData
- my_club_config = ClubConfig.objects.filter(is_self=1).first()
- if not my_club_config:
- logger.error("未配置我方俱乐部信息,无法通知对方")
- return
- self_club_id = my_club_config.club_id
-
- # 2. 查询对方服务器域名
- club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
- partner_domain = club_rel.partner_domain
- if not partner_domain:
- logger.error(f"对方俱乐部 {partner_club_id} 未配置域名")
- return
-
- # 3. 构造通知请求
- url = partner_domain.rstrip('/') + '/houtai/dfddtk'
- payload = {
- 'order_id': partner_order_id, # 在对方看来这是他们的订单ID
- 'club_id': self_club_id,
- 'status': new_status
- }
- resp = requests.post(url, json=payload, timeout=3)
- if resp.status_code == 200:
- resp_json = resp.json()
- if resp_json.get('code') == 0:
- # 4. 更新跨平台订单扩展表(记录对方状态)
- CrossPlatformOrderData.objects.update_or_create(
- dingdan_id=my_dingdan_id, # 我方订单ID
- defaults={
- 'partner_order_id': partner_order_id,
- 'partner_club_id': partner_club_id,
- 'partner_order_status': new_status,
- 'partner_dashou_id': '',
- 'partner_amount': Decimal('0')
- }
- )
- logger.info(f"老板结单通知对方成功: {partner_order_id} -> {new_status}")
- else:
- logger.warning(f"对方接口返回错误: {resp_json.get('msg')}")
- else:
- logger.warning(f"对方接口HTTP错误: {resp.status_code}")
- except requests.exceptions.Timeout:
- logger.error(f"通知对方平台超时: {partner_club_id}")
- except Exception as e:
- logger.error(f"通知对方平台异常: {str(e)}")
-
-
-
-
-
-
-
-from dengji.models import DashouBiaoxian
-from dengji.services import record_dashou_biaoxian
-
-
-
-
-
-
-class DingdanXiangqingView2(APIView):
- """订单详情接口(老板端)- 返回完整评价信息"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- try:
- yonghuid = request.user.yonghuid
- except Exception:
- return Response({'code': 1, 'msg': '用户认证失败', 'data': None})
-
- dingdan_id = request.data.get('dingdanId', '')
- if not dingdan_id:
- return Response({'code': 2, 'msg': '订单ID不能为空', 'data': None})
-
- try:
- # 权限验证
- pingtai_ext = DingdanPingtai.objects.filter(
- laoban_id=yonghuid,
- dingdan__dingdan_id=dingdan_id
- ).select_related('dingdan').first()
-
- if not pingtai_ext:
- return Response({'code': 3, 'msg': '订单不存在或无权限查看', 'data': None})
-
- order = pingtai_ext.dingdan
- zhuangtai = order.zhuangtai or 0
- jiedan_dashou_id = order.jiedan_dashou_id or ''
- dashou_liuyan = order.dashou_liuyan or ''
- update_time = order.update_time.strftime('%Y-%m-%d %H:%M:%S') if order.update_time else ''
-
- # 交付图片
- dashoujiaofu_list = list(
- Dashoutupian.objects.filter(dingdan_id=dingdan_id)
- .order_by('create_time')[:9]
- .values_list('tupian', flat=True)
- )
-
- # 打手信息
- dashou_nicheng = ''
- dashou_touxiang = ''
- jiedan_time = ''
- tijiao_time = ''
- if jiedan_dashou_id:
- dashou_user = UserMain.objects.filter(yonghuid=jiedan_dashou_id).first()
- if dashou_user:
- dashou_touxiang = dashou_user.avatar or ''
- try:
- dashou_nicheng = dashou_user.dashou_profile.nicheng or ''
- except UserDashou.DoesNotExist:
- pass
- biaoxian = DashouBiaoxian.objects.filter(
- dingdan_id=dingdan_id,
- dashou_id=jiedan_dashou_id
- ).first()
- if biaoxian:
- if biaoxian.jiedan_time:
- jiedan_time = biaoxian.jiedan_time.strftime('%Y-%m-%d %H:%M:%S')
- if biaoxian.tijiao_time:
- tijiao_time = biaoxian.tijiao_time.strftime('%Y-%m-%d %H:%M:%S')
-
- # 老板自己的评价(如果已经结算过,可能已有评价)
- laoban_pingjia = pingtai_ext.laoban_pingjia or ''
- shangxian_sudu = None
- fuwu_taidu = None
- fanche_cishu = None
- if jiedan_dashou_id and zhuangtai in [3, 4, 5, 6]: # 已完成或退款相关状态才可能有评价
- biaoxian = DashouBiaoxian.objects.filter(
- dingdan_id=dingdan_id,
- dashou_id=jiedan_dashou_id
- ).first()
- if biaoxian:
- shangxian_sudu = biaoxian.shangxian_sudu
- fuwu_taidu = biaoxian.fuwu_taidu
- fanche_cishu = biaoxian.fanche_cishu
-
- data = {
- 'zhuangtai': zhuangtai,
- 'dashou': jiedan_dashou_id,
- 'dashou_nicheng': dashou_nicheng,
- 'dashou_touxiang': dashou_touxiang,
- 'dashouliuyan': dashou_liuyan,
- 'dashoujiaofu': dashoujiaofu_list,
- 'update_time': update_time,
- 'jiedan_time': jiedan_time,
- 'tijiao_time': tijiao_time,
- 'laoban_pingjia': laoban_pingjia,
- 'shangxian_sudu': shangxian_sudu, # 1-5 或 null
- 'fuwu_taidu': fuwu_taidu, # 1-5 或 null
- 'fanche_cishu': fanche_cishu # int 或 null
- }
-
- return Response({'code': 0, 'msg': '成功', 'data': data})
-
- except Exception as e:
- logger.error(f"订单详情查询异常: {e}", exc_info=True)
- return Response({'code': 4, 'msg': '服务器内部错误', 'data': None})
-
-
-
-class JiedanView2(APIView):
- """结单接口(老板端)- 接收全新评价字段并写入表现表"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 1. 用户认证
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except Exception:
- return Response({'code': 1, 'msg': '用户认证失败', 'data': None})
-
- # 2. 获取参数(字段名与前端完全一致)
- try:
- data = request.data
- dingdan_id = data.get('dingdanId', '')
- fanche_cishu = data.get('fanche_cishu') # 前端传的字符串或数字,后端转为整数
- shangxian_sudu = data.get('shangxian_sudu')
- fuwu_taidu = data.get('fuwu_taidu')
- liuyan = data.get('liuyan', '')
- except Exception:
- return Response({'code': 2, 'msg': '参数格式错误', 'data': None})
-
- # 参数校验
- if not dingdan_id:
- return Response({'code': 3, 'msg': '订单ID不能为空', 'data': None})
-
- # 转换并校验必填评价字段
- try:
- fanche_cishu = int(fanche_cishu) if fanche_cishu is not None else None
- shangxian_sudu = int(shangxian_sudu) if shangxian_sudu is not None else None
- fuwu_taidu = int(fuwu_taidu) if fuwu_taidu is not None else None
- except (ValueError, TypeError):
- return Response({'code': 4, 'msg': '评价参数必须为整数', 'data': None})
-
- if fanche_cishu is None:
- return Response({'code': 5, 'msg': '翻车次数不能为空', 'data': None})
- if not (1 <= shangxian_sudu <= 5 and 1 <= fuwu_taidu <= 5):
- return Response({'code': 6, 'msg': '评价分数必须在1-5之间', 'data': None})
-
- # 使用事务保证数据一致性
- try:
- with transaction.atomic():
- # 3. 权限验证:订单属于当前老板
- pingtai_ext = DingdanPingtai.objects.filter(
- laoban_id=yonghuid,
- dingdan__dingdan_id=dingdan_id
- ).select_related('dingdan').first()
-
- if not pingtai_ext:
- return Response({'code': 7, 'msg': '订单不存在或无权限操作', 'data': None})
-
- order = pingtai_ext.dingdan
-
- # 4. 状态校验:必须是结算中(状态8)
- if order.zhuangtai != 8:
- return Response({'code': 8, 'msg': '订单不在结算状态', 'data': None})
-
- # 5. 提取订单关键数据
- dashou_fencheng = order.dashou_fencheng or Decimal('0.00')
- jiedan_dashou_id = order.jiedan_dashou_id or ''
- shangpin_id = order.shangpin_id
-
- # 6. 保存老板评价(兼容旧逻辑)
- if liuyan:
- pingtai_ext.laoban_pingjia = liuyan
- pingtai_ext.save()
-
- # 7. 更新评分表(用服务态度分作为综合评分,保留原有统计)
- if jiedan_dashou_id and fuwu_taidu:
- pingfen_records = Pingfen.objects.filter(dashou=jiedan_dashou_id)
- if pingfen_records.exists():
- pf = pingfen_records.first()
- pf.zongfen += Decimal(str(fuwu_taidu))
- pf.cishu += 1
- pf.save()
- else:
- Pingfen.objects.create(
- dashou=jiedan_dashou_id,
- zongfen=Decimal(str(fuwu_taidu)),
- cishu=1
- )
-
- # 8. 更新商品真实销量
- if shangpin_id:
- shangpin_qs = Shangpin.objects.filter(id=shangpin_id)
- if shangpin_qs.exists():
- sp = shangpin_qs.first()
- if hasattr(sp, 'zhenshi_xiaoliang'):
- sp.zhenshi_xiaoliang = (sp.zhenshi_xiaoliang or 0) + 1
- sp.save()
-
- # 9. 更新打手收益与状态
- if jiedan_dashou_id and dashou_fencheng > 0:
- dashou_user = UserMain.objects.filter(yonghuid=jiedan_dashou_id).first()
- if dashou_user:
- try:
- dashou_ext = dashou_user.dashou_profile
- dashou_ext.zonge = (dashou_ext.zonge or Decimal('0.00')) + dashou_fencheng
- dashou_ext.yue = (dashou_ext.yue or Decimal('0.00')) + dashou_fencheng
- dashou_ext.jinrishouyi = (dashou_ext.jinrishouyi or Decimal('0.00')) + dashou_fencheng
- dashou_ext.jinyueshouyi = (dashou_ext.jinyueshouyi or Decimal('0.00')) + dashou_fencheng
- dashou_ext.chengjiaozongliang = (dashou_ext.chengjiaozongliang or 0) + 1
- dashou_ext.zhuangtai = 1
- dashou_ext.save()
- except UserDashou.DoesNotExist:
- pass
-
- # 10. 更新订单状态为已完成
- order.zhuangtai = 3
- order.save()
-
- # 11. 打手每日统计(成交)
- try:
- update_dashou_daily_by_action(
- yonghuid=jiedan_dashou_id,
- amount=dashou_fencheng,
- action=2 # 2=结算/成交
- )
- except Exception as e:
- logger.error(f"打手每日统计失败: {e}")
-
- # 12. 商品/店铺每日统计
- try:
- if shangpin_id:
- # 从扩展表取已计算收益
- pingtai_shouyi = Decimal('0.00')
- dianpu_shouyi = Decimal('0.00')
- if hasattr(order, 'pingtai_kuozhan'):
- pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
- dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
-
- update_shangpin_daily_stat(
- shangpin_id=shangpin_id,
- caozuo_leixing=2,
- dingdan_jiage=order.jine,
- pingtai_shouyi=pingtai_shouyi,
- dianpu_zongshouyi=dianpu_shouyi
- )
-
- # 店铺统计(含上级分红)
- if hasattr(order, 'pingtai_kuozhan'):
- dianpu_id = order.pingtai_kuozhan.dianpu_id
- shangji_id = order.pingtai_kuozhan.shangji_dianpu_id
- shangji_fen = order.pingtai_kuozhan.shangji_fenhong or Decimal('0.00')
- if dianpu_id:
- update_dianpu_daily_stat(
- dianpu_id=dianpu_id,
- caozuo_leixing=2,
- dingdan_jiage=order.jine,
- dianpu_shouyi=dianpu_shouyi,
- shangji_dianpu_id=shangji_id,
- shangji_fenhong=shangji_fen
- )
- except Exception as e:
- logger.error(f"商品/店铺统计失败: {e}")
-
- # 13. 平台收益流水(仅平台发单)
- if order.fadan_pingtai == 1:
- try:
- from dingdan.utils import update_platform_profit # 导入函数
- update_platform_profit(dingdan_id)
- except Exception as e:
- logger.error(f"平台收益更新失败: {e}")
-
- # 14. 🆕 写入打手表现表(结算评价)
- if jiedan_dashou_id:
- record_dashou_biaoxian(
- dingdan_id=dingdan_id,
- dashou_id=jiedan_dashou_id,
- xingwei=3, # 3=结算
- fanche_cishu=fanche_cishu,
- shangxian_sudu=shangxian_sudu,
- fuwu_taidu=fuwu_taidu
- # bankuai 暂不传入,后续可从订单商品关联
- )
-
- # 15. 返回成功
- return Response({'code': 0, 'msg': '结单成功', 'data': None})
-
- except Exception as e:
- logger.error(f"结单事务异常: {e}", exc_info=True)
- return Response({'code': 9, 'msg': '服务器内部错误,结单失败', 'data': None})
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from django.db.models import Prefetch
-from dengji.models import Chenghao
-
-
-class ShangpinLeixingView(APIView):
- """
- 获取商家可用的商品类型列表(含关联板块的称号)
- POST /dingdan/sjspleixing
- """
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
-
- try:
- # 1. 查询审核通过且抢单要求类型为1的商品类型,按排序权重升序
- shangpin_queryset = ShangpinLeixing.objects.filter(
- yaoqiuleixing=1,
- shenhezhuangtai=1
- ).select_related('bankuai').order_by('paixu')
-
- # 2. 收集所有涉及到的板块ID
- bankuai_ids = shangpin_queryset.values_list('bankuai_id', flat=True).distinct()
-
- # 3. 查询这些板块下的所有称号(每个称号绑定到一个板块)
- chenghao_list = Chenghao.objects.filter(
- bankuai_id__in=bankuai_ids
- ).values('id', 'mingcheng', 'texiao_miaoshu', 'bankuai_id')
-
- # 4. 构建 板块ID -> 称号列表 的映射(确保准确关联)
- bankuai_chenghao_map = {}
- for ch in chenghao_list:
- bid = ch['bankuai_id']
- if bid not in bankuai_chenghao_map:
- bankuai_chenghao_map[bid] = []
- bankuai_chenghao_map[bid].append({
- 'id': ch['id'],
- 'mingcheng': ch['mingcheng'],
- 'texiao_json': ch['texiao_miaoshu'] # 前端 chenghao-tag 组件需要
- })
-
- # 5. 组装最终数据
- data_list = []
- for sp in shangpin_queryset:
- item = {
- 'id': sp.id,
- 'yaoqiuleixing': sp.yaoqiuleixing,
- 'huiyuan_id': sp.huiyuan_id,
- 'jieshao': sp.jieshao,
- # 当前类型所属板块的称号列表(无板块则为空数组)
- 'chenghaoList': bankuai_chenghao_map.get(sp.bankuai_id, [])
- }
- data_list.append(item)
-
- return Response({
- 'code': 200,
- 'msg': '获取成功',
- 'data': data_list
- })
-
- except Exception as e:
- logger.error(f"获取商品类型失败: {str(e)}", exc_info=True)
- return Response({'code': 500, 'msg': '服务器内部错误', 'data': {}}, status=500)
-
-import threading
-from utils.weixin_broadcast import WeixinBroadcastSender
-
-
-
-
-
-'''class ShangjiaPaifaView(APIView):
- """商家派单接口"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 记录请求日志
- logger.info(f"商家派单请求: user_id={request.user.yonghuid}, data={request.data}")
-
- # 1. 获取前端参数
- data = request.data
- shangpin_type_id = data.get('shangpinTypeId')
- huiyuan_id = data.get('huiyuanId')
- yaoqiuleixing = data.get('yaoqiuleixing')
- dingdan_jieshao = data.get('dingdanJieshao', '').strip()
- dingdan_beizhu = data.get('dingdanBeizhu', '').strip()
- laoban_name = data.get('laobanName', '').strip()
- zhiding_uid = data.get('zhidingUid', '').strip()
- jiage_str = data.get('jiage')
-
- # 2. 基本参数验证
- if not all([shangpin_type_id, huiyuan_id, dingdan_jieshao, laoban_name, jiage_str]):
- logger.warning(f"参数缺失: {request.data}")
- return Response({'code': 400, 'msg': '缺少必填参数', 'data': None}, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- jiage = float(jiage_str)
- if jiage < 1 or jiage > 10000:
- return Response({'code': 400, 'msg': '价格需在1-10000元之间', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
- if '.' in jiage_str and len(jiage_str.split('.')[1]) > 2:
- return Response({'code': 400, 'msg': '最多支持两位小数', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
- except ValueError:
- return Response({'code': 400, 'msg': '价格格式错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 获取商家扩展信息
- try:
- shangjia = request.user.shop_profile
- except UserShangjia.DoesNotExist:
- logger.warning(f"用户不是商家: user_id={request.user.yonghuid}")
- return Response({'code': 403, 'msg': '您不是商家或商家信息未完善', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
-
- # 4. 验证商家状态
- if shangjia.zhuangtai != 1:
- logger.warning(f"商家状态异常: user_id={request.user.yonghuid}, status={shangjia.zhuangtai}")
- return Response({'code': 403, 'msg': '商家账号状态异常,请联系管理员', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
-
- # 5. 验证商家余额
- if jiage > shangjia.yue:
- logger.warning(f"余额不足: user_id={request.user.yonghuid}, 余额={shangjia.yue}, 需要={jiage}")
- return Response({'code': 400, 'msg': f'余额不足,当前余额{shangjia.yue}元', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- # 6. 验证指定打手
- zhiding_dashou = None
- dashou_user_main = None
- if zhiding_uid:
- try:
- zhiding_user = UserMain.objects.get(yonghuid=zhiding_uid)
- dashou_user_main = zhiding_user
-
- try:
- zhiding_dashou = zhiding_user.dashou_profile
- except UserDashou.DoesNotExist:
- logger.warning(f"指定用户不是打手: zhiding_uid={zhiding_uid}")
- return Response({'code': 400, 'msg': '指定用户不是打手', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- if zhiding_dashou.zhanghaozhuangtai != 1:
- logger.warning(
- f"指定打手状态异常: zhiding_uid={zhiding_uid}, status={zhiding_dashou.zhanghaozhuangtai}")
- return Response({'code': 400, 'msg': '指定打手账号状态异常', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- except UserMain.DoesNotExist:
- logger.warning(f"指定用户不存在: zhiding_uid={zhiding_uid}")
- return Response({'code': 400, 'msg': '指定用户不存在', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- # 7. 查询利率
- try:
- lilu_obj = Lilubiao.objects.get(fadanpingtai='3')
- lilu = float(lilu_obj.lilu)
- if lilu <= 0:
- lilu = 1
- except Lilubiao.DoesNotExist:
- logger.warning("利率表不存在,使用默认值1")
- lilu = 1
-
- dashou_fencheng = round(jiage * lilu, 2)
-
- # 8. 生成订单ID
- def generate_dingdan_id():
- import time
- import random
- timestamp = int(time.time() * 1000)
- process_id = os.getpid() % 1000
- random_num = random.randint(1000, 9999)
- return f"SJ{timestamp}{process_id:03d}{random_num}"
-
- # 9. 使用事务
- try:
- with transaction.atomic():
- # 创建订单主表
- dingdan = Dingdan.objects.create(
- dingdan_id=generate_dingdan_id(),
- zhuangtai=7 if zhiding_dashou else 1,
- fadan_pingtai=2,
- jine=jiage,
- dashou_fencheng=dashou_fencheng,
- zhiding_id=zhiding_uid if zhiding_dashou else '',
- shangpin_id=0,
- leixing_id=int(shangpin_type_id),
- yaoqiuleixing=int(yaoqiuleixing) if yaoqiuleixing else 1,
- huiyuan_id=str(huiyuan_id),
- jieshao=dingdan_jieshao[:2000],
- beizhu=dingdan_beizhu[:1000],
- nicheng=laoban_name[:50],
- tupian=request.user.avatar,
- user1_id=f"Sj{request.user.yonghuid}"
- )
-
- # 创建商家订单扩展表
- DingdanShangjia.objects.create(
- dingdan=dingdan,
- shangjia_id=request.user.yonghuid,
- sjnicheng=request.user.boss_profile.nickname,
- )
-
- # 更新商家统计信息
- from django.db.models import F
- UserShangjia.objects.filter(id=shangjia.id).update(
- fabu=F('fabu') + 1,
- yue=F('yue') - jiage,
- jinridingdan=F('jinridingdan') + 1,
- jinriliushui=F('jinriliushui') + jiage,
- jinyuedingdan=F('jinyuedingdan') + 1,
- jinyueliushui=F('jinyueliushui') + jiage,
- )
-
- # 🟢【10. 修改】发送模板消息 - 现在改为广播给所有用户
- from houtai.utils import update_shangjia_daily
-
- # ✅ 新方法(行为驱动,更安全)
- update_shangjia_daily(
- yonghuid=request.user.yonghuid,
- amount=Decimal(str(jiage)),
- action=1 # 1 = 派发
- )
-
-
-
-
-
-
-
-
-
- #🟢【7. 新增】支付成功后的广播通知(异步,不阻塞主流程)
- from dingdan.tongzhi_tasks import dingdan_guangbo
-
- # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用)
- order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self._get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': (dingdan.jieshao or '')[:50],
- }
- dingdan_guangbo.delay(order_info) # 瞬间返回,不阻塞请求
-
- sync_order_to_partners(dingdan.dingdan_id)
-
-
-
-
-
-
-
-
- #self.broadcast_order_notification_async(dingdan)
-
- # 记录成功日志
- logger.info(f"商家派单成功: order_id={dingdan.dingdan_id}, amount={jiage}, zhiding={zhiding_uid}")
-
- return Response({'code': 200, 'msg': '派单成功', 'data': {'jine': str(dingdan.jine)}},
- status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f"商家派单异常: {str(e)}", exc_info=True)
- return Response({'code': 500, 'msg': '系统错误,派单失败', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- def broadcast_order_notification_async(self, dingdan):
- """异步广播订单通知给所有关注用户"""
- try:
- def broadcast_task():
- try:
- # 构建订单信息
- order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self.get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': dingdan.jieshao[:50] if dingdan.jieshao else '新订单'
- }
-
- # 调用广播器
- sender = WeixinBroadcastSender()
- result = sender.broadcast_order(order_info)
-
- if result['success']:
- logger.info(f"【商家派单】订单广播完成: {result['msg']}")
- else:
- logger.warning(f"【商家派单】订单广播异常: {result['msg']}")
-
- except Exception as e:
- logger.error(f"【商家派单】广播任务异常: {str(e)}")
-
- # 启动异步线程
- thread = threading.Thread(target=broadcast_task)
- thread.daemon = True
- thread.start()
-
- except Exception as e:
- logger.error(f"启动广播线程异常: {str(e)}")
-
- # 🗑️【重要】原 send_template_notification_async 方法不再需要
- # 可以注释掉或删除,因为现在统一用 broadcast_order_notification_async
-
- def _get_game_type_name(self, leixing_id):
- try:
- from shangpin.models import ShangpinLeixing
- gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
- return gt.jieshao if gt else '游戏订单'
- except Exception as e:
- logger.error(f"获取游戏类型名称异常: {e}")
- return '游戏订单'''''
-
-
-
-from dengji.models import DingdanBiaoqian,Chenghao
-
-
-import os
-import threading
-import logging
-from decimal import Decimal
-from django.db import transaction
-from django.db.models import F
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from rest_framework import status
-
-from yonghu.models import UserMain, UserShangjia, UserDashou
-
-from houtai.utils import update_shangjia_daily
-
-
-# 导入 Celery 广播任务
-from dingdan.tongzhi_tasks import dingdan_guangbo
-
-logger = logging.getLogger(__name__)
-
-
-
-"""class ShangjiaPaifaView(APIView):
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- logger.info(f"商家派单请求: user_id={request.user.yonghuid}, data={request.data}")
-
- try:
- data = request.data
-
- # ----- 1. 获取原始参数(保持原类型)-----
- shangpin_type_id = data.get('shangpinTypeId')
- huiyuan_id = data.get('huiyuanId')
- yaoqiuleixing = data.get('yaoqiuleixing')
- dingdan_jieshao_raw = data.get('dingdanJieshao', '')
- dingdan_beizhu_raw = data.get('dingdanBeizhu', '')
- laoban_name_raw = data.get('laobanName', '')
- zhiding_uid_raw = data.get('zhidingUid', '')
- jiage_str = data.get('jiage')
-
- label_id = data.get('labelId') # 整数或 None
- commission_enabled = data.get('commissionEnabled', False)
- commission_value_raw = data.get('commissionValue')
-
- # ----- 2. 字符串字段安全处理 -----
- dingdan_jieshao = str(dingdan_jieshao_raw).strip()
- dingdan_beizhu = str(dingdan_beizhu_raw).strip()
- laoban_name = str(laoban_name_raw).strip()
- zhiding_uid = str(zhiding_uid_raw).strip()
- commission_value_str = str(commission_value_raw).strip() if commission_value_raw is not None else ''
-
- # ----- 3. 必填校验 -----
- if not all([shangpin_type_id, huiyuan_id, dingdan_jieshao, laoban_name, jiage_str]):
- logger.warning(f"参数缺失: {request.data}")
- return Response({'code': 400, 'msg': '缺少必填参数'})
-
- # ----- 4. 价格验证 -----
- try:
- jiage = float(jiage_str)
- if jiage < 1 or jiage > 10000:
- return Response({'code': 400, 'msg': '价格需在1-10000元之间'})
- if '.' in str(jiage_str) and len(str(jiage_str).split('.')[1]) > 2:
- return Response({'code': 400, 'msg': '最多支持两位小数'})
- except ValueError:
- return Response({'code': 400, 'msg': '价格格式错误'})
-
- # ----- 5. 商品类型 -----
- try:
- shangpin_type = ShangpinLeixing.objects.get(id=shangpin_type_id)
- except ShangpinLeixing.DoesNotExist:
- logger.error(f"商品类型不存在: {shangpin_type_id}")
- return Response({'code': 400, 'msg': '商品类型不存在'})
-
- # ----- 6. 押金 / 会员模式决定(修正版)-----
- # 先根据商品类型中的配置填充默认值
- final_yaoqiu_type = shangpin_type.yaoqiuleixing if shangpin_type.yaoqiuleixing else 1
- final_yongjin = shangpin_type.yongjin if shangpin_type.yongjin else None
- final_huiyuan_id = shangpin_type.huiyuan_id if shangpin_type.huiyuan_id else ''
-
- # 如果前端明确开启了佣金且金额大于0,则覆盖为押金模式
- if commission_enabled and commission_value_str:
- try:
- cv = float(commission_value_str)
- if cv > 0:
- final_yaoqiu_type = 2
- final_yongjin = Decimal(str(cv))
- final_huiyuan_id = ''
- logger.info(f"前端开启押金模式: 佣金={cv}")
- else:
- # 佣金金额为0,视为无效,保持商品类型默认
- logger.warning("前端传佣金为0,无效,使用商品类型默认")
- except ValueError:
- logger.warning("佣金格式错误,使用商品类型默认")
- else:
- logger.info(f"使用商品类型默认模式: 类型={final_yaoqiu_type}, huiyuan_id={final_huiyuan_id}, yongjin={final_yongjin}")
-
- # ----- 7. 称号验证(label_id 是整数或 None)-----
- chenghao_obj = None
- if label_id is not None:
- try:
- chenghao_obj = Chenghao.objects.get(id=int(label_id))
- logger.info(f"订单将关联称号: {chenghao_obj.mingcheng}")
- except (Chenghao.DoesNotExist, ValueError, TypeError):
- logger.warning(f"称号不存在或ID无效: {label_id}")
- return Response({'code': 400, 'msg': '所选标签不存在'})
-
- # ----- 8. 商家信息 -----
- try:
- shangjia = request.user.shop_profile
- except UserShangjia.DoesNotExist:
- logger.warning(f"用户不是商家: {request.user.yonghuid}")
- return Response({'code': 403, 'msg': '您不是商家'})
-
- if shangjia.zhuangtai != 1:
- logger.warning(f"商家状态异常: {shangjia.zhuangtai}")
- return Response({'code': 403, 'msg': '商家状态异常'})
-
- if jiage > shangjia.yue:
- logger.warning(f"余额不足: {shangjia.yue} < {jiage}")
- return Response({'code': 400, 'msg': f'余额不足,当前余额{shangjia.yue}元'})
-
- # ----- 9. 指定打手 -----
- zhiding_dashou = None
- if zhiding_uid:
- try:
- zhiding_user = UserMain.objects.get(yonghuid=zhiding_uid)
- zhiding_dashou = zhiding_user.dashou_profile
- if zhiding_dashou.zhanghaozhuangtai != 1:
- return Response({'code': 400, 'msg': '指定打手状态异常'})
- except UserMain.DoesNotExist:
- return Response({'code': 400, 'msg': '指定用户不存在'})
- except UserDashou.DoesNotExist:
- return Response({'code': 400, 'msg': '指定用户不是打手'})
-
- # ----- 10. 利率 -----
- try:
- lilu_obj = Lilubiao.objects.get(fadanpingtai='3')
- lilu = float(lilu_obj.lilu) if lilu_obj.lilu > 0 else 1
- except Lilubiao.DoesNotExist:
- lilu = 1
- dashou_fencheng = round(jiage * lilu, 2)
-
- # ----- 11. 生成订单ID -----
- def generate_dingdan_id():
- import time, random
- ts = int(time.time() * 1000)
- pid = os.getpid() % 1000
- rnd = random.randint(1000, 9999)
- return f"SJ{ts}{pid:03d}{rnd}"
-
- # ----- 12. 事务:创建订单、更新商家统计 -----
- dingdan = None
- try:
- with transaction.atomic():
- dingdan = Dingdan.objects.create(
- dingdan_id=generate_dingdan_id(),
- zhuangtai=7 if zhiding_dashou else 1,
- fadan_pingtai=2,
- jine=jiage,
- dashou_fencheng=dashou_fencheng,
- zhiding_id=zhiding_uid if zhiding_dashou else '',
- shangpin_id=0,
- leixing_id=int(shangpin_type_id),
- yaoqiuleixing=final_yaoqiu_type,
- yongjin=final_yongjin,
- huiyuan_id=final_huiyuan_id,
- jieshao=dingdan_jieshao[:2000],
- beizhu=dingdan_beizhu[:1000],
- nicheng=laoban_name[:50],
- tupian=request.user.avatar,
- user1_id=f"Sj{request.user.yonghuid}"
- )
- logger.info(f"订单主表创建: {dingdan.dingdan_id}")
-
- DingdanShangjia.objects.create(
- dingdan=dingdan,
- shangjia_id=request.user.yonghuid,
- sjnicheng=shangjia.nicheng,
- )
-
- if chenghao_obj:
- DingdanBiaoqian.objects.create(dingdan=dingdan, chenghao=chenghao_obj)
- logger.info(f"订单关联称号: {chenghao_obj.mingcheng}")
-
- # 更新商家统计
- UserShangjia.objects.filter(id=shangjia.id).update(
- fabu=F('fabu') + 1,
- yue=F('yue') - jiage,
- jinridingdan=F('jinridingdan') + 1,
- jinriliushui=F('jinriliushui') + jiage,
- jinyuedingdan=F('jinyuedingdan') + 1,
- jinyueliushui=F('jinyueliushui') + jiage,
- )
-
- update_shangjia_daily(
- yonghuid=request.user.yonghuid,
- amount=Decimal(str(jiage)),
- action=1
- )
- # 事务提交,订单已持久化
- except Exception as e:
- logger.error(f"订单创建事务失败: {str(e)}", exc_info=True)
- return Response({'code': 500, 'msg': '订单创建失败,请稍后重试'})
-
- # ========== 以下操作均在事务外,避免异步任务查不到订单 ==========
- try:
- sync_order_to_partners(dingdan.dingdan_id)
- except Exception as e:
- logger.error(f"跨平台同步失败: {e}", exc_info=True)
-
- try:
- order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self._get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': (dingdan.jieshao or '')[:50],
- }
- dingdan_guangbo.delay(order_info)
- logger.info(f"已提交广播任务: {dingdan.dingdan_id}")
- except Exception as e:
- logger.error(f"广播任务提交失败: {e}", exc_info=True)
-
- logger.info(f"商家派单成功: {dingdan.dingdan_id}, 金额={jiage}")
- return Response({'code': 200, 'msg': '派单成功', 'data': {'jine': str(dingdan.jine)}})
-
- except Exception as e:
- logger.error(f"商家派单接口未捕获异常: {str(e)}", exc_info=True)
- return Response({'code': 500, 'msg': '系统内部错误'})
-
- def _get_game_type_name(self, leixing_id):
- try:
- gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
- return gt.jieshao if gt else '游戏订单'
- except Exception:
- return '游戏订单'"""
-
-
-
-
-
-logger = logging.getLogger(__name__)
-
-
-
-class ShangjiaPaifaView(APIView):
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- logger.info(f"商家派单请求: user_id={request.user.yonghuid}, data={request.data}")
-
- try:
- data = request.data
-
- # ----- 1. 获取原始参数(保持原类型)-----
- shangpin_type_id = data.get('shangpinTypeId')
- huiyuan_id = data.get('huiyuanId')
- yaoqiuleixing = data.get('yaoqiuleixing')
- dingdan_jieshao_raw = data.get('dingdanJieshao', '')
- dingdan_beizhu_raw = data.get('dingdanBeizhu', '')
- laoban_name_raw = data.get('laobanName', '')
- zhiding_uid_raw = data.get('zhidingUid', '')
- jiage_str = data.get('jiage')
-
- label_id = data.get('labelId') # 整数或 None
- commission_enabled = data.get('commissionEnabled', False)
- commission_value_raw = data.get('commissionValue')
-
- # ----- 2. 字符串字段安全处理 -----
- dingdan_jieshao = str(dingdan_jieshao_raw).strip()
- dingdan_beizhu = str(dingdan_beizhu_raw).strip()
- laoban_name = str(laoban_name_raw).strip()
- zhiding_uid = str(zhiding_uid_raw).strip()
- commission_value_str = str(commission_value_raw).strip() if commission_value_raw is not None else ''
-
- # ----- 3. 必填校验 -----
- if not all([shangpin_type_id, dingdan_jieshao, laoban_name, jiage_str]):
- logger.warning(f"参数缺失: {request.data}")
- return Response({'code': 400, 'msg': '缺少必填参数'})
-
- # ----- 4. 价格验证 -----
- try:
- jiage = float(jiage_str)
- if jiage < 1 or jiage > 10000:
- return Response({'code': 400, 'msg': '价格需在1-10000元之间'})
- if '.' in str(jiage_str) and len(str(jiage_str).split('.')[1]) > 2:
- return Response({'code': 400, 'msg': '最多支持两位小数'})
- except ValueError:
- return Response({'code': 400, 'msg': '价格格式错误'})
-
- # ----- 5. 商品类型 -----
- try:
- shangpin_type = ShangpinLeixing.objects.get(id=shangpin_type_id)
- except ShangpinLeixing.DoesNotExist:
- logger.error(f"商品类型不存在: {shangpin_type_id}")
- return Response({'code': 400, 'msg': '商品类型不存在'})
-
- # ----- 6. 押金 / 会员模式决定 -----
- final_yongjin = None
- if commission_enabled and commission_value_str:
- try:
- cv = float(commission_value_str)
- if cv > 0:
- final_yaoqiu_type = 2
- final_yongjin = Decimal(str(cv))
- final_huiyuan_id = ''
- logger.info(f"押金模式: 佣金={cv}")
- else:
- final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
- final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
- logger.warning("佣金为0,降级为会员模式")
- except ValueError:
- final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
- final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
- logger.warning("佣金格式错误,降级为会员模式")
- else:
- final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
- final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
- logger.info(f"会员模式: 类型={final_yaoqiu_type}")
-
- # ----- 7. 称号(可选:不传/空值/无效/不存在均视为无标签)-----
- chenghao_obj = None
- label_id_str = str(label_id).strip() if label_id is not None else ''
- if label_id_str and label_id_str not in ('0', 'null', 'undefined'):
- try:
- lid = int(label_id_str)
- if lid > 0:
- chenghao_obj = Chenghao.objects.filter(id=lid).first()
- if chenghao_obj:
- logger.info(f"订单将关联称号: {chenghao_obj.mingcheng}")
- else:
- logger.info(f"标签不存在,按无标签派单: {label_id}")
- except (ValueError, TypeError):
- logger.info(f"标签ID无效,按无标签派单: {label_id}")
-
- # ----- 8. 商家信息 -----
- try:
- shangjia = request.user.shop_profile
- except UserShangjia.DoesNotExist:
- logger.warning(f"用户不是商家: {request.user.yonghuid}")
- return Response({'code': 403, 'msg': '您不是商家'})
-
- if shangjia.zhuangtai != 1:
- logger.warning(f"商家状态异常: {shangjia.zhuangtai}")
- return Response({'code': 403, 'msg': '商家状态异常'})
-
- if jiage > shangjia.yue:
- logger.warning(f"余额不足: {shangjia.yue} < {jiage}")
- return Response({'code': 400, 'msg': f'余额不足,当前余额{shangjia.yue}元'})
-
- # ----- 9. 指定打手 -----
- zhiding_dashou = None
- if zhiding_uid:
- try:
- zhiding_user = UserMain.objects.get(yonghuid=zhiding_uid)
- zhiding_dashou = zhiding_user.dashou_profile
- if zhiding_dashou.zhanghaozhuangtai != 1:
- return Response({'code': 400, 'msg': '指定打手状态异常'})
- except UserMain.DoesNotExist:
- return Response({'code': 400, 'msg': '指定用户不存在'})
- except UserDashou.DoesNotExist:
- return Response({'code': 400, 'msg': '指定用户不是打手'})
-
- # ----- 10. 利率(打手+管事分成,打手优先) -----
- from dingdan.utils import calc_shangjia_order_fencheng
- dashou_fencheng, guanshi_fencheng = calc_shangjia_order_fencheng(jiage)
-
- # ----- 11. 生成订单ID -----
- def generate_dingdan_id():
- import time, random
- ts = int(time.time() * 1000)
- pid = os.getpid() % 1000
- rnd = random.randint(1000, 9999)
- return f"SJ{ts}{pid:03d}{rnd}"
-
- # ----- 12. 事务:创建订单、更新商家统计 -----
- dingdan = None
- try:
- with transaction.atomic():
- dingdan = Dingdan.objects.create(
- dingdan_id=generate_dingdan_id(),
- zhuangtai=7 if zhiding_dashou else 1,
- fadan_pingtai=2,
- jine=jiage,
- dashou_fencheng=dashou_fencheng,
- guanshi_fencheng=guanshi_fencheng,
- zhiding_id=zhiding_uid if zhiding_dashou else '',
- shangpin_id=0,
- leixing_id=int(shangpin_type_id),
- yaoqiuleixing=final_yaoqiu_type,
- yongjin=final_yongjin,
- huiyuan_id=final_huiyuan_id,
- jieshao=dingdan_jieshao[:2000],
- beizhu=dingdan_beizhu[:1000],
- nicheng=laoban_name[:50],
- tupian=request.user.avatar,
- user1_id=f"Sj{request.user.yonghuid}"
- )
- logger.info(f"订单主表创建: {dingdan.dingdan_id}")
-
- DingdanShangjia.objects.create(
- dingdan=dingdan,
- shangjia_id=request.user.yonghuid,
- sjnicheng=shangjia.nicheng,
- )
-
- if chenghao_obj:
- DingdanBiaoqian.objects.create(dingdan=dingdan, chenghao=chenghao_obj)
- logger.info(f"订单关联称号: {chenghao_obj.mingcheng}")
-
- # 更新商家统计
- UserShangjia.objects.filter(id=shangjia.id).update(
- fabu=F('fabu') + 1,
- yue=F('yue') - jiage,
- jinridingdan=F('jinridingdan') + 1,
- jinriliushui=F('jinriliushui') + jiage,
- jinyuedingdan=F('jinyuedingdan') + 1,
- jinyueliushui=F('jinyueliushui') + jiage,
- )
-
- update_shangjia_daily(
- yonghuid=request.user.yonghuid,
- amount=Decimal(str(jiage)),
- action=1
- )
- # 事务提交,订单已持久化
- except Exception as e:
- logger.error(f"订单创建事务失败: {str(e)}", exc_info=True)
- return Response({'code': 500, 'msg': '订单创建失败,请稍后重试'})
-
- # ========== 以下操作均在事务外,避免异步任务查不到订单 ==========
- # 跨平台同步(内部可能是异步任务,但函数本身是同步调用,现在订单已存在)
- try:
- sync_order_to_partners(dingdan.dingdan_id)
- except Exception as e:
- logger.error(f"跨平台同步失败: {e}", exc_info=True)
-
- # Celery 广播任务(异步)
- try:
- order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self._get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': (dingdan.jieshao or '')[:50],
- }
- dingdan_guangbo.delay(order_info)
- logger.info(f"已提交广播任务: {dingdan.dingdan_id}")
- except Exception as e:
- logger.error(f"广播任务提交失败: {e}", exc_info=True)
-
- logger.info(f"商家派单成功: {dingdan.dingdan_id}, 金额={jiage}")
- return Response({'code': 200, 'msg': '派单成功', 'data': {'jine': str(dingdan.jine)}})
-
- except Exception as e:
- logger.error(f"商家派单接口未捕获异常: {str(e)}", exc_info=True)
- return Response({'code': 500, 'msg': '系统内部错误'})
-
- def _get_game_type_name(self, leixing_id):
- try:
- gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
- return gt.jieshao if gt else '游戏订单'
- except Exception:
- return '游戏订单'
-
-# apps/dingdan/views.py
-from django.db.models import Q, Count
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-from rest_framework.permissions import IsAuthenticated
-from .models import Dingdan, DingdanShangjia
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-
-'''class ShangjiaDingdanHuoquView(APIView):
- """
- 商家订单获取接口
- POST /dingdan/shangjia/dingdan
- 需要JWT认证
- 支持分页、状态筛选、精确搜索(订单ID或游戏昵称)
- 返回待处理订单数量(pending_count)
- """
-
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 1. 获取当前登录用户的yonghuid
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except AttributeError:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- }, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 解析参数
- try:
- data = request.data
- zhuangtai_list = data.get('zhuangtai_list', [])
- page = max(1, int(data.get('page', 1)))
- page_size = min(100, max(1, int(data.get('page_size', 10))))
-
- # 搜索参数(精确匹配)
- search_type = data.get('search_type') # 可能的值:'order_id' 或 'nicheng'
- search_value = data.get('search_value', '').strip()
- except (ValueError, TypeError):
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 验证状态列表
- if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
- return Response({
- 'code': 3,
- 'msg': '状态列表不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 4. 构建基础查询(该商家的所有订单)
- base_qs = DingdanShangjia.objects.filter(
- shangjia_id=yonghuid
- ).select_related('dingdan')
-
- # 5. 状态过滤(始终使用传入的zhuangtai_list)
- if zhuangtai_list:
- base_qs = base_qs.filter(dingdan__zhuangtai__in=zhuangtai_list)
-
- # 6. 搜索过滤(精确匹配)
- if search_type and search_value:
- if search_type == 'order_id':
- # 订单ID精确匹配(dingdan_id有唯一索引)
- base_qs = base_qs.filter(dingdan__dingdan_id=search_value)
- elif search_type == 'nicheng':
- # 游戏昵称精确匹配(建议为nicheng字段添加索引)
- base_qs = base_qs.filter(dingdan__nicheng=search_value)
- # 其他类型忽略
-
- # 7. 计算待处理订单数量(可抢订单:状态为1或7)
- pending_count = DingdanShangjia.objects.filter(
- shangjia_id=yonghuid,
- dingdan__zhuangtai__in=[1, 7]
- ).count()
-
- # 8. 排序、分页
- total_count = base_qs.count()
- start_index = (page - 1) * page_size
- end_index = start_index + page_size
- # 注意:这里使用切片会触发数据库LIMIT,性能良好
- current_page_orders = base_qs.order_by('-dingdan__create_time')[start_index:end_index]
-
- # 9. 构建返回数据
- order_list = []
- for shangjia_order in current_page_orders:
- order = shangjia_order.dingdan
- order_data = {
- 'dingdan_id': order.dingdan_id,
- 'zhuangtai': order.zhuangtai,
- 'jine': float(order.jine) if order.jine else 0.0,
- 'jieshao': order.jieshao or '',
- 'beizhu': order.beizhu or '',
- 'nicheng': order.nicheng or '',
- 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else ''
- }
- order_list.append(order_data)
-
- # 10. 判断是否还有更多
- current_page_count = len(current_page_orders)
- has_more = current_page_count == page_size and end_index < total_count
-
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': {
- 'list': order_list,
- 'has_more': has_more,
- 'total': total_count,
- 'pending_count': pending_count # 新增待处理数量
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f"商家订单获取失败: {str(e)}", exc_info=True)
- return Response({
- 'code': 4,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
-
-
-# views.py 中 ShangjiaDingdanHuoquView 完整改造
-
-class ShangjiaDingdanHuoquView(APIView):
- """
- 商家订单获取接口
- POST /dingdan/sjdingdanhq
- 需要JWT认证
- 支持分页、状态筛选、商品类型筛选、模糊搜索(订单ID/订单介绍/游戏昵称/打手ID)
- 返回待结算订单数量(pending_count)
- """
-
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 1. 获取当前登录用户的yonghuid
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except AttributeError:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- }, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 解析参数
- try:
- data = request.data
- zhuangtai_list = data.get('zhuangtai_list', [])
- page = max(1, int(data.get('page', 1)))
- page_size = min(100, max(1, int(data.get('page_size', 10))))
-
- # 🆕 商品类型筛选
- leixing_id = data.get('leixing_id', None)
-
- # 🆕 模糊搜索关键字(支持订单ID、订单介绍、游戏昵称、打手ID)
- keyword = data.get('keyword', '').strip()
- except (ValueError, TypeError):
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 验证状态列表
- if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
- return Response({
- 'code': 3,
- 'msg': '状态列表不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 4. 构建基础查询(该商家的所有订单)
- base_qs = DingdanShangjia.objects.filter(
- shangjia_id=yonghuid
- ).select_related('dingdan')
-
- # 5. 状态过滤
- base_qs = base_qs.filter(dingdan__zhuangtai__in=zhuangtai_list)
-
- # 🆕 6. 商品类型筛选
- if leixing_id is not None:
- try:
- leixing_id_int = int(leixing_id)
- base_qs = base_qs.filter(dingdan__leixing_id=leixing_id_int)
- except (ValueError, TypeError):
- # 如果leixing_id不是有效整数,忽略该筛选
- pass
-
- # 🆕 7. 模糊搜索(支持订单ID、订单介绍、游戏昵称、打手ID)
- if keyword:
- from django.db.models import Q
- # 订单ID精确匹配(dingdan_id有唯一索引,先用精确匹配)
- dingdan_id_match = Q(dingdan__dingdan_id__exact=keyword)
- # 订单介绍模糊匹配
- jieshao_match = Q(dingdan__jieshao__icontains=keyword)
- # 游戏昵称模糊匹配
- nicheng_match = Q(dingdan__nicheng__icontains=keyword)
- # 打手ID精确匹配
- dashou_match = Q(dingdan__jiedan_dashou_id__exact=keyword)
-
- base_qs = base_qs.filter(
- dingdan_id_match | jieshao_match | nicheng_match | dashou_match
- )
-
- # 8. 计算待结算订单数量(状态为8)
- pending_count = DingdanShangjia.objects.filter(
- shangjia_id=yonghuid,
- dingdan__zhuangtai=8
- ).count()
-
- # 9. 排序、分页
- total_count = base_qs.count()
- start_index = (page - 1) * page_size
- end_index = start_index + page_size
- current_page_orders = base_qs.order_by('-dingdan__create_time')[start_index:end_index]
-
- # 10. 构建返回数据
- order_list = []
- for shangjia_order in current_page_orders:
- order = shangjia_order.dingdan
- order_data = {
- 'dingdan_id': order.dingdan_id,
- 'zhuangtai': order.zhuangtai,
- 'jine': float(order.jine) if order.jine else 0.0,
- 'jieshao': order.jieshao or '',
- 'beizhu': order.beizhu or '',
- 'nicheng': order.nicheng or '',
- 'leixing_id': order.leixing_id, # 🆕 返回商品类型ID
- 'tupian': order.tupian or '', # 🆕 返回商品图片
- 'jiedan_dashou_id': order.jiedan_dashou_id or '', # 🆕 返回打手ID
- 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else ''
- }
- order_list.append(order_data)
-
- # 11. 判断是否还有更多
- current_page_count = len(current_page_orders)
- has_more = current_page_count == page_size and end_index < total_count
-
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': {
- 'list': order_list,
- 'has_more': has_more,
- 'total': total_count,
- 'pending_count': pending_count
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f"商家订单获取失败: {str(e)}", exc_info=True)
- return Response({
- 'code': 4,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-
-
-
-
-'''class ShangjiaDingdanXiangqingView(APIView):
- """商家订单详情接口 - 高性能实现"""
- permission_classes = [IsAuthenticated] # JWT认证
-
- def post(self, request):
- # 1. 获取当前登录用户的yonghuid
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except AttributeError:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- }, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdan_id', '').strip()
- except Exception:
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 参数验证
- if not dingdan_id:
- return Response({
- 'code': 3,
- 'msg': '订单ID不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 4. 高性能查询:一次性获取所有关联数据
- # 使用select_related和prefetch_related避免N+1查询
-
- # 先查询商家订单扩展表,验证订单权限
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- shangjia_id=yonghuid,
- dingdan__dingdan_id=dingdan_id
- ).select_related('dingdan').first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 4,
- 'msg': '订单不存在或您无权限查看',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 5. 查询打手交付图片(使用prefetch_related可以一次性获取所有相关图片)
- # 这里我们单独查询,因为是多对一关系
- dashou_tupian_list = Dashoutupian.objects.filter(
- dingdan_id=dingdan_id
- ).order_by('create_time').values_list('tupian', flat=True)
-
- # 转换为列表,确保即使为空也返回列表
- dashou_jiaofu = list(dashou_tupian_list)
-
- # 6. 构建返回数据 - 字段名必须与前端完全匹配
- # 注意:所有字段即使为空也要返回,前端会处理空值
- order = shangjia_kuozhan.dingdan
-
- response_data = {
- # 基础字段
- 'dingdan_id': order.dingdan_id,
- 'zhuangtai': order.zhuangtai or 0,
-
- # 服务相关信息
- 'fuwudashou_id': order.jiedan_dashou_id or '', # 服务打手ID
- 'dashou_liuyan': order.dashou_liuyan or '', # 打手留言
- 'dashou_jiaofu': dashou_jiaofu, # 打手交付图片列表
-
- # 商家订单扩展表字段
- 'shangjia_liuyan': shangjia_kuozhan.shangjia_pingjia or '', # 商家留言
- 'tkliyou': order.tkly or '', # 退款理由(暂时没有字段)
- 'cfliyou': shangjia_kuozhan.cfliyou or '', # 处罚申请原因
- 'sqzhuangtai': shangjia_kuozhan.sqzhuangtai or 0, # 申请状态
- 'bhliyou': shangjia_kuozhan.bhliyou or '', # 驳回理由
- 'chufa_jieguo': '', # 处罚结果(暂时没有字段)
-
- # 其他可能需要的基础信息
- 'jine': float(order.jine) if order.jine else 0.0,
- 'jieshao': order.jieshao or '',
- 'beizhu': order.beizhu or '',
- 'nicheng': order.nicheng or '',
- 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S'),
-
-
- }
-
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': response_data
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- # 记录异常日志
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"商家订单详情查询失败: {str(e)}", exc_info=True)
-
- return Response({
- 'code': 5,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
-
-
-
-
-from dingdan.models import Dingdan, DingdanShangjia, Dashoutupian, Fadan
-
-from .models import FadanFenhong # 注意:fadan_fenhong 可能在其他 app 中,根据实际路径调整
-
-logger = logging.getLogger(__name__)
-
-
-class ShangjiaDingdanXiangqingView(APIView):
- """
- 商家订单详情接口
- 返回订单基础信息、打手信息、交付图片、罚款数据、分红利率、更换打手产生的新订单ID等
- """
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 1. 获取当前登录用户的 yonghuid
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except AttributeError:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- }, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdan_id', '').strip()
- except Exception:
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- if not dingdan_id:
- return Response({
- 'code': 3,
- 'msg': '订单ID不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 3. 查询商家订单扩展表,验证权限并获取基础订单数据
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- shangjia_id=yonghuid,
- dingdan__dingdan_id=dingdan_id
- ).select_related('dingdan').first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 4,
- 'msg': '订单不存在或您无权限查看',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- order = shangjia_kuozhan.dingdan
-
- # 4. 交付图片列表
- dashou_tupian_list = Dashoutupian.objects.filter(
- dingdan_id=dingdan_id
- ).order_by('create_time').values_list('tupian', flat=True)
- dashou_jiaofu = list(dashou_tupian_list)
-
- # 5. 打手详细信息(头像、昵称、用户ID)
- dashou_yonghuid = order.jiedan_dashou_id or ''
- dashou_nicheng = ''
- dashou_avatar = ''
- if dashou_yonghuid:
- dashou_user = UserMain.objects.filter(
- yonghuid=dashou_yonghuid
- ).select_related('dashou_profile').first()
- if dashou_user:
- dashou_avatar = dashou_user.avatar or ''
- if hasattr(dashou_user, 'dashou_profile') and dashou_user.dashou_profile:
- dashou_nicheng = dashou_user.dashou_profile.nicheng or ''
-
- # 6. 罚款数据(如果存在)
- fadan_obj = Fadan.objects.filter(
- guanliandingdan_id=dingdan_id,
- beichufa_id=dashou_yonghuid
- ).first()
- fadan_dict = None
- if fadan_obj:
- fadan_dict = {
- 'id': fadan_obj.id,
- 'chufaliyou': fadan_obj.chufaliyou or '',
- 'fakuanjine': float(fadan_obj.fakuanjine) if fadan_obj.fakuanjine else 0.0,
- 'yingxiang_qiangdan': fadan_obj.yingxiang_qiangdan,
- 'zhuangtai': fadan_obj.zhuangtai,
- 'shensuliyou': fadan_obj.shensuliyou or '',
- 'tongyi_chufaliyou': fadan_obj.tongyi_chufaliyou or '',
- 'bohuiliyou': fadan_obj.bohuiliyou or '',
- 'create_time': fadan_obj.create_time.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.create_time else '',
- 'update_time': fadan_obj.update_time.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.update_time else '',
- }
-
- # 7. 商家自己的分红利率(用于罚款分红计算)
- fenhong_lilv = 0.0
- try:
- # 查找商家作为分红者的最新记录,取应得利率
- last_fenhong = FadanFenhong.objects.filter(
- fenhongzhe_id=yonghuid,
- shenfen=1 # 1=商家
- ).order_by('-create_time').first()
- if last_fenhong:
- fenhong_lilv = float(last_fenhong.yingde_lilv or 0)
- except Exception as e:
- logger.warning(f'获取分红利率失败: {e}')
-
- # 8. 更换打手后产生的新订单ID(如果当前订单被更换过)
- xin_dingdan_id = ''
- try:
- # 查找以当前订单作为“上一个订单ID”的新订单扩展记录
- new_shangjia_order = DingdanShangjia.objects.filter(
- shangyige_dingdan_id=dingdan_id,
- shangjia_id=yonghuid
- ).select_related('dingdan').first()
- if new_shangjia_order:
- xin_dingdan_id = new_shangjia_order.dingdan.dingdan_id
- except Exception as e:
- logger.warning(f'查找新订单ID失败: {e}')
-
- # 9. 构建返回数据
- response_data = {
- # 基础订单字段
- 'dingdan_id': order.dingdan_id,
- 'zhuangtai': order.zhuangtai or 0,
- 'jine': float(order.jine) if order.jine else 0.0,
- 'jieshao': order.jieshao or '',
- 'beizhu': order.beizhu or '',
- 'nicheng': order.nicheng or '',
- 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else '',
-
- # 打手信息
- 'dashou_yonghuid': dashou_yonghuid,
- 'dashou_nicheng': dashou_nicheng,
- 'dashou_avatar': dashou_avatar,
-
- # 服务详情
- 'fuwudashou_id': dashou_yonghuid, # 保留旧字段兼容
- 'dashou_liuyan': order.dashou_liuyan or '',
- 'dashou_jiaofu': dashou_jiaofu,
-
- # 商家扩展字段(旧)
- 'shangjia_liuyan': shangjia_kuozhan.shangjia_pingjia or '',
- 'tkliyou': order.tkly or '',
- 'cfliyou': shangjia_kuozhan.cfliyou or '', # 保留,但不影响新罚款
- 'sqzhuangtai': shangjia_kuozhan.sqzhuangtai or 0,
- 'bhliyou': shangjia_kuozhan.bhliyou or '',
- 'chufa_jieguo': '', # 已弃用
-
- # 新增:罚款数据
- 'fadan': fadan_dict,
-
- # 新增:分红利率
- 'fenhong_lilv': fenhong_lilv,
-
- # 新增:更换打手后的新订单ID
- 'xin_dingdan_id': xin_dingdan_id,
- }
-
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': response_data
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f"商家订单详情查询失败: {str(e)}", exc_info=True)
- return Response({
- 'code': 5,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-from dingdan.models import Dingdan, DingdanShangjia, PreSettlement
-
-from dingdan.utils import update_daily_dispatch_stat
-
-
-logger = logging.getLogger(__name__)
-
-from houtai.utils import update_shangjia_daily
-class ShangjiaJiesuanView(APIView):
- """商家结单接口(支持跨平台订单)"""
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- # 1. 获取当前登录用户
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
-
- try:
- shangjia = current_user.shop_profile
- except UserShangjia.DoesNotExist:
- return Response({
- 'code': 1,
- 'msg': '您不是商家或商家信息未完善',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- except AttributeError:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- }, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdan_id', '').strip()
- pingfen = int(data.get('pingfen', 0))
- liuyan = data.get('liuyan', '').strip()
- except (ValueError, TypeError):
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 参数验证
- if not dingdan_id:
- return Response({
- 'code': 3,
- 'msg': '订单ID不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- if pingfen < 0 or pingfen > 5:
- return Response({
- 'code': 4,
- 'msg': '评分必须在0-5分之间',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- with transaction.atomic():
- # 4. 查询订单并加锁(修复并发问题)
- order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)
-
- # 验证订单是否属于当前商家
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- dingdan=order,
- shangjia_id=yonghuid
- ).first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 5,
- 'msg': '订单不存在或您无权限操作',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 5. 验证订单状态
- if order.zhuangtai != 8:
- return Response({
- 'code': 6,
- 'msg': '订单不在结算状态,无法结单',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 获取订单字段
- jine = order.jine or Decimal('0')
- dashou_fencheng = order.dashou_fencheng or Decimal('0')
- jiedan_dashou_id = order.jiedan_dashou_id
- dispatch_type = order.dispatch_type # 1=我方派单,2=对方派单
- is_cross = order.is_cross
- partner_club_id = order.partner_club_id
- partner_order_id = order.partner_order_id
-
- if not jiedan_dashou_id:
- return Response({
- 'code': 7,
- 'msg': '订单没有接单打手,无法结单',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # ========== 跨平台判断 ==========
- is_cross_claimed_by_partner = (is_cross == 1 and (partner_club_id or partner_order_id))
-
- # ========== 根据订单类型处理 ==========
- # 情况1:对方派单(我方打手接单)-> 预结算,不直接给打手加钱
- if dispatch_type == 2:
- # 创建预结算记录
- PreSettlement.objects.update_or_create(
- dingdan_id=dingdan_id,
- defaults={
- 'partner_order_id': partner_order_id,
- 'partner_club_id': partner_club_id or '000001',
- 'dashou_id': jiedan_dashou_id,
- 'amount': dashou_fencheng,
- 'order_amount': jine,
- 'status': 0,
- 'operator': request.user.phone,
- }
- )
- # 更新订单状态
- order.zhuangtai = 3
- order.save()
-
- # 更新统计表
- update_daily_dispatch_stat(
- direction=2,
- partner_club_id=partner_club_id or '000001',
- success_amount=jine,
- success_count=1
- )
-
- # 情况2:我方派单且被对方打手接单 -> 只改状态,不给本地打手加钱
- elif dispatch_type == 1 and is_cross_claimed_by_partner:
- order.zhuangtai = 3
- order.save()
-
- update_daily_dispatch_stat(
- direction=1,
- partner_club_id=partner_club_id or '000001',
- success_amount=dashou_fencheng,
- success_count=1
- )
-
- # 情况3:本地订单 或 跨平台但自家打手接单 -> 正常结算给打手
- else:
- # 查询打手信息
- try:
- dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
- dashou = dashou_user.dashou_profile
- except (UserMain.DoesNotExist, UserDashou.DoesNotExist):
- return Response({
- 'code': 8,
- 'msg': '接单打手不存在或信息不完整',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 原子更新打手数据(使用 F 表达式)
- UserDashou.objects.filter(id=dashou.id).update(
- chengjiaozongliang=F('chengjiaozongliang') + 1,
- yue=F('yue') + dashou_fencheng,
- zonge=F('zonge') + dashou_fencheng,
- jinrishouyi=F('jinrishouyi') + dashou_fencheng,
- jinyueshouyi=F('jinyueshouyi') + dashou_fencheng,
- )
-
- # 更新商家成交单量
- UserShangjia.objects.filter(id=shangjia.id).update(
- chengjiao=F('chengjiao') + 1,
- )
-
- # 更新订单状态
- order.zhuangtai = 3
- order.save()
-
- # 处理评分(所有情况都处理)
- if pingfen > 0:
- pingfen_obj, created = Pingfen.objects.get_or_create(
- dashou=jiedan_dashou_id,
- defaults={'zongfen': pingfen, 'cishu': 1}
- )
- if not created:
- Pingfen.objects.filter(id=pingfen_obj.id).update(
- zongfen=F('zongfen') + pingfen,
- cishu=F('cishu') + 1,
- )
-
- # 更新商家订单扩展表的评价
- DingdanShangjia.objects.filter(id=shangjia_kuozhan.id).update(
- shangjia_pingjia=liuyan,
- )
-
- # 🔥 新增:调用打手每日统计(成交)
- try:
- update_dashou_daily_by_action(
- yonghuid=jiedan_dashou_id,
- amount=dashou_fencheng,
- action=2 # 2 表示成交(结算)
- )
- logger.info(f"打手 {jiedan_dashou_id} 每日统计更新成功")
- except Exception as e:
- logger.error(f"打手每日统计更新失败: {e}")
- # 不影响主流程
-
- # ✅ 新方法(行为驱动,更安全)
- update_shangjia_daily(
- yonghuid=request.user.yonghuid,
- amount=Decimal(str(jine)),
- action=2 # 2 = 结算
- )
-
- # 商家订单管事分红(仅 fadan_pingtai=2)
- if order.fadan_pingtai == 2:
- from dingdan.utils import settle_shangjia_order_guanshi_fenhong
- settle_shangjia_order_guanshi_fenhong(order, jiedan_dashou_id)
-
- # 返回成功
- return Response({
- 'code': 0,
- 'msg': '结单成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'new_zhuangtai': 3,
- 'jine': float(jine),
- 'dashou_fencheng': float(dashou_fencheng),
- 'jiedan_dashou_id': jiedan_dashou_id,
- 'pingfen': pingfen,
- 'liuyan': liuyan
- }
- }, status=status.HTTP_200_OK)
-
- except Dingdan.DoesNotExist:
- return Response({
- 'code': 5,
- 'msg': '订单不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
- except Exception as e:
- logger.error(f"商家结单失败: {str(e)}", exc_info=True)
- return Response({
- 'code': 9,
- 'msg': '服务器内部错误,结单失败',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
-
-
-class ShangjiaChexiaoView(APIView):
- """商家撤销订单申请接口"""
- permission_classes = [IsAuthenticated] # JWT认证
-
- def post(self, request):
- # 1. 获取当前登录用户并验证商家身份
- try:
- current_user = request.user
- # 使用反向查询获取商家扩展信息(性能最佳)
- shangjia = current_user.shop_profile
- except UserShangjia.DoesNotExist:
- return Response({
- 'code': 1,
- 'msg': '您不是商家或商家信息未完善',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 2. 检查商家账号状态
- if shangjia.zhuangtai != 1:
- return Response({
- 'code': 2,
- 'msg': '商家账号状态异常,无法操作',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 3. 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdan_id', '').strip()
- chexiao_liyou = data.get('chexiao_liyou', '').strip()
- except Exception:
- return Response({
- 'code': 3,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 4. 参数验证
- if not dingdan_id or not chexiao_liyou:
- return Response({
- 'code': 4,
- 'msg': '订单ID和撤销原因不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 5. 使用事务确保数据一致性
- with transaction.atomic():
- # 查询订单主表
- dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
- if not dingdan:
- return Response({
- 'code': 5,
- 'msg': '订单不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
-
- # 6. 验证订单状态
- if dingdan.zhuangtai not in [1, 7]:
- return Response({
- 'code': 6,
- 'msg': '当前订单状态不可撤销',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 7. 验证订单所属商家(使用反向查询)
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- dingdan__dingdan_id=dingdan_id,
- shangjia_id=current_user.yonghuid
- ).first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 7,
- 'msg': '您无权撤销此订单',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 8. 创建退款记录(注意:申请状态直接设为1-成功)
- Tuikuanjilu.objects.create(
- dingdan_id=dingdan_id,
- qingqiuid=current_user.yonghuid,
- liyou=chexiao_liyou,
- sqzhuangtai=1, # 直接成功
- jine=dingdan.jine,
- dashou_fencheng=dingdan.dashou_fencheng,
- jieshao=dingdan.jieshao,
- beizhu=dingdan.beizhu,
- nicheng=dingdan.nicheng,
-
- )
-
- # 9. 更新商家扩展表数据(使用F表达式原子操作)
- UserShangjia.objects.filter(id=shangjia.id).update(
- yue=F('yue') + dingdan.jine,
- tuikuan=F('tuikuan') + 1,
- jinridingdan=F('jinridingdan') - 1,
- jinyuedingdan=F('jinyuedingdan') - 1,
- jinriliushui=F('jinriliushui') - dingdan.jine,
- jinyueliushui=F('jinyueliushui') - dingdan.jine,
-
- )
-
- # 10. 更新订单状态为5(已退款)
- dingdan.zhuangtai = 5
-
- dingdan.save()
-
- # ✅ 新方法(行为驱动,更安全)
- from houtai.utils import update_shangjia_daily
- update_shangjia_daily(
- yonghuid=request.user.yonghuid,
- amount=Decimal(str(dingdan.jine)),
- action=3 # 3 = 退款
- )
-
- return Response({
- 'code': 0,
- 'msg': '撤销成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'new_zhuangtai': 5,
- 'tuikuan_liyou': chexiao_liyou,
- 'jine': float(dingdan.jine) if dingdan.jine else 0.0
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- # 记录异常日志
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"商家撤销订单失败: {str(e)}", exc_info=True)
-
- return Response({
- 'code': 8,
- 'msg': '服务器内部错误,撤销失败',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-
-
-class ShangjiaTuikuanShenqingView(APIView):
- """商家退款申请接口"""
- permission_classes = [IsAuthenticated] # JWT认证
-
- def post(self, request):
- # 1. 获取当前登录用户并验证商家身份
- try:
- current_user = request.user
- shangjia = current_user.shop_profile
- except UserShangjia.DoesNotExist:
- return Response({
- 'code': 1,
- 'msg': '您不是商家或商家信息未完善',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 2. 检查商家账号状态
- if shangjia.zhuangtai != 1:
- return Response({
- 'code': 2,
- 'msg': '商家账号状态异常,无法操作',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 3. 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdan_id', '').strip()
- tuikuan_liyou = data.get('tuikuan_liyou', '').strip()
- except Exception:
- return Response({
- 'code': 3,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 4. 参数验证
- if not dingdan_id or not tuikuan_liyou:
- return Response({
- 'code': 4,
- 'msg': '订单ID和退款原因不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 5. 使用事务确保数据一致性
- with transaction.atomic():
- # 查询订单主表
- dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
- if not dingdan:
- return Response({
- 'code': 5,
- 'msg': '订单不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
-
- # 6. 验证订单状态
- if dingdan.zhuangtai not in [2, 8]:
- return Response({
- 'code': 6,
- 'msg': '当前订单状态不可申请退款',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 7. 验证订单所属商家
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- dingdan__dingdan_id=dingdan_id,
- shangjia_id=current_user.yonghuid
- ).first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 7,
- 'msg': '您无权为此订单申请退款',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # ========== 8. 处理退款记录(修改点:先查询是否存在) ==========
- refund_record, created = Tuikuanjilu.objects.get_or_create(
- dingdan_id=dingdan_id,
- defaults={
- 'dashouid': dingdan.jiedan_dashou_id,
- 'qingqiuid': current_user.yonghuid,
- 'liyou': tuikuan_liyou,
- 'sqzhuangtai': 0, # 待处理
- 'jine': dingdan.jine,
- 'dashou_fencheng': dingdan.dashou_fencheng,
- 'jieshao': dingdan.jieshao,
- 'beizhu': dingdan.beizhu,
- 'nicheng': dingdan.nicheng,
- }
- )
- if not created:
- # 记录已存在,更新退款理由和申请时间
- refund_record.liyou = tuikuan_liyou
- refund_record.save(update_fields=['liyou', 'update_time'])
- logger.info(f"订单 {dingdan_id} 已有退款记录,已更新退款理由")
-
- # 9. 更新订单状态为4(退款中)
- dingdan.zhuangtai = 4
- dingdan.tkly = tuikuan_liyou
- dingdan.save()
-
- # ====== 原有功能:更新打手状态 ======
- if dingdan.jiedan_dashou_id:
- try:
- dashou_main = UserMain.objects.filter(
- yonghuid=dingdan.jiedan_dashou_id
- ).first()
- if dashou_main and hasattr(dashou_main, 'dashou_profile'):
- dashou_profile = dashou_main.dashou_profile
- dashou_profile.zhuangtai = 1
- dashou_profile.save()
- logger.info(f"退款申请成功,打手状态已更新:订单{dingdan_id},打手{dingdan.jiedan_dashou_id}状态变更为1")
- except Exception as e:
- logger.error(f"更新打手状态失败:{str(e)},订单{dingdan_id},打手ID{dingdan.jiedan_dashou_id}")
-
- # ========== 新增:如果是跨平台订单,异步通知对方平台 ==========
- if dingdan.is_cross == 1:
- import threading
- import requests
- from django.conf import settings
-
- def send_refund_notify():
- url = f"{settings.PARTNER_BASE_URL}{settings.PARTNER_REFUND_NOTIFY_URL}"
- payload = {
- 'token': settings.CROSS_PLATFORM_TOKEN,
- 'order_id': dingdan.dingdan_id
- }
- try:
- resp = requests.post(url, json=payload, timeout=5)
- if resp.status_code == 200:
- data = resp.json()
- if data.get('code') == 0:
- logger.info(f"跨平台退款通知发送成功: {dingdan.dingdan_id}")
- else:
- logger.error(f"跨平台退款通知业务失败: {data}")
- else:
- logger.error(f"跨平台退款通知HTTP失败: {resp.status_code}")
- except Exception as e:
- logger.error(f"跨平台退款通知异常: {str(e)}", exc_info=True)
-
- thread = threading.Thread(target=send_refund_notify)
- thread.daemon = True
- thread.start()
- logger.info(f"跨平台退款通知线程已启动: {dingdan.dingdan_id}")
- # ========== 新增结束 ==========
-
- return Response({
- 'code': 0,
- 'msg': '退款申请提交成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'new_zhuangtai': 4,
- 'tuikuan_liyou': tuikuan_liyou,
- 'jine': float(dingdan.jine) if dingdan.jine else 0.0,
- 'dashou_id': dingdan.jiedan_dashou_id
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f"商家退款申请失败: {str(e)}", exc_info=True)
- return Response({
- 'code': 8,
- 'msg': '服务器内部错误,申请失败',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-'''class ShangjiaTuikuanShenqingView(APIView):
- """商家退款申请接口"""
- permission_classes = [IsAuthenticated] # JWT认证
-
- def post(self, request):
- # 1. 获取当前登录用户并验证商家身份
- try:
- current_user = request.user
- shangjia = current_user.shop_profile
- except UserShangjia.DoesNotExist:
- return Response({
- 'code': 1,
- 'msg': '您不是商家或商家信息未完善',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 2. 检查商家账号状态
- if shangjia.zhuangtai != 1:
- return Response({
- 'code': 2,
- 'msg': '商家账号状态异常,无法操作',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 3. 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdan_id', '').strip()
- tuikuan_liyou = data.get('tuikuan_liyou', '').strip()
- except Exception:
- return Response({
- 'code': 3,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 4. 参数验证
- if not dingdan_id or not tuikuan_liyou:
- return Response({
- 'code': 4,
- 'msg': '订单ID和退款原因不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 5. 使用事务确保数据一致性
- with transaction.atomic():
- # 查询订单主表
- dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
- if not dingdan:
- return Response({
- 'code': 5,
- 'msg': '订单不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
-
- # 6. 验证订单状态
- if dingdan.zhuangtai not in [2, 8]:
- return Response({
- 'code': 6,
- 'msg': '当前订单状态不可申请退款',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 7. 验证订单所属商家
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- dingdan__dingdan_id=dingdan_id,
- shangjia_id=current_user.yonghuid
- ).first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 7,
- 'msg': '您无权为此订单申请退款',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 8. 创建退款记录(申请状态为0-待处理)
- Tuikuanjilu.objects.create(
- dingdan_id=dingdan_id,
- dashouid=dingdan.jiedan_dashou_id,
- qingqiuid=current_user.yonghuid,
- liyou=tuikuan_liyou,
- sqzhuangtai=0, # 待处理
- jine=dingdan.jine,
- dashou_fencheng=dingdan.dashou_fencheng,
- jieshao=dingdan.jieshao,
- beizhu=dingdan.beizhu,
- nicheng=dingdan.nicheng,
-
- )
-
- # 9. 更新订单状态为4(退款中)
- dingdan.zhuangtai = 4
- dingdan.tkly = tuikuan_liyou
-
- dingdan.save()
-
-
- # ====== 新增功能:更新打手状态 ======
- if dingdan.jiedan_dashou_id:
- try:
- # 高效查询:通过yonghuid查询用户主表,然后通过反向关系获取打手扩展表
- dashou_main = UserMain.objects.filter(
- yonghuid=dingdan.jiedan_dashou_id
- ).first()
-
- if dashou_main and hasattr(dashou_main, 'dashou_profile'):
- # 通过OneToOneField反向关系直接获取打手扩展表
- dashou_profile = dashou_main.dashou_profile
- # 将打手状态更新为1
- dashou_profile.zhuangtai = 1
- dashou_profile.save()
-
- # 记录日志
- import logging
- logger = logging.getLogger(__name__)
- logger.info(
- f"退款申请成功,打手状态已更新:订单{dingdan_id},打手{dingdan.jiedan_dashou_id}状态变更为1")
- except Exception as e:
- # 打手状态更新失败不影响退款申请主流程,但记录错误日志
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"更新打手状态失败:{str(e)},订单{dingdan_id},打手ID{dingdan.jiedan_dashou_id}")
-
- return Response({
- 'code': 0,
- 'msg': '退款申请提交成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'new_zhuangtai': 4,
- 'tuikuan_liyou': tuikuan_liyou,
- 'jine': float(dingdan.jine) if dingdan.jine else 0.0,
- 'dashou_id': dingdan.jiedan_dashou_id
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"商家退款申请失败: {str(e)}", exc_info=True)
-
- return Response({
- 'code': 8,
- 'msg': '服务器内部错误,申请失败',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
-
-
-
-class ShangjiaChufaShenqingView(APIView):
- """商家处罚申请接口(已添加图片上传功能)"""
- permission_classes = [IsAuthenticated] # JWT认证
-
- def post(self, request):
- # 1. 获取当前登录用户并验证商家身份
- try:
- current_user = request.user
- shangjia = current_user.shop_profile
- except UserShangjia.DoesNotExist:
- return Response({
- 'code': 1,
- 'msg': '您不是商家或商家信息未完善',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 2. 检查商家账号状态
- if shangjia.zhuangtai != 1:
- return Response({
- 'code': 2,
- 'msg': '商家账号状态异常,无法操作',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 3. 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdan_id', '').strip()
- dashou_id = data.get('dashou_id', '').strip()
- chufa_liyou = data.get('chufa_liyou', '').strip()
- chufa_tupian_urls = data.get('chufa_tupian_urls', []) # 🔴 新增:图片URL数组
- except Exception:
- return Response({
- 'code': 3,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 4. 参数验证
- if not dingdan_id or not dashou_id or not chufa_liyou:
- return Response({
- 'code': 4,
- 'msg': '订单ID、打手ID和处罚原因不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 5. 使用事务确保数据一致性
- with transaction.atomic():
- # 验证打手身份
- try:
- dashou_user = UserMain.objects.get(yonghuid=dashou_id)
- dashou = dashou_user.dashou_profile
- except UserMain.DoesNotExist:
- return Response({
- 'code': 5,
- 'msg': '打手用户不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
- except UserDashou.DoesNotExist:
- return Response({
- 'code': 5,
- 'msg': '该用户不是打手',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 查询订单主表
- dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
- if not dingdan:
- return Response({
- 'code': 6,
- 'msg': '订单不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
-
- # 验证订单状态
- if dingdan.zhuangtai not in [2, 8, 4, 5, 6, 3]:
- return Response({
- 'code': 7,
- 'msg': '当前订单状态不可申请处罚',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 验证订单所属商家
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- dingdan__dingdan_id=dingdan_id,
- shangjia_id=current_user.yonghuid
- ).first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 8,
- 'msg': '您无权为此订单申请处罚',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 验证订单的打手是否与提供的打手ID一致
- if dingdan.jiedan_dashou_id != dashou_id:
- return Response({
- 'code': 9,
- 'msg': '该打手并非此订单的服务打手',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 创建处罚记录
- chufajilu = Chufajilu.objects.create(
- dashouid=dashou_id,
- qingqiuid=current_user.yonghuid,
- cfliyou=chufa_liyou,
- sqzhuangtai=0, # 待处理
- jifen=5, # 扣除5积分
- dingdan_id=dingdan_id,
- )
- # 🔴【新增】在创建处罚记录后立即扣除打手积分
- # 注意:这里使用前面已经获取的dashou对象(打手扩展表)
- jifen_kouchu = chufajilu.jifen # 扣除的积分,这里是5
- if jifen_kouchu > 0:
- # 检查打手积分是否足够
- if dashou.jifen < jifen_kouchu:
- dashou.jifen = 0
- else:
- dashou.jifen -= jifen_kouchu
- dashou.save()
-
- # 🔴【新增】保存处罚图片到数据库
- if chufa_tupian_urls and isinstance(chufa_tupian_urls, list):
- for tupian_url in chufa_tupian_urls:
- if tupian_url and tupian_url.strip():
- Chufatupian.objects.create(
- dingdan_id=dingdan_id,
- yonghuid=current_user.yonghuid,
- tupian=tupian_url
- )
-
- # 更新商家订单扩展表的处罚相关字段
- shangjia_kuozhan.cfliyou = chufa_liyou
- shangjia_kuozhan.sqzhuangtai = 0
- shangjia_kuozhan.save()
-
- return Response({
- 'code': 0,
- 'msg': '处罚申请提交成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'dashou_id': dashou_id,
- 'chufa_liyou': chufa_liyou,
- 'sqzhuangtai': 0,
- 'jifen': 5,
- 'tupian_count': len(chufa_tupian_urls) if chufa_tupian_urls else 0 # 🔴 返回图片数量
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"商家处罚申请失败: {str(e)}", exc_info=True)
-
- return Response({
- 'code': 10,
- 'msg': '服务器内部错误,申请失败',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-'''class ShangjiaChufaShenqingView(APIView):
- """商家处罚申请接口"""
- permission_classes = [IsAuthenticated] # JWT认证
-
- def post(self, request):
- # 1. 获取当前登录用户并验证商家身份
- try:
- current_user = request.user
- shangjia = current_user.shop_profile
- except UserShangjia.DoesNotExist:
- return Response({
- 'code': 1,
- 'msg': '您不是商家或商家信息未完善',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 2. 检查商家账号状态
- if shangjia.zhuangtai != 1:
- return Response({
- 'code': 2,
- 'msg': '商家账号状态异常,无法操作',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 3. 获取前端参数
- try:
- data = request.data
- dingdan_id = data.get('dingdan_id', '').strip()
- dashou_id = data.get('dashou_id', '').strip()
- chufa_liyou = data.get('chufa_liyou', '').strip()
- except Exception:
- return Response({
- 'code': 3,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 4. 参数验证
- if not dingdan_id or not dashou_id or not chufa_liyou:
- return Response({
- 'code': 4,
- 'msg': '订单ID、打手ID和处罚原因不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 5. 使用事务确保数据一致性
- with transaction.atomic():
- # 验证打手身份
- try:
- dashou_user = UserMain.objects.get(yonghuid=dashou_id)
- dashou = dashou_user.dashou_profile
- except UserMain.DoesNotExist:
- return Response({
- 'code': 5,
- 'msg': '打手用户不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
- except UserDashou.DoesNotExist:
- return Response({
- 'code': 5,
- 'msg': '该用户不是打手',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 查询订单主表
- dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
- if not dingdan:
- return Response({
- 'code': 6,
- 'msg': '订单不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
-
- # 验证订单状态
- if dingdan.zhuangtai not in [2, 8,3,4,5,6]:
- return Response({
- 'code': 7,
- 'msg': '当前订单状态不可申请处罚',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 验证订单所属商家
- shangjia_kuozhan = DingdanShangjia.objects.filter(
- dingdan__dingdan_id=dingdan_id,
- shangjia_id=current_user.yonghuid
- ).first()
-
- if not shangjia_kuozhan:
- return Response({
- 'code': 8,
- 'msg': '您无权为此订单申请处罚',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 验证订单的打手是否与提供的打手ID一致
- if dingdan.jiedan_dashou_id != dashou_id:
- return Response({
- 'code': 9,
- 'msg': '该打手并非此订单的服务打手',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 创建处罚记录
- Chufajilu.objects.create(
- dashouid=dashou_id,
- qingqiuid=current_user.yonghuid,
- cfliyou=chufa_liyou,
- sqzhuangtai=0, # 待处理
- jifen=5, # 扣除5积分
- dingdan_id=dingdan_id,
-
- )
-
- # 更新商家订单扩展表的处罚相关字段
- shangjia_kuozhan.cfliyou = chufa_liyou
- shangjia_kuozhan.sqzhuangtai = 0
-
- shangjia_kuozhan.save()
-
- return Response({
- 'code': 0,
- 'msg': '处罚申请提交成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'dashou_id': dashou_id,
- 'chufa_liyou': chufa_liyou,
- 'sqzhuangtai': 0,
- 'jifen': 5
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"商家处罚申请失败: {str(e)}", exc_info=True)
-
- return Response({
- 'code': 10,
- 'msg': '服务器内部错误,申请失败',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
-
-
-# apps/dingdan/views.py
-from django.db.models import Q, OuterRef, Subquery
-from django.core.paginator import Paginator
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from rest_framework_simplejwt.authentication import JWTAuthentication
-from .models import Dingdan, DingdanShangjia
-
-
-
-
-
-
-
-'''class DashouDingdanHuoquView(APIView):
- """
- 获取可抢订单列表
- POST /dingdan/ddhq
- 需要JWT Token认证
- """
-
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 1. 获取请求参数
- data = request.data
- leixing_id = data.get('leixing_id')
- page = int(data.get('page', 1))
- page_size = int(data.get('page_size', 10))
-
- # 2. 构建基础查询条件
- base_query = Q(zhuangtai__in=[1, 7])
- if leixing_id:
- base_query &= Q(leixing_id=leixing_id)
-
- # 3. 构建基础查询集(仅过滤,不执行)
- base_qs = Dingdan.objects.filter(base_query).order_by('-create_time')
-
- # 4. 获取总记录数
- total = base_qs.count()
-
- # 5. 计算分页偏移量
- offset = (page - 1) * page_size
- limit = page_size
-
- # 6. 获取当前页数据(数据库层切片,只返回当前页)
- page_qs = base_qs[offset:offset + limit]
-
- # 7. 使用子查询获取商家昵称(性能优化,只针对当前页)
- shangjia_sub = DingdanShangjia.objects.filter(
- dingdan_id=OuterRef('id')
- ).values('sjnicheng')[:1]
-
- # 8. 获取当前页所有需要的字段(包含商家昵称)
- dingdan_values = page_qs.annotate(
- sjnicheng=Subquery(shangjia_sub)
- ).values(
- 'dingdan_id', 'zhuangtai', 'fadan_pingtai', 'dashou_fencheng',
- 'zhiding_id', 'leixing_id', 'tupian', 'yaoqiuleixing',
- 'huiyuan_id', 'yongjin', 'jieshao', 'beizhu', 'create_time',
- 'sjnicheng'
- )
-
- # 9. 转换为列表以便后续修改
- dingdan_list = list(dingdan_values)
-
- # 10. 批量获取指定打手的头像和昵称
- zhiding_ids = {item['zhiding_id'] for item in dingdan_list if item.get('zhiding_id')}
- zhiding_info = {}
- if zhiding_ids:
- # 一次性查询所有打手信息(通过 UserMain 关联 UserDashou)
- dashou_query = UserMain.objects.filter(
- yonghuid__in=zhiding_ids
- ).select_related('dashou_profile').annotate(
- nicheng=F('dashou_profile__nicheng')
- ).values('yonghuid', 'avatar', 'nicheng')
-
- zhiding_info = {
- item['yonghuid']: {
- 'avatar': item['avatar'] or '',
- 'nicheng': item['nicheng'] or ''
- }
- for item in dashou_query
- }
-
- # 11. 格式化返回数据,并添加打手头像和昵称
- formatted_list = []
- for order in dingdan_list:
- zid = order.get('zhiding_id')
- info = zhiding_info.get(zid, {})
- formatted_order = {
- 'dingdan_id': order['dingdan_id'],
- 'zhuangtai': order['zhuangtai'],
- 'pingtai': order['fadan_pingtai'],
- 'fadan_pingtai': order['fadan_pingtai'],
- 'dashou_fencheng': float(order['dashou_fencheng']) if order['dashou_fencheng'] else 0.0,
- 'zhiding_id': order['zhiding_id'] or '',
- 'leixing_id': order['leixing_id'] or 0,
- 'tupian': order['tupian'] or '',
- 'yaoqiuleixing': order['yaoqiuleixing'] or 0,
- 'huiyuan_id': order['huiyuan_id'] or '',
- 'yajin': float(order['yongjin']) if order['yongjin'] else 0.0,
- 'yongjin': float(order['yongjin']) if order['yongjin'] else 0.0,
- 'jieshao': order['jieshao'] or '',
- 'beizhu': order['beizhu'] or '',
- 'creat_time': order['create_time'].strftime('%Y-%m-%d %H:%M:%S') if order['create_time'] else '',
- 'sjnicheng': order['sjnicheng'] or '',
- 'zhiding_uid': order['zhiding_id'] or '',
- # 新增字段
- 'zhiding_avatar': info.get('avatar', ''),
- 'zhiding_nicheng': info.get('nicheng', ''),
- }
- formatted_list.append(formatted_order)
-
- # 12. 判断是否有更多数据
- has_more = (page * page_size) < total
-
- return Response({
- 'code': 200,
- 'msg': '获取成功',
- 'data': {
- 'list': formatted_list,
- 'has_more': has_more,
- 'total': total,
- 'current_page': page,
- 'page_size': page_size,
- 'total_pages': (total + page_size - 1) // page_size,
- }
- })'''
-
-
-
-
-
-
-
-
-
-# dingdan/views.py
-from django.db.models import Q, OuterRef, Subquery, F, Prefetch
-
-from dengji.models import DingdanBiaoqian
-from dengji.models import YonghuChenghao, Chenghao
-
-
-
-class DashouDingdanHuoquView(APIView):
- """
- 获取可抢订单列表(支持标签筛选)
- POST /dingdan/ddhq
- 需要JWT Token认证
- """
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 获取请求参数
- data = request.data
- leixing_id = data.get('leixing_id')
- page = int(data.get('page', 1))
- page_size = int(data.get('page_size', 10))
- biaoqian_id = data.get('biaoqian_id')
-
- # 2. 构建基础查询条件
- base_query = Q(zhuangtai__in=[1, 7])
- if leixing_id:
- base_query &= Q(leixing_id=leixing_id)
-
- # 3. 标签筛选:先查出符合条件的订单ID,避免跨表 JOIN 排序冲突
- if biaoqian_id and int(biaoqian_id) > 0:
- biaoqian_order_ids = DingdanBiaoqian.objects.filter(
- chenghao_id=int(biaoqian_id)
- ).values_list('dingdan_id', flat=True)
- base_query &= Q(dingdan_id__in=list(biaoqian_order_ids))
-
- # 4. 构建基础查询集
- base_qs = Dingdan.objects.filter(base_query).order_by('-create_time').distinct()
-
- # 5. 获取总记录数
- total = base_qs.count()
-
- # 6. 计算分页偏移量
- offset = (page - 1) * page_size
- limit = page_size
-
- # 7. 获取当前页数据
- page_qs = base_qs[offset:offset + limit]
-
- # 8. 使用子查询获取商家昵称
- shangjia_sub = DingdanShangjia.objects.filter(
- dingdan__dingdan_id=OuterRef('dingdan_id')
- ).values('sjnicheng')[:1]
-
- # 9. 获取当前页所有需要的字段
- dingdan_values = page_qs.annotate(
- sjnicheng=Subquery(shangjia_sub)
- ).values(
- 'dingdan_id', 'zhuangtai', 'fadan_pingtai', 'dashou_fencheng',
- 'zhiding_id', 'leixing_id', 'tupian', 'yaoqiuleixing',
- 'huiyuan_id', 'yongjin', 'jieshao', 'beizhu', 'create_time',
- 'sjnicheng'
- )
-
- # 10. 转换为列表
- dingdan_list = list(dingdan_values)
-
- # 11. 批量获取指定打手的头像和昵称
- zhiding_ids = {item['zhiding_id'] for item in dingdan_list if item.get('zhiding_id')}
- zhiding_info = {}
- if zhiding_ids:
- dashou_query = UserMain.objects.filter(
- yonghuid__in=zhiding_ids
- ).select_related('dashou_profile').annotate(
- nicheng=F('dashou_profile__nicheng')
- ).values('yonghuid', 'avatar', 'nicheng')
- zhiding_info = {
- item['yonghuid']: {
- 'avatar': item['avatar'] or '',
- 'nicheng': item['nicheng'] or ''
- }
- for item in dashou_query
- }
-
- # ============ 批量获取三层标签 ============
- order_ids = [item['dingdan_id'] for item in dingdan_list]
-
- # --- 订单需求标签 ---
- xuqiu_tags_map = {}
- if order_ids:
- try:
- dbq_qs = DingdanBiaoqian.objects.filter(
- dingdan_id__in=order_ids
- ).select_related('chenghao')
- for dbq in dbq_qs:
- oid = dbq.dingdan_id
- if oid not in xuqiu_tags_map:
- xuqiu_tags_map[oid] = []
- ch = dbq.chenghao
- xuqiu_tags_map[oid].append({
- 'id': ch.id,
- 'mingcheng': ch.mingcheng,
- 'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
- })
- except Exception as e:
- logger.error(f"查询订单需求标签失败: {e}", exc_info=True)
-
- # --- 所有需要查询标签的用户ID集合(指定打手 + 商家) ---
- yonghuid_set = set(zhiding_ids)
- shangjia_order_ids = [item['dingdan_id'] for item in dingdan_list if item['fadan_pingtai'] == 2]
- shangjia_id_map = {}
- if shangjia_order_ids:
- # 使用跨表查询 dingdan__dingdan_id__in 匹配字符串订单ID
- sj_exts = DingdanShangjia.objects.filter(
- dingdan__dingdan_id__in=shangjia_order_ids
- ).values('dingdan_id', 'shangjia_id')
- for sj in sj_exts:
- shangjia_id_map[sj['dingdan_id']] = sj['shangjia_id']
- if sj['shangjia_id']:
- yonghuid_set.add(sj['shangjia_id'])
-
- # 将 yonghuid 字符串转换为 UserMain 的主键 id
- user_id_map = {}
- if yonghuid_set:
- users = UserMain.objects.filter(yonghuid__in=yonghuid_set).values('id', 'yonghuid')
- for u in users:
- user_id_map[u['yonghuid']] = u['id']
-
- user_main_ids = list(user_id_map.values())
- user_tags_map = {}
- if user_main_ids:
- uc_qs = YonghuChenghao.objects.filter(
- yonghu_id__in=user_main_ids
- ).select_related('chenghao')
- for uc in uc_qs:
- uid = uc.yonghu_id
- if uid not in user_tags_map:
- user_tags_map[uid] = []
- user_tags_map[uid].append(uc.chenghao)
-
- dashou_tags_map = {}
- shangjia_tags_map = {}
- for item in dingdan_list:
- oid = item['dingdan_id']
- # 指定打手标签
- zhid = item['zhiding_id']
- dashou_tags = []
- if zhid and zhid in user_id_map:
- uid = user_id_map[zhid]
- if uid in user_tags_map:
- for ch in user_tags_map[uid]:
- if ch.leixing == 'dashou':
- dashou_tags.append({
- 'id': ch.id,
- 'mingcheng': ch.mingcheng,
- 'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
- })
- dashou_tags_map[oid] = dashou_tags
-
- # 商家标签
- sj_tags = []
- if item['fadan_pingtai'] == 2 and oid in shangjia_id_map:
- sj_yonghuid = shangjia_id_map[oid]
- if sj_yonghuid and sj_yonghuid in user_id_map:
- uid = user_id_map[sj_yonghuid]
- if uid in user_tags_map:
- for ch in user_tags_map[uid]:
- if ch.leixing == 'shangjia':
- sj_tags.append({
- 'id': ch.id,
- 'mingcheng': ch.mingcheng,
- 'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
- })
- shangjia_tags_map[oid] = sj_tags
-
- # 12. 格式化返回数据
- formatted_list = []
- for order in dingdan_list:
- oid = order['dingdan_id']
- zid = order.get('zhiding_id')
- info = zhiding_info.get(zid, {})
- formatted_list.append({
- 'dingdan_id': order['dingdan_id'],
- 'zhuangtai': order['zhuangtai'],
- 'pingtai': order['fadan_pingtai'],
- 'fadan_pingtai': order['fadan_pingtai'],
- 'dashou_fencheng': float(order['dashou_fencheng']) if order['dashou_fencheng'] else 0.0,
- 'zhiding_id': order['zhiding_id'] or '',
- 'leixing_id': order['leixing_id'] or 0,
- 'tupian': order['tupian'] or '',
- 'yaoqiuleixing': order['yaoqiuleixing'] or 0,
- 'huiyuan_id': order['huiyuan_id'] or '',
- 'yajin': float(order['yongjin']) if order['yongjin'] else 0.0,
- 'yongjin': float(order['yongjin']) if order['yongjin'] else 0.0,
- 'jieshao': order['jieshao'] or '',
- 'beizhu': order['beizhu'] or '',
- 'creat_time': order['create_time'].strftime('%Y-%m-%d %H:%M:%S') if order['create_time'] else '',
- 'sjnicheng': order['sjnicheng'] or '',
- 'zhiding_uid': order['zhiding_id'] or '',
- 'zhiding_avatar': info.get('avatar', ''),
- 'zhiding_nicheng': info.get('nicheng', ''),
- # 三层标签
- 'xuqiu_biaoqian': xuqiu_tags_map.get(oid, []),
- 'dashou_biaoqian': dashou_tags_map.get(oid, []),
- 'shangjia_biaoqian': shangjia_tags_map.get(oid, []),
- })
-
- has_more = (page * page_size) < total
-
- return Response({
- 'code': 200,
- 'msg': '获取成功',
- 'data': {
- 'list': formatted_list,
- 'has_more': has_more,
- 'total': total,
- 'current_page': page,
- 'page_size': page_size,
- 'total_pages': (total + page_size - 1) // page_size,
- }
- })
-
- except Exception as e:
- logger.error(f"订单列表查询失败: {e}", exc_info=True)
- return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
-
- def _parse_texiao_json(self, texiao_miaoshu):
- if not texiao_miaoshu:
- return {}
- try:
- return json.loads(texiao_miaoshu)
- except:
- return {}
-
-
-# apps/dingdan/views.py
-from shangpin.models import ShangpinLeixing # 假设这是你的商品类型模型
-
-
-class ShangpinLeixingHuoquView(APIView):
- """
- 获取商品类型列表
- POST /dingdan/ddlxhq
- 需要JWT Token认证
- """
- authentication_classes = [JWTAuthentication]
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 1. 查询商品类型表
- # 假设你的商品类型模型字段为:id, jieshao, tupian_url
- leixing_list = ShangpinLeixing.objects.all().values(
- 'id', # 商品类型ID
- 'jieshao', # 类型介绍
- 'tupian_url' # 类型图片URL(相对URL)
- ).order_by('id') # 按ID排序,可根据需要调整
-
- # 2. 格式化返回数据
- formatted_list = []
- for leixing in leixing_list:
- formatted_leixing = {
- 'id': leixing['id'],
- 'jieshao': leixing['jieshao'] or '', # 处理空值
- 'tupian_url': leixing['tupian_url'] or '' # 处理空值
- }
- formatted_list.append(formatted_leixing)
-
- # 3. 返回响应
- return Response({
- 'code': 200,
- 'msg': '获取成功',
- 'data': {
- 'list': formatted_list,
- 'total': len(formatted_list)
- }
- })
-
-
-# apps/dingdan/views.py
-from django.db import transaction
-from django.utils import timezone
-from django.db.models import F
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from rest_framework_simplejwt.authentication import JWTAuthentication
-import logging
-
-from .models import Dingdan, Liaotian
-
-from utils.goeasy_service import GoEasyService
-
-logger = logging.getLogger(__name__)
-
-
-from peizhi.models import Club
-
-logger = logging.getLogger(__name__)
-
-
-
-
-
-
-# apps/dingdan/views.py
-from django.db import transaction
-from django.utils import timezone
-from django.db.models import F
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from rest_framework_simplejwt.authentication import JWTAuthentication
-import logging
-import requests
-import json
-
-from .models import Dingdan, DingdanPingtai, DingdanShangjia
-
-
-logger = logging.getLogger(__name__)
-
-from django.db import transaction
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from django.utils import timezone
-from django.db.models import F
-from django.conf import settings
-import json
-import requests
-from datetime import datetime
-
-
-
-
-
-
-from peizhi.models import Club
-
-logger = logging.getLogger(__name__)
-
-
-
-
-
-
-'''class QiangdanView(APIView):
- authentication_classes = [JWTAuthentication]
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- data = request.data
- dingdan_id = data.get('dingdan_id')
- if not dingdan_id:
- return Response({'code': 400, 'msg': '订单ID不能为空'}, status=400)
-
- try:
- with transaction.atomic():
- # 1. 验证打手身份和状态
- dashou_profile = self._validate_dashou_status(request.user)
- if not dashou_profile:
- return Response({'code': 403, 'msg': '您不是打手或账号状态异常'}, status=403)
-
- # 2. 锁定并验证订单(行锁)
- order_result = self._validate_and_lock_order(dingdan_id, request.user.yonghuid)
- if isinstance(order_result, str):
- return Response({'code': 400, 'msg': order_result})
- order = order_result
-
- is_cross = order.is_cross == 1
- dispatch_type = order.dispatch_type # 1=我方派单,2=对方派单
-
- # ========== 跨平台订单处理 ==========
- if is_cross and dispatch_type == 2:
- # 对方派单(跨平台且对方派给我方)
- validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
- if not validation_result['success']:
- return Response({'code': 400, 'msg': validation_result['message']})
-
- # 向对方平台发送抢单询问
- claim_allowed = self._request_partner_check_claim(order, request.user.yonghuid)
- if claim_allowed is None:
- return Response({'code': 500, 'msg': '跨平台通信失败,请重试'}, status=500)
- elif claim_allowed is False:
- if order.zhuangtai == 1:
- order.zhuangtai = 21
- order.save()
- logger.info(f"订单 {dingdan_id} 被对方拒绝抢单,状态改为21")
- else:
- order.zhuangtai = 20
- order.save()
- logger.info(f"订单 {dingdan_id} 已被其他平台抢走,状态改为20")
- return Response({'code': 400, 'msg': '订单已被其他平台打手抢走或不可抢'})
-
- # 允许抢单,执行本地抢单
- self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
-
- # 更新抢单统计(对方派单,我方抢到)
- try:
- update_daily_dispatch_stat(
- direction=2, # 对方派单
- partner_club_id=order.partner_club_id or '000001',
- claimed_amount=order.jine, # 订单金额
- claimed_count=1
- )
- except Exception as e:
- logger.error(f"更新抢单统计失败: {e}", exc_info=True)
-
- elif is_cross and dispatch_type == 1:
- # 我方派单
- validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
- if not validation_result['success']:
- return Response({'code': 400, 'msg': validation_result['message']})
-
- self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
-
- # 异步通知所有合作俱乐部:此订单已被抢
- self._async_notify_partners_order_claimed(order)
-
- else:
- # 普通订单
- validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
- if not validation_result['success']:
- return Response({'code': 400, 'msg': validation_result['message']})
- self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
-
- # 抢单成功后建立聊天关系(事务外)
- chat_success = self._establish_chat_relationship(order, request.user.yonghuid, dashou_profile, request.user.avatar)
-
- # 🔥 新增:调用打手每日统计(成交)
- try:
- update_dashou_daily_by_action(
- yonghuid=order.jiedan_dashou_id,
- amount=order.dashou_fencheng,
- action=1 # 1 表示接单(接单)
- )
- logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功")
- except Exception as e:
- logger.error(f"打手每日统计更新失败: {e}")
- # 不影响主流程
-
- return Response({
- 'code': 200,
- 'msg': '抢单成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'chat_established': chat_success
- }
- })
-
- except Exception as e:
- logger.error(f"抢单异常: {str(e)}", exc_info=True)
- return Response({'code': 500, 'msg': '系统异常,请稍后重试'}, status=500)
-
- # ---------- 辅助方法 ----------
- def _validate_dashou_status(self, user):
- try:
- dashou_profile = user.dashou_profile
- if (dashou_profile.zhuangtai != 1 or
- dashou_profile.zhanghaozhuangtai != 1 or
- dashou_profile.jifen < 5):
- return None
- return dashou_profile
- except UserDashou.DoesNotExist:
- return None
-
- def _validate_and_lock_order(self, dingdan_id, dashou_yonghuid):
- try:
- order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)
- if order.zhuangtai not in [1, 7]:
- return "订单状态不可抢"
- if order.zhuangtai == 7:
- if not order.zhiding_id or order.zhiding_id != dashou_yonghuid:
- return "此订单为指定单,您不是指定打手"
- if order.jiedan_dashou_id:
- return "订单已被其他打手抢走"
- return order
- except Dingdan.DoesNotExist:
- return "订单不存在"
-
- def _validate_order_requirements(self, order, dashou_profile, dashou_yonghuid):
- if order.yaoqiuleixing == 1:
- return self._validate_huiyuan_requirement(order, dashou_yonghuid)
- elif order.yaoqiuleixing == 2:
- return self._validate_yajin_requirement(order, dashou_profile)
- return {'success': True, 'message': '验证通过'}
-
- def _validate_huiyuan_requirement(self, order, dashou_yonghuid):
- try:
- huiyuan_record = Huiyuangoumai.objects.get(
- huiyuan_id=order.huiyuan_id,
- yonghu_id=dashou_yonghuid
- )
- if huiyuan_record.jiance_shifou_daoqi() or huiyuan_record.huiyuan_zhuangtai != 1:
- return {'success': False, 'message': '会员已过期或未开通对应会员'}
- return {'success': True, 'message': '会员验证通过'}
- except Huiyuangoumai.DoesNotExist:
- return {'success': False, 'message': '未开通对应会员,无法抢此订单'}
-
- def _validate_yajin_requirement(self, order, dashou_profile):
- yajin_required = order.yongjin or 0
- dashou_yajin = dashou_profile.yajin or 0
- if dashou_yajin < yajin_required:
- return {'success': False, 'message': f'押金不足,需要{yajin_required}元,当前有{dashou_yajin}元'}
- return {'success': True, 'message': '押金验证通过'}
-
- def _execute_qiangdan(self, order, dashou_profile, dashou_yonghuid):
- order.zhuangtai = 2
- order.jiedan_dashou_id = dashou_yonghuid
- order.user2_id = f"Ds{dashou_yonghuid}"
- order.save()
-
- dashou_profile.zhuangtai = 0
- dashou_profile.jiedanzongliang = F('jiedanzongliang') + 1
- dashou_profile.jinrijiedan = F('jinrijiedan') + 1
- dashou_profile.jinyuejiedan = F('jinyuejiedan') + 1
- dashou_profile.save()
-
- # ---------- 跨平台询问对方 ----------
- def _request_partner_check_claim(self, order, dashou_id):
- partner_club_id = order.partner_club_id or '000001'
- try:
- club = Club.objects.filter(partner_club_id=partner_club_id).first()
- if not club or not club.partner_domain:
- logger.error(f"未找到对方俱乐部配置: partner_club_id={partner_club_id}")
- return None
-
- my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first()
- if not my_club:
- logger.error(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}")
- return None
-
- payload = {
- 'token': settings.CROSS_PLATFORM_TOKEN,
- 'wofangdingdan_id': order.partner_order_id,
- 'partner_order_id': order.dingdan_id,
- 'our_club_id': my_club.club_id,
- 'partner_club_id': club.partner_club_id,
- 'dashou_id': dashou_id,
- 'sj_id': f"Sj{settings.AGENT_SHANGJIA_ID}"
- }
-
- url = club.partner_domain.rstrip('/') + '/dingdan/partner_check_claim'
- resp = requests.post(url, json=payload, timeout=5)
-
- if resp.status_code != 200:
- logger.error(f"跨平台抢单询问HTTP失败: {resp.status_code}")
- return None
-
- data = resp.json()
- if data.get('code') == 0:
- return data.get('data', {}).get('can_claim') is True
- else:
- logger.error(f"跨平台抢单询问业务失败: {data}")
- return False
- except Exception as e:
- logger.error(f"跨平台抢单询问异常: {e}", exc_info=True)
- return None
-
- # ---------- 异步通知其他俱乐部 ----------
- def _async_notify_partners_order_claimed(self, order):
- def notify_task():
- clubs = Club.objects.filter(is_interop_enabled=True).select_related()
- for club in clubs:
- for attempt in range(1, 4):
- success = self._send_claim_notification_to_partner(order, club)
- if success:
- break
- if attempt < 3:
- import time
- time.sleep(2 ** attempt)
- else:
- logger.error(f"通知俱乐部 {club.club_id} 订单已抢失败,重试3次后仍失败")
-
- thread = threading.Thread(target=notify_task)
- thread.daemon = True
- thread.start()
-
- def _send_claim_notification_to_partner(self, order, club):
- try:
- my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first()
- if not my_club:
- logger.warning(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}")
- return False
-
- payload = {
- 'token': settings.CROSS_PLATFORM_TOKEN,
- 'our_club_id': my_club.club_id,
- 'partner_club_id': club.partner_club_id,
- 'partner_order_id': order.dingdan_id,
- 'claimed': True
- }
- url = club.partner_domain.rstrip('/') + '/dingdan/partner_notify_claim'
- resp = requests.post(url, json=payload, timeout=3)
- return resp.status_code == 200
- except Exception as e:
- logger.error(f"通知俱乐部 {club.club_id} 异常: {e}")
- return False
-
- # ---------- 聊天关系建立(完整版) ----------
- def _establish_chat_relationship(self, order, dashou_yonghuid, dashou_profile, dashou_avatar):
- try:
- dashou_info = {
- 'goeasy_id': f"Ds{dashou_yonghuid}",
- 'avatar': dashou_avatar or '',
- 'nickname': dashou_profile.nicheng or f'打手{dashou_yonghuid}'
- }
-
- laoban_info = self._get_laoban_info(order)
- if not laoban_info:
- return False
-
- # 打手给老板/商家发消息
- success1 = self._send_goeasy_message(
- sender_id=dashou_info['goeasy_id'],
- sender_data={
- "avatar": dashou_info['avatar'],
- "name": dashou_info['nickname'],
- "nickname": dashou_info['nickname']
- },
- receiver_id=laoban_info['goeasy_id'],
- receiver_data={
- "avatar": laoban_info['avatar'],
- "name": laoban_info['nickname'],
- "nickname": laoban_info['nickname']
- },
- message_type="text",
- message_payload={
- "action": "new_chat",
- "partner_id": laoban_info['goeasy_id'],
- "partner_info": {
- "userId": laoban_info['goeasy_id'],
- "avatar": laoban_info['avatar'],
- "name": laoban_info['nickname'],
- "nickname": laoban_info['nickname']
- },
- "order_id": order.dingdan_id,
- "message": f"您好,我已接单:{order.jieshao},您的备注是:{order.beizhu},游戏ID是:{order.nicheng} 请确认订单信息"
- }
- )
-
- # 老板/商家给打手发消息
- success2 = self._send_goeasy_message(
- sender_id=laoban_info['goeasy_id'],
- sender_data={
- "avatar": laoban_info['avatar'],
- "name": laoban_info['nickname'],
- "nickname": laoban_info['nickname']
- },
- receiver_id=dashou_info['goeasy_id'],
- receiver_data={
- "avatar": dashou_info['avatar'],
- "name": dashou_info['nickname'],
- "nickname": dashou_info['nickname']
- },
- message_type="text",
- message_payload={
- "action": "new_chat",
- "partner_id": dashou_info['goeasy_id'],
- "partner_info": {
- "userId": dashou_info['goeasy_id'],
- "avatar": dashou_info['avatar'],
- "name": dashou_info['nickname'],
- "nickname": dashou_info['nickname']
- },
- "order_id": order.dingdan_id,
- "message": f"好的,订单已确认,请开始服务"
- }
- )
-
- return success1 and success2
-
- except Exception as e:
- logger.error(f"建立聊天关系异常: {e}", exc_info=True)
- return False
-
- def _get_laoban_info(self, order):
- try:
- laoban_info = {
- 'goeasy_id': '',
- 'avatar': '',
- 'nickname': ''
- }
-
- if order.fadan_pingtai == 1:
- pingtai_kuozhan = DingdanPingtai.objects.filter(dingdan=order).first()
- if not pingtai_kuozhan or not pingtai_kuozhan.laoban_id:
- return None
- yonghuid = pingtai_kuozhan.laoban_id
- laoban_info['goeasy_id'] = f"Boss{yonghuid}"
- laoban_user = UserMain.objects.filter(yonghuid=yonghuid).first()
- if not laoban_user:
- return None
- laoban_info['avatar'] = laoban_user.avatar or ''
- boss_profile = UserBoss.objects.filter(user=laoban_user).first()
- laoban_info['nickname'] = boss_profile.nickname if boss_profile and boss_profile.nickname else f'老板{yonghuid}'
-
- elif order.fadan_pingtai == 2:
- shangjia_kuozhan = DingdanShangjia.objects.filter(dingdan=order).first()
- if not shangjia_kuozhan or not shangjia_kuozhan.shangjia_id:
- return None
- yonghuid = shangjia_kuozhan.shangjia_id
- laoban_info['goeasy_id'] = f"Sj{yonghuid}"
- shangjia_user = UserMain.objects.filter(yonghuid=yonghuid).first()
- if not shangjia_user:
- return None
- laoban_info['avatar'] = shangjia_user.avatar or ''
- if shangjia_kuozhan.sjnicheng:
- laoban_info['nickname'] = shangjia_kuozhan.sjnicheng
- else:
- shop_profile = UserShangjia.objects.filter(user=shangjia_user).first()
- laoban_info['nickname'] = shop_profile.nicheng if shop_profile else f'商家{yonghuid}'
- else:
- if order.user1_id:
- laoban_info['goeasy_id'] = order.user1_id
- if laoban_info['goeasy_id'].startswith('Boss'):
- yonghuid = laoban_info['goeasy_id'][4:]
- laoban_info['nickname'] = f'老板{yonghuid}'
- elif laoban_info['goeasy_id'].startswith('Sj'):
- yonghuid = laoban_info['goeasy_id'][2:]
- laoban_info['nickname'] = f'商家{yonghuid}'
- else:
- laoban_info['nickname'] = '用户'
- else:
- return None
-
- if not laoban_info['goeasy_id']:
- return None
- return laoban_info
-
- except Exception as e:
- logger.error(f"获取老板信息异常: {e}", exc_info=True)
- return None
-
- def _send_goeasy_message(self, sender_id, sender_data, receiver_id, receiver_data, message_type, message_payload):
- try:
- if not hasattr(settings, 'GOEASY_APPKEY') or not settings.GOEASY_APPKEY:
- return False
- if not hasattr(settings, 'GOEASY_SECRET') or not settings.GOEASY_SECRET:
- return False
- if not hasattr(settings, 'GOEASY_REST_URL') or not settings.GOEASY_REST_URL:
- return False
-
- appkey = settings.GOEASY_APPKEY
- secret = settings.GOEASY_SECRET
- url = settings.GOEASY_REST_URL
-
- if isinstance(message_payload, dict) and 'message' in message_payload:
- payload_text = str(message_payload['message'])
- else:
- payload_text = str(message_payload)
-
- payload_data = {"text": payload_text}
- payload_str = json.dumps(payload_data, ensure_ascii=False)
-
- request_data = {
- "appkey": appkey,
- "senderId": str(sender_id),
- "senderData": {
- "avatar": str(sender_data.get('avatar', '')),
- "name": str(sender_data.get('name', '系统通知'))
- },
- "to": {
- "type": "private",
- "id": str(receiver_id),
- "data": {
- "avatar": str(receiver_data.get('avatar', '')),
- "name": str(receiver_data.get('name', '用户'))
- }
- },
- "type": "text",
- "payload": payload_str
- }
-
- headers = {
- "Content-Type": "application/json",
- "Authorization": f"Bearer {secret}"
- }
-
- response = requests.post(url, headers=headers, json=request_data, timeout=10)
- return response.status_code == 200
-
- except Exception as e:
- logger.error(f"发送GoEasy消息异常: {e}", exc_info=True)
- return False'''
-
-
-
-
-
-# dingdan/views.py
-import logging
-import requests
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status, permissions
-from .models import Dingdan, DingdanPingtai, DingdanShangjia
-from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
-from peizhi.models import ClubConfig
-from utils.chat_utils import _send_group_message, _subscribe_users_to_group
-
-logger = logging.getLogger('dingdan')
-
-# dingdan/views.py
-# dingdan/views.py
-import logging
-import requests
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status, permissions
-from .models import Dingdan, DingdanPingtai, DingdanShangjia
-from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
-from peizhi.models import ClubConfig
-
-logger = logging.getLogger('dingdan')
-
-
-# dingdan/views.py
-
-import logging
-import requests
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import permissions
-from .models import Dingdan, DingdanPingtai, DingdanShangjia
-from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
-from peizhi.models import ClubConfig
-
-logger = logging.getLogger('dingdan')
-
-import logging
-import requests
-import time
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import permissions
-
-from .models import Dingdan, DingdanPingtai, DingdanShangjia
-from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
-from peizhi.models import ClubConfig
-
-logger = logging.getLogger('dingdan')
-
-
-# ----------------- 基础配置获取 -----------------
-def get_self_goeasy_appkey():
- try:
- cfg = ClubConfig.objects.filter(is_self=1).first()
- return cfg.chat_api_key if cfg else None
- except Exception:
- return None
-
-
-def get_self_goeasy_secret():
- try:
- cfg = ClubConfig.objects.filter(is_self=1).first()
- return cfg.chat_api_id if cfg else None
- except Exception:
- return None
-
-
-def get_self_storage_domain():
- try:
- cfg = ClubConfig.objects.filter(is_self=1).first()
- return cfg.storage_bucket_domain if cfg else ''
- except Exception:
- return ''
-
-
-def full_avatar(relative_url, bucket_domain=None):
- if not relative_url:
- return ''
- if relative_url.startswith('http'):
- return relative_url
- domain = bucket_domain or get_self_storage_domain()
- if domain:
- domain = domain.rstrip('/') + '/'
- return domain + relative_url.lstrip('/')
- return relative_url
-
-
-# ----------------- GoEasy API 封装 -----------------
-def subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None):
- """订阅用户到群组,确保能收发该群消息"""
- if not appkey:
- appkey = get_self_goeasy_appkey()
- if not secret:
- secret = get_self_goeasy_secret()
- if not appkey:
- logger.error("订阅失败:GoEasy AppKey 未配置")
- return False
- url = 'https://rest-hangzhou.goeasy.io/v2/im/subscribe-groups'
- body = {"appkey": appkey, "userIds": user_ids, "groupIds": group_ids}
- headers = {"Content-Type": "application/json"}
- if secret:
- headers["Authorization"] = f"Bearer {secret}"
- try:
- r = requests.post(url, headers=headers, json=body, timeout=10)
- if r.status_code == 200:
- logger.info(f"订阅成功:用户 {user_ids} -> 群 {group_ids}")
- return True
- logger.error(f"订阅失败:{r.status_code} {r.text}")
- return False
- except Exception as e:
- logger.exception("订阅异常")
- return False
-
-
-def send_group_message(
- appkey, secret, group_id, sender_id, sender_name, sender_avatar,
- message_text='', custom_payload=None, group_name='', order_id='', is_cross=0
-):
- """
- 通过 GoEasy REST API 发送群消息。
- message_text: 普通文本内容
- custom_payload: 自定义消息体(如订单卡片),此时 type 设为 'order'
- """
- url = 'https://rest-hangzhou.goeasy.io/v2/im/message'
- to_data = {
- "name": group_name or group_id,
- "avatar": sender_avatar or '',
- "orderId": order_id,
- "isCross": is_cross
- }
- msg_type = 'order' if custom_payload else 'text'
- payload = custom_payload if custom_payload else message_text
-
- body = {
- "appkey": appkey,
- "senderId": sender_id,
- "senderData": {"avatar": sender_avatar, "name": sender_name},
- "to": {"type": "group", "id": group_id, "data": to_data},
- "type": msg_type,
- "payload": payload
- }
- headers = {"Content-Type": "application/json"}
- if secret:
- headers["Authorization"] = f"Bearer {secret}"
-
- for attempt in range(3): # 最多重试3次
- try:
- r = requests.post(url, headers=headers, json=body, timeout=10)
- if r.status_code == 200:
- logger.info(f"消息发送成功:sender={sender_id}, group={group_id}, type={msg_type}")
- return True
- logger.warning(f"消息发送失败 (尝试 {attempt+1}/3):状态码 {r.status_code}, 响应 {r.text}")
- except Exception as e:
- logger.warning(f"消息发送异常 (尝试 {attempt+1}/3):{e}")
- if attempt < 2:
- time.sleep(1)
- logger.error(f"消息最终发送失败:sender={sender_id}, group={group_id}")
- return False
-
-
-# ----------------- 权限校验 -----------------
-def check_user_permission(identity_type, uid, order):
- """验证用户身份、账号状态、以及是否属于该订单"""
- try:
- user = UserMain.objects.get(yonghuid=uid)
- except UserMain.DoesNotExist:
- return False, '用户不存在'
-
- if identity_type == 'dashou':
- try:
- dashou = UserDashou.objects.get(user=user)
- if dashou.zhanghaozhuangtai != 1 :
- return False, '打手账号已被封禁或状态异常'
- except UserDashou.DoesNotExist:
- return False, '该用户不是打手'
- if order.jiedan_dashou_id != uid:
- return False, '你不是该订单的接单打手'
-
- elif identity_type == 'shangjia':
- try:
- shop = UserShangjia.objects.get(user=user)
- if shop.zhuangtai != 1:
- return False, '商家账号已被禁用'
- except UserShangjia.DoesNotExist:
- return False, '该用户不是商家'
- try:
- if order.shangjia_kuozhan.shangjia_id != uid:
- return False, '你不是该订单的发布商家'
- except DingdanShangjia.DoesNotExist:
- return False, '订单缺少商家信息'
-
- elif identity_type == 'boss':
- try:
- UserBoss.objects.get(user=user) # 老板暂无封禁字段,仅需存在
- except UserBoss.DoesNotExist:
- return False, '该用户不是老板'
- try:
- if order.pingtai_kuozhan.laoban_id != uid:
- return False, '你不是该订单的老板'
- except DingdanPingtai.DoesNotExist:
- return False, '订单缺少平台信息'
-
- else:
- return False, '不支持的身份类型'
- return True, ''
-
-
-# ----------------- 跨平台对方配置获取 -----------------
-def get_partner_club_config(partner_club_id):
- try:
- return ClubConfig.objects.get(club_id=partner_club_id, is_self=0)
- except ClubConfig.DoesNotExist:
- return None
-
-
-# ----------------- 主视图 -----------------
-
-
-
-class DaiLiQunLiaoXiaoXiView(APIView):
- """
- 代理发送群聊消息
- 支持:
- - 非跨平台订单:直接用我方 GoEasy Key 以真实身份发送
- - 跨平台订单(双方任意一方派单):除了我方群外,还需用对方 GoEasy Key 以代理身份向对方群发送
- 跨平台判断条件:订单同时存在 partner_order_id 和 partner_club_id 即视为跨平台
- """
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- # ===== 1. 参数解析 =====
- order_id = request.data.get('orderId')
- identity_type = request.data.get('identityType') # dashou / shangjia / boss
- sender_name = request.data.get('senderName', '')
- sender_avatar_raw = request.data.get('senderAvatar', '')
- message_type = request.data.get('messageType', 'text')
- message_text = request.data.get('text', '')
- order_payload = request.data.get('orderPayload', None)
-
- logger.info(f"[群聊代理] 收到请求: orderId={order_id}, identityType={identity_type}, "
- f"messageType={message_type}, text={message_text[:20]}...")
-
- if not order_id or not identity_type:
- return Response({'code': 400, 'msg': '缺少 orderId 或 identityType'})
- if identity_type not in ('dashou', 'shangjia', 'boss'):
- return Response({'code': 400, 'msg': 'identityType 无效'})
-
- # ===== 2. 构造 senderId =====
- uid = request.user.yonghuid
- prefix_map = {'dashou': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'}
- sender_id = prefix_map[identity_type] + uid
- logger.info(f"[群聊代理] 登录用户 yonghuid={uid}, 构造 senderId={sender_id}")
-
- # ===== 3. 查询订单 =====
- try:
- order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=order_id)
- except Dingdan.DoesNotExist:
- logger.warning(f"[群聊代理] 订单 {order_id} 不存在")
- return Response({'code': 404, 'msg': '订单不存在'})
-
- # ===== 4. 权限校验 =====
- valid, err_msg = check_user_permission(identity_type, uid, order)
- if not valid:
- logger.warning(f"[群聊代理] 权限校验失败: {err_msg}, uid={uid}, identity={identity_type}")
- return Response({'code': 403, 'msg': err_msg})
-
- # ===== 5. 我方 GoEasy 配置 =====
- self_appkey = get_self_goeasy_appkey()
- self_secret = get_self_goeasy_secret()
- if not self_appkey:
- logger.error("[群聊代理] 我方 GoEasy AppKey 未配置")
- return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
-
- # ===== 6. 公共群信息 =====
- real_group_id = f"group_{order.dingdan_id}"
- group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}"
- sender_avatar = full_avatar(sender_avatar_raw) if sender_avatar_raw else full_avatar('')
-
- # ===== 7. 订阅发送者到群 =====
- if not subscribe_users_to_group([sender_id], [real_group_id], self_appkey, self_secret):
- logger.error("[群聊代理] 订阅群组失败")
- return Response({'code': 500, 'msg': '订阅群组失败'})
-
- # ===== 8. 判断跨平台(只看对方订单ID和俱乐部ID) =====
- partner_order_id = order.partner_order_id
- partner_club_id = order.partner_club_id
- is_cross = 1 if (partner_order_id and partner_club_id) else 0
- logger.info(f"[群聊代理] 订单 {order_id} partner_order_id={partner_order_id}, partner_club_id={partner_club_id}, 判定跨平台={is_cross}")
-
- custom_payload = order_payload if message_type == 'order' else None
- text_content = message_text
-
- # ---------- 先向我方群发送消息(永远执行) ----------
- local_ok = send_group_message(
- appkey=self_appkey,
- secret=self_secret,
- group_id=real_group_id,
- sender_id=sender_id,
- sender_name=sender_name,
- sender_avatar=sender_avatar,
- message_text=text_content,
- custom_payload=custom_payload,
- group_name=group_name,
- order_id=order.dingdan_id,
- is_cross=is_cross
- )
- if not local_ok:
- return Response({'code': 500, 'msg': '消息发送失败'})
-
- # ---------- 跨平台转发(如果有对方订单ID和俱乐部ID) ----------
- if is_cross:
- logger.info("[群聊代理] 满足跨平台条件,准备双平台发送")
- partner_cfg = get_partner_club_config(partner_club_id)
- if not partner_cfg or not partner_cfg.chat_api_key:
- logger.warning("[群聊代理] 对方俱乐部配置不可用,仅发送我方群")
- return Response({'code': 200, 'msg': '发送成功(对方GoEasy未配置)'})
-
- partner_appkey = partner_cfg.chat_api_key
- partner_secret = partner_cfg.chat_api_id or ''
- partner_group_id = f"group_{partner_order_id}"
-
- # 构造代理身份
- agent_sender_id = f"{order.dingdan_id}_{sender_id}"
- if identity_type == 'dashou':
- agent_sender_name = f"[我方]{sender_name}"
- elif identity_type == 'shangjia':
- agent_sender_name = f"[我方商家]{sender_name}"
- else:
- agent_sender_name = f"[我方老板]{sender_name}"
- agent_avatar = full_avatar('')
-
- logger.info(f"[群聊代理] 对方群组={partner_group_id}, 代理身份={agent_sender_id}/{agent_sender_name}")
-
- # 向对方群发送代理消息
- partner_ok = send_group_message(
- appkey=partner_appkey,
- secret=partner_secret,
- group_id=partner_group_id,
- sender_id=agent_sender_id,
- sender_name=agent_sender_name,
- sender_avatar=agent_avatar,
- message_text=text_content,
- custom_payload=custom_payload,
- group_name='跨平台订单交流',
- order_id=partner_order_id,
- is_cross=1
- )
- logger.info(f"[群聊代理] 对方群发送结果: {partner_ok}")
-
- if partner_ok:
- return Response({'code': 200, 'msg': '发送成功(双平台)'})
- else:
- return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'})
- else:
- logger.info("[群聊代理] 非跨平台,仅发送我方群")
- return Response({'code': 200, 'msg': '发送成功'})
-
-
-#通过跨平台字段判断是否是跨平台
-'''class DaiLiQunLiaoXiaoXiView(APIView):
- """
- 代理发送群聊消息
- - 永远向我方群 group_{订单ID} 发送真实身份消息
- - 如果是跨平台订单且存在 partner_order_id + partner_club_id,且对方配置有效,
- 则同时向对方群 group_{partner_order_id} 发送代理消息(不管派单方向)
- - 前端传递 identityType: 'dashou' / 'shangjia' / 'boss'
- """
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- # ===== 1. 参数解析 =====
- order_id = request.data.get('orderId')
- identity_type = request.data.get('identityType') # 原有字段:dashou / shangjia / boss
- sender_name = request.data.get('senderName', '')
- sender_avatar_raw = request.data.get('senderAvatar', '')
- message_type = request.data.get('messageType', 'text')
- message_text = request.data.get('text', '')
- order_payload = request.data.get('orderPayload', None)
-
- logger.info(f"[群聊代理] orderId={order_id}, identityType={identity_type}, text={message_text[:20]}...")
-
- if not order_id or not identity_type:
- return Response({'code': 400, 'msg': '缺少 orderId 或 identityType'})
- if identity_type not in ('dashou', 'shangjia', 'boss'):
- return Response({'code': 400, 'msg': 'identityType 无效 (dashou/shangjia/boss)'})
-
- # ===== 2. 构造 senderId =====
- uid = request.user.yonghuid
- prefix_map = {'dashou': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'}
- sender_id = prefix_map[identity_type] + uid
- # 记录真实身份名称(用于代理命名)
- identity_name_map = {'dashou': '打手', 'shangjia': '商家', 'boss': '老板'}
- identity_name = identity_name_map[identity_type]
- logger.info(f"[群聊代理] 用户 yonghuid={uid}, senderId={sender_id}, 身份={identity_name}")
-
- # ===== 3. 查询订单 =====
- try:
- order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=order_id)
- except Dingdan.DoesNotExist:
- logger.warning(f"[群聊代理] 订单 {order_id} 不存在")
- return Response({'code': 404, 'msg': '订单不存在'})
-
- # ===== 4. 权限校验 =====
- valid, err_msg = check_user_permission(identity_type, uid, order)
- if not valid:
- logger.warning(f"[群聊代理] 权限校验失败: {err_msg}, uid={uid}, role={identity_type}")
- return Response({'code': 403, 'msg': err_msg})
-
- # ===== 5. 我方 GoEasy 配置 =====
- self_appkey = get_self_goeasy_appkey()
- self_secret = get_self_goeasy_secret()
- if not self_appkey:
- logger.error("[群聊代理] 我方 GoEasy AppKey 未配置")
- return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
-
- # ===== 6. 公共群信息(我方) =====
- real_group_id = f"group_{order.dingdan_id}"
- group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}"
- sender_avatar = full_avatar(sender_avatar_raw) if sender_avatar_raw else full_avatar('')
- is_cross = order.is_cross
-
- # ===== 7. 先发送到我方群(永远执行) =====
- local_ok = send_group_message(
- appkey=self_appkey,
- secret=self_secret,
- group_id=real_group_id,
- sender_id=sender_id,
- sender_name=sender_name,
- sender_avatar=sender_avatar,
- message_text=message_text,
- custom_payload=order_payload if message_type == 'order' else None,
- group_name=group_name,
- order_id=order.dingdan_id,
- is_cross=1 if is_cross == 1 else 0
- )
- logger.info(f"[群聊代理] 我方群发送结果: {local_ok}")
- if not local_ok:
- return Response({'code': 500, 'msg': '消息发送失败'})
-
- # ===== 8. 判断是否需要向对方群发代理消息 =====
- partner_club_id = order.partner_club_id
- partner_order_id = order.partner_order_id
-
- # 非跨平台,或者缺少对方订单/俱乐部ID,直接结束
- if is_cross != 1 or not partner_club_id or not partner_order_id:
- logger.info("[群聊代理] 未满足跨平台双发条件,仅发送我方群")
- return Response({'code': 200, 'msg': '发送成功'})
-
- # 获取对方俱乐部配置
- partner_cfg = get_partner_club_config(partner_club_id)
- if not partner_cfg or not partner_cfg.chat_api_key:
- logger.warning(f"[群聊代理] 对方俱乐部配置不可用,仅发送我方群")
- return Response({'code': 200, 'msg': '发送成功(对方GoEasy未配置)'})
-
- # ===== 9. 构造代理身份并发送到对方群 =====
- agent_sender_id = f"{order.dingdan_id}_{sender_id}" # 代理ID
- agent_sender_name = f"[我方{identity_name}]{uid}" # 代理名称:如 [我方打手]1234567
- agent_avatar = full_avatar('')
-
- partner_group_id = f"group_{partner_order_id}"
- logger.info(f"[群聊代理] 对方群组={partner_group_id}, 代理身份={agent_sender_id}/{agent_sender_name}")
-
- partner_ok = send_group_message(
- appkey=partner_cfg.chat_api_key,
- secret=partner_cfg.chat_api_id or '',
- group_id=partner_group_id,
- sender_id=agent_sender_id,
- sender_name=agent_sender_name,
- sender_avatar=agent_avatar,
- message_text=message_text,
- custom_payload=order_payload if message_type == 'order' else None,
- group_name='跨平台订单交流',
- order_id=partner_order_id,
- is_cross=1
- )
- logger.info(f"[群聊代理] 对方群发送结果: {partner_ok}")
-
- if partner_ok:
- return Response({'code': 200, 'msg': '发送成功(双平台)'})
- else:
- return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'})'''
-
-
-
-
-
-
-
-
-
-
-
-
-from peizhi.models import ClubConfig, ShangjiaLianjie
-logger = logging.getLogger('dingdan')
-
-# ---------- 基础配置 ----------
-def get_self_goeasy_appkey():
- try:
- cfg = ClubConfig.objects.filter(is_self=1).first()
- return cfg.chat_api_key if cfg else None
- except Exception:
- return None
-
-def get_self_goeasy_secret():
- try:
- cfg = ClubConfig.objects.filter(is_self=1).first()
- return cfg.chat_api_id if cfg else None
- except Exception:
- return None
-
-def get_partner_club_config(partner_club_id):
- try:
- return ClubConfig.objects.get(club_id=partner_club_id, is_self=0)
- except ClubConfig.DoesNotExist:
- return None
-
-
-class ShangjiaLianjieLiuYanZhuanFaView(APIView):
- """
- 商家链接留言转发接口
- 路径:POST /dingdan/sjljlbxxzf
- 固定以 B{商家ID} 身份发送群消息。通过 partner_order_id/partner_club_id 判断是否跨平台。
- """
- permission_classes = []
-
- def post(self, request):
- token = request.data.get('token')
- text = request.data.get('text', '')
-
- if not token or not text:
- return Response({'code': 400, 'msg': '缺少参数'})
-
- try:
- lianjie = ShangjiaLianjie.objects.get(lianjie_token=token)
- except ShangjiaLianjie.DoesNotExist:
- return Response({'code': 404, 'msg': '链接不存在或已失效'})
-
- if not lianjie.dingdan_id:
- return Response({'code': 400, 'msg': '链接缺少订单ID'})
-
- try:
- order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=lianjie.dingdan_id)
- except Dingdan.DoesNotExist:
- return Response({'code': 500, 'msg': '订单数据异常'})
-
- shangjia_uid = lianjie.yonghu_id
- sender_id = f"B{shangjia_uid}"
- sender_name = "老板"
- sender_avatar = ''
-
- appkey = get_self_goeasy_appkey()
- secret = get_self_goeasy_secret()
- if not appkey:
- return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
-
- group_id = f"group_{order.dingdan_id}"
- group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}"
-
- local_cross_flag = 1 if (order.partner_order_id and order.partner_club_id) else 0
-
- if not _subscribe_users_to_group([sender_id], [group_id], appkey, secret):
- return Response({'code': 500, 'msg': '订阅群组失败'})
-
- local_ok = _send_group_message(
- appkey=appkey,
- secret=secret,
- group_id=group_id,
- sender_id=sender_id,
- sender_name=sender_name,
- sender_avatar=sender_avatar,
- message_text=text,
- custom_payload=None,
- group_name=group_name,
- order_id=order.dingdan_id,
- is_cross=local_cross_flag
- )
- if not local_ok:
- return Response({'code': 500, 'msg': '消息发送失败'})
-
- # 跨平台转发
- partner_order_id = order.partner_order_id
- partner_club_id = order.partner_club_id
-
- if not partner_order_id or not partner_club_id:
- return Response({'code': 200, 'msg': '发送成功'})
-
- partner_cfg = get_partner_club_config(partner_club_id)
- if not partner_cfg or not partner_cfg.chat_api_key:
- return Response({'code': 200, 'msg': '发送成功(对方未配置)'})
-
- agent_sender_id = f"{order.dingdan_id}_{sender_id}"
- agent_sender_name = f"[我方老板]{shangjia_uid}"
-
- partner_group_id = f"group_{partner_order_id}"
-
- partner_ok = _send_group_message(
- appkey=partner_cfg.chat_api_key,
- secret=partner_cfg.chat_api_id or '',
- group_id=partner_group_id,
- sender_id=agent_sender_id,
- sender_name=agent_sender_name,
- sender_avatar='',
- message_text=text,
- custom_payload=None,
- group_name='跨平台订单交流',
- order_id=partner_order_id,
- is_cross=1
- )
-
- if partner_ok:
- return Response({'code': 200, 'msg': '发送成功(双平台)'})
- else:
- return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'})
-
-
-
-
-
-
-
-
-
-
-
-
-
-'''class DaiLiQunLiaoXiaoXiView(APIView):
- """
- 代理发送群聊消息
- 支持:
- - 非跨平台订单:直接用我方 GoEasy Key 以真实身份发送
- - 跨平台订单(我方派单):同上
- - 跨平台订单(对方派单):除了我方群外,还需用对方 GoEasy Key 以代理身份向对方群发送
- """
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- # ===== 1. 参数解析 =====
- order_id = request.data.get('orderId')
- identity_type = request.data.get('identityType') # 前端必传:dashou / shangjia / boss
- sender_name = request.data.get('senderName', '')
- sender_avatar_raw = request.data.get('senderAvatar', '')
- message_type = request.data.get('messageType', 'text')
- message_text = request.data.get('text', '')
- order_payload = request.data.get('orderPayload', None)
-
- logger.info(f"[群聊代理] 收到请求: orderId={order_id}, identityType={identity_type}, "
- f"messageType={message_type}, text={message_text[:20]}...")
-
- if not order_id or not identity_type:
- return Response({'code': 400, 'msg': '缺少 orderId 或 identityType'})
- if identity_type not in ('dashou', 'shangjia', 'boss'):
- return Response({'code': 400, 'msg': 'identityType 无效'})
-
- # ===== 2. 构造 senderId =====
- uid = request.user.yonghuid
- prefix_map = {'dashou': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'}
- sender_id = prefix_map[identity_type] + uid
- logger.info(f"[群聊代理] 登录用户 yonghuid={uid}, 构造 senderId={sender_id}")
-
- # ===== 3. 查询订单 =====
- try:
- order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=order_id)
- except Dingdan.DoesNotExist:
- logger.warning(f"[群聊代理] 订单 {order_id} 不存在")
- return Response({'code': 404, 'msg': '订单不存在'})
-
- # ===== 4. 权限校验 =====
- valid, err_msg = check_user_permission(identity_type, uid, order)
- if not valid:
- logger.warning(f"[群聊代理] 权限校验失败: {err_msg}, uid={uid}, identity={identity_type}")
- return Response({'code': 403, 'msg': err_msg})
-
- # ===== 5. 我方 GoEasy 配置 =====
- self_appkey = get_self_goeasy_appkey()
- self_secret = get_self_goeasy_secret()
- if not self_appkey:
- logger.error("[群聊代理] 我方 GoEasy AppKey 未配置")
- return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
-
- # ===== 6. 公共群信息 =====
- real_group_id = f"group_{order.dingdan_id}"
- group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}"
- sender_avatar = full_avatar(sender_avatar_raw) if sender_avatar_raw else full_avatar('')
-
- # ===== 7. 订阅发送者到群(确保在群内) =====
- if not subscribe_users_to_group([sender_id], [real_group_id], self_appkey, self_secret):
- logger.error("[群聊代理] 订阅群组失败")
- return Response({'code': 500, 'msg': '订阅群组失败'})
-
- # ===== 8. 判断跨平台状态 =====
- is_cross = order.is_cross
- dispatch_type = order.dispatch_type # 1=我方派单, 2=对方派单
- logger.info(f"[群聊代理] 订单 {order_id} is_cross={is_cross}, dispatch_type={dispatch_type}")
-
- custom_payload = order_payload if message_type == 'order' else None
- text_content = message_text
-
- # ---------- 非跨平台 或 我方派单 ----------
- if is_cross != 1 or dispatch_type == 1:
- logger.info("[群聊代理] 走非跨平台/我方派单发送通道")
- success = send_group_message(
- appkey=self_appkey,
- secret=self_secret,
- group_id=real_group_id,
- sender_id=sender_id,
- sender_name=sender_name,
- sender_avatar=sender_avatar,
- message_text=text_content,
- custom_payload=custom_payload,
- group_name=group_name,
- order_id=order.dingdan_id,
- is_cross=1 if is_cross == 1 else 0
- )
- if success:
- return Response({'code': 200, 'msg': '发送成功'})
- else:
- return Response({'code': 500, 'msg': '消息发送失败'})
-
- # ---------- 跨平台且对方派单 ----------
- logger.info("[群聊代理] 检测到跨平台对方派单,准备双平台发送")
- partner_club_id = order.partner_club_id
- partner_order_id = order.partner_order_id
-
- if not partner_club_id or not partner_order_id:
- logger.warning("[群聊代理] 缺少 partner_club_id 或 partner_order_id,降级为本地发送")
- success = send_group_message(
- self_appkey, self_secret, real_group_id, sender_id, sender_name,
- sender_avatar, text_content, custom_payload, group_name, order.dingdan_id, is_cross=1
- )
- return Response({'code': 200, 'msg': '发送成功(对方信息缺失)'} if success else ('发送失败', 500))
-
- partner_cfg = get_partner_club_config(partner_club_id)
- if not partner_cfg or not partner_cfg.chat_api_key:
- logger.warning("[群聊代理] 对方俱乐部配置不可用,仅发送我方群")
- success = send_group_message(
- self_appkey, self_secret, real_group_id, sender_id, sender_name,
- sender_avatar, text_content, custom_payload, group_name, order.dingdan_id, is_cross=1
- )
- return Response({'code': 200, 'msg': '发送成功(对方GoEasy未配置)'} if success else ('发送失败', 500))
-
- partner_appkey = partner_cfg.chat_api_key
- partner_secret = partner_cfg.chat_api_id or ''
- partner_group_id = f"group_{partner_order_id}"
-
- # 构造代理身份(参考 chat_utils.py)
- agent_sender_id = f"{order.dingdan_id}_{sender_id}"
- if identity_type == 'dashou':
- agent_sender_name = f"[我方]{sender_name}"
- elif identity_type == 'shangjia':
- agent_sender_name = f"[我方商家]{sender_name}"
- else:
- agent_sender_name = f"[我方老板]{sender_name}"
- agent_avatar = full_avatar('') # 可定制
-
- logger.info(f"[群聊代理] 对方群组={partner_group_id}, 代理身份={agent_sender_id}/{agent_sender_name}")
-
- # 1) 在我方群发送真实身份消息
- local_ok = send_group_message(
- self_appkey, self_secret, real_group_id, sender_id, sender_name,
- sender_avatar, text_content, custom_payload, group_name, order.dingdan_id, is_cross=1
- )
- logger.info(f"[群聊代理] 我方群发送结果: {local_ok}")
-
- # 2) 在对方群发送代理身份消息
- partner_ok = send_group_message(
- partner_appkey, partner_secret, partner_group_id,
- agent_sender_id, agent_sender_name, agent_avatar,
- text_content, custom_payload,
- '跨平台订单交流', partner_order_id, is_cross=1
- )
- logger.info(f"[群聊代理] 对方群发送结果: {partner_ok}")
-
- if local_ok and partner_ok:
- return Response({'code': 200, 'msg': '发送成功(双平台)'})
- elif local_ok:
- return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'})
- else:
- return Response({'code': 500, 'msg': '消息发送失败'})'''
-
-
-
-from peizhi.models import ClubConfig
-from utils.chat_utils import establish_order_chat # 【新增】导入聊天工具
-class QiangdanView(APIView):
- authentication_classes = [JWTAuthentication]
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- data = request.data
- dingdan_id = data.get('dingdan_id')
- if not dingdan_id:
- return Response({'code': 400, 'msg': '订单ID不能为空'}, status=400)
-
- try:
- with transaction.atomic():
- # 1. 验证打手身份和状态
- dashou_profile = self._validate_dashou_status(request.user)
- if not dashou_profile:
- return Response({'code': 403, 'msg': '您不是打手或账号状态异常'}, status=403)
-
-
- # ==================== 【新增】罚单检查 ====================
- from utils.fadan_utils import check_fadan_qiangdan_eligible
- fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(request.user.yonghuid,1)
- if not fadan_ok:
- return Response({'code': 400, 'msg': fadan_msg})
- # =========================================================
- # ========== 标签快速校验(提前拒绝,避免锁资源) ==========
- from dengji.utils import check_dashou_biaoqian_required
- biaoqian_ok, biaoqian_msg = check_dashou_biaoqian_required(dingdan_id, request.user.yonghuid)
- if not biaoqian_ok:
- return Response({'code': 400, 'msg': biaoqian_msg})
- # ===========================================================
-
- # 2. 锁定并验证订单(行锁)
- order_result = self._validate_and_lock_order(dingdan_id, request.user.yonghuid)
- if isinstance(order_result, str):
- return Response({'code': 400, 'msg': order_result})
- order = order_result
-
- is_cross = order.is_cross == 1
- dispatch_type = order.dispatch_type
-
- if is_cross and dispatch_type == 2:
- validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
- if not validation_result['success']:
- return Response({'code': 400, 'msg': validation_result['message']})
-
- claim_allowed = self._request_partner_check_claim(order, request.user.yonghuid)
- if claim_allowed is None:
- return Response({'code': 500, 'msg': '跨平台通信失败,请重试'}, status=500)
- elif claim_allowed is False:
- if order.zhuangtai == 1:
- order.zhuangtai = 21
- order.save()
- logger.info(f"订单 {dingdan_id} 被对方拒绝抢单,状态改为21")
- else:
- order.zhuangtai = 20
- order.save()
- logger.info(f"订单 {dingdan_id} 已被其他平台抢走,状态改为20")
- return Response({'code': 400, 'msg': '订单已被其他平台打手抢走或不可抢'})
-
- self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
-
- try:
- update_daily_dispatch_stat(
- direction=2,
- partner_club_id=order.partner_club_id or '000001',
- claimed_amount=order.jine,
- claimed_count=1
- )
- except Exception as e:
- logger.error(f"更新抢单统计失败: {e}", exc_info=True)
-
- elif is_cross and dispatch_type == 1:
- validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
- if not validation_result['success']:
- return Response({'code': 400, 'msg': validation_result['message']})
-
- self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
- self._async_notify_partners_order_claimed(order)
-
- else:
- validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
- if not validation_result['success']:
- return Response({'code': 400, 'msg': validation_result['message']})
- self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
-
- # ========== 【唯一改动】聊天建立方式 ==========
- chat_success = establish_order_chat(dingdan_id)
-
- try:
- update_dashou_daily_by_action(
- yonghuid=order.jiedan_dashou_id,
- amount=order.dashou_fencheng,
- action=1
- )
- logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功")
- except Exception as e:
- logger.error(f"打手每日统计更新失败: {e}")
-
-
- # 在 QiangdanView.post 里,_execute_qiangdan 之后添加:
- from dengji.services import record_dashou_biaoxian
- # 记录抢单表现(不影响主流程)
- # 如果订单表里有 bankuai 信息,可以用 order.bankuai 代替 None
- record_dashou_biaoxian(
- dingdan_id=dingdan_id,
- dashou_id=order.jiedan_dashou_id,
- xingwei=1,
- jiedan_time=timezone.now(), # 直接取当前时间
- bankuai=None # 暂不填,后续提交时补上
- )
-
- return Response({
- 'code': 200,
- 'msg': '抢单成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'chat_established': chat_success
- }
- })
-
- except Exception as e:
- logger.error(f"抢单异常: {str(e)}", exc_info=True)
- return Response({'code': 500, 'msg': '系统异常,请稍后重试'}, status=500)
-
- # ---------- 以下所有方法原封不动照抄旧版 ----------
- def _validate_dashou_status(self, user):
- try:
- dashou_profile = user.dashou_profile
- if (dashou_profile.zhuangtai != 1 or
- dashou_profile.zhanghaozhuangtai != 1 or
- dashou_profile.jifen < 5):
- return None
- return dashou_profile
- except UserDashou.DoesNotExist:
- return None
-
- def _validate_and_lock_order(self, dingdan_id, dashou_yonghuid):
- try:
- order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)
- if order.zhuangtai not in [1, 7]:
- return "订单状态不可抢"
- if order.zhuangtai == 7:
- if not order.zhiding_id or order.zhiding_id != dashou_yonghuid:
- return "此订单为指定单,您不是指定打手"
- if order.jiedan_dashou_id:
- return "订单已被其他打手抢走"
- return order
- except Dingdan.DoesNotExist:
- return "订单不存在"
-
- def _validate_order_requirements(self, order, dashou_profile, dashou_yonghuid):
- if order.yaoqiuleixing == 1:
- return self._validate_huiyuan_requirement(order, dashou_yonghuid)
- elif order.yaoqiuleixing == 2:
- return self._validate_yajin_requirement(order, dashou_profile)
- return {'success': True, 'message': '验证通过'}
-
- def _validate_huiyuan_requirement(self, order, dashou_yonghuid):
- try:
- huiyuan_record = Huiyuangoumai.objects.get(
- huiyuan_id=order.huiyuan_id,
- yonghu_id=dashou_yonghuid
- )
- if huiyuan_record.jiance_shifou_daoqi() or huiyuan_record.huiyuan_zhuangtai != 1:
- return {'success': False, 'message': '会员已过期或未开通对应会员'}
- return {'success': True, 'message': '会员验证通过'}
- except Huiyuangoumai.DoesNotExist:
- return {'success': False, 'message': '未开通对应会员,无法抢此订单'}
-
- def _validate_yajin_requirement(self, order, dashou_profile):
- yajin_required = order.yongjin or 0
- dashou_yajin = dashou_profile.yajin or 0
- if dashou_yajin < yajin_required:
- return {'success': False, 'message': f'押金不足,需要{yajin_required}元,当前有{dashou_yajin}元'}
- return {'success': True, 'message': '押金验证通过'}
-
- def _execute_qiangdan(self, order, dashou_profile, dashou_yonghuid):
- order.zhuangtai = 2
- order.jiedan_dashou_id = dashou_yonghuid
- order.user2_id = f"Ds{dashou_yonghuid}"
- order.save()
-
- dashou_profile.zhuangtai = 0
- dashou_profile.jiedanzongliang = F('jiedanzongliang') + 1
- dashou_profile.jinrijiedan = F('jinrijiedan') + 1
- dashou_profile.jinyuejiedan = F('jinyuejiedan') + 1
- dashou_profile.save()
-
- # ---------- 跨平台询问对方(照抄) ----------
- def _request_partner_check_claim(self, order, dashou_id):
- partner_club_id = order.partner_club_id or '000001'
- try:
- club = Club.objects.filter(partner_club_id=partner_club_id).first()
- if not club or not club.partner_domain:
- logger.error(f"未找到对方俱乐部配置: partner_club_id={partner_club_id}")
- return None
-
- my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first()
- if not my_club:
- logger.error(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}")
- return None
-
- payload = {
- 'token': settings.CROSS_PLATFORM_TOKEN,
- 'wofangdingdan_id': order.partner_order_id,
- 'partner_order_id': order.dingdan_id,
- 'our_club_id': my_club.club_id,
- 'partner_club_id': club.partner_club_id,
- 'dashou_id': dashou_id,
- 'sj_id': f"Sj{settings.AGENT_SHANGJIA_ID}"
- }
-
- url = club.partner_domain.rstrip('/') + '/dingdan/partner_check_claim'
- resp = requests.post(url, json=payload, timeout=5)
-
- if resp.status_code != 200:
- logger.error(f"跨平台抢单询问HTTP失败: {resp.status_code}")
- return None
-
- data = resp.json()
- if data.get('code') == 0:
- return data.get('data', {}).get('can_claim') is True
- else:
- logger.error(f"跨平台抢单询问业务失败: {data}")
- return False
- except Exception as e:
- logger.error(f"跨平台抢单询问异常: {e}", exc_info=True)
- return None
-
- # ---------- 异步通知其他俱乐部 ----------
- def _async_notify_partners_order_claimed(self, order):
- def notify_task():
- clubs = Club.objects.filter(is_interop_enabled=True).select_related()
- for club in clubs:
- for attempt in range(1, 4):
- success = self._send_claim_notification_to_partner(order, club)
- if success:
- break
- if attempt < 3:
- time.sleep(2 ** attempt)
- else:
- logger.error(f"通知俱乐部 {club.club_id} 订单已抢失败,重试3次后仍失败")
-
- thread = threading.Thread(target=notify_task)
- thread.daemon = True
- thread.start()
-
- def _send_claim_notification_to_partner(self, order, club):
- try:
- my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first()
- if not my_club:
- logger.warning(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}")
- return False
-
- payload = {
- 'token': settings.CROSS_PLATFORM_TOKEN,
- 'our_club_id': my_club.club_id,
- 'partner_club_id': club.partner_club_id,
- 'partner_order_id': order.dingdan_id,
- 'claimed': True
- }
- url = club.partner_domain.rstrip('/') + '/dingdan/partner_notify_claim'
- resp = requests.post(url, json=payload, timeout=3)
- return resp.status_code == 200
- except Exception as e:
- logger.error(f"通知俱乐部 {club.club_id} 异常: {e}")
- return False
-
-
-
-
-
-
-
-class DashouDingdanHuoquView1(APIView):
- """
- 打手订单获取接口
- 支持筛选条件:
- - zhuangtai_list: 订单状态列表(可选,空数组或不传表示全部状态)
- - leixing_id: 商品类型ID(可选)
- - keyword: 搜索关键词,模糊匹配订单ID和订单介绍(可选)
- """
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 1. 获取当前登录用户的 yonghuid
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
- except AttributeError:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- }, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 获取并校验前端参数
- try:
- data = request.data
- zhuangtai_list = data.get('zhuangtai_list', []) # 可选,默认空列表(全部)
- page = max(1, int(data.get('page', 1)))
- page_size = min(100, max(1, int(data.get('page_size', 10))))
- leixing_id = data.get('leixing_id', None) # 可选,商品类型ID
- keyword = data.get('keyword', '').strip() # 可选,搜索关键词
- except (ValueError, TypeError):
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 参数默认值与类型修正
- if not isinstance(zhuangtai_list, list):
- zhuangtai_list = [] # 非列表则视为全部
- if leixing_id is not None:
- try:
- leixing_id = int(leixing_id)
- except (ValueError, TypeError):
- leixing_id = None
-
- # 4. 构建查询条件
- try:
- # 基础条件:当前打手接单
- query = Q(jiedan_dashou_id=yonghuid)
-
- # 状态筛选:如果 zhuangtai_list 不为空列表,则加入筛选;否则查询全部状态
- if zhuangtai_list:
- query &= Q(zhuangtai__in=zhuangtai_list)
-
- # 商品类型筛选
- if leixing_id is not None:
- query &= Q(leixing_id=leixing_id)
-
- # 关键词搜索:模糊匹配订单ID和订单介绍
- if keyword:
- keyword_query = Q(dingdan_id__icontains=keyword) | Q(jieshao__icontains=keyword)
- query &= keyword_query
-
- # 5. 执行查询,按创建时间倒序
- dingdan_query = Dingdan.objects.filter(query).order_by('-create_time')
-
- # 只选择需要的字段,提高性能
- dingdan_query = dingdan_query.only(
- 'dingdan_id',
- 'zhuangtai',
- 'dashou_fencheng', # 打手分成金额
- 'jieshao',
- 'tupian',
- 'nicheng',
- 'create_time'
- )
-
- total_count = dingdan_query.count()
-
- # 6. 分页处理
- start_index = (page - 1) * page_size
- end_index = start_index + page_size
- current_page_orders = dingdan_query[start_index:end_index]
- current_page_count = len(current_page_orders)
-
- # 7. 构建返回数据列表
- order_list = []
- for order in current_page_orders:
- order_data = {
- 'dingdan_id': order.dingdan_id,
- 'zhuangtai': order.zhuangtai,
- 'jine': float(order.dashou_fencheng) if order.dashou_fencheng else 0.0,
- 'jieshao': order.jieshao or '',
- 'tupian': order.tupian or '',
- 'nicheng': order.nicheng or '',
- 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else ''
- }
- order_list.append(order_data)
-
- # 8. 判断是否还有更多数据
- has_more = (current_page_count == page_size) and (end_index < total_count)
- if total_count == 0:
- has_more = False
-
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': {
- 'list': order_list,
- 'has_more': has_more,
- 'total': total_count
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"打手订单获取失败: {str(e)}", exc_info=True)
-
- return Response({
- 'code': 4,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-'''class DashouDingdanHuoquView1(APIView):
- """打手订单获取接口"""
- permission_classes = [IsAuthenticated] # JWT认证
-
- def post(self, request):
- # 1. 获取当前登录用户的yonghuid
- try:
- current_user = request.user
- yonghuid = current_user.yonghuid
-
- except AttributeError:
- return Response({
- 'code': 1,
- 'msg': '用户认证失败',
- 'data': None
- }, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 获取和验证前端参数
- try:
- data = request.data
- zhuangtai_list = data.get('zhuangtai_list', [])
- page = max(1, int(data.get('page', 1)))
- page_size = min(100, max(1, int(data.get('page_size', 10))))
-
-
-
- except (ValueError, TypeError):
- return Response({
- 'code': 2,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 验证状态列表参数
- if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
- return Response({
- 'code': 3,
- 'msg': '状态列表不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- # 4. 构建查询条件 - 直接查询订单主表
- # 条件1: 当前打手是接单打手
- # 条件2: 订单状态在指定列表中
-
- # 注意:打手看不到状态1和7的订单(已下单、指定中)
- # 但这里由前端控制传递的zhuangtai_list决定
- dashou_query = Q(jiedan_dashou_id=yonghuid)
- zhuangtai_query = Q(zhuangtai__in=zhuangtai_list)
-
- # 5. 执行查询 - 只查询需要的字段,按创建时间倒序
- dingdan_query = Dingdan.objects.filter(
- dashou_query & zhuangtai_query
- ).order_by('-create_time')
-
- # 只选择需要的字段,提高性能
- dingdan_query = dingdan_query.only(
- 'dingdan_id',
- 'zhuangtai',
- 'dashou_fencheng', # ✅ 关键:打手分成,不是jine
- 'jieshao',
- 'tupian',
- 'nicheng',
- 'create_time'
- )
-
- total_count = dingdan_query.count()
-
-
- # 6. 计算分页
- start_index = (page - 1) * page_size
- end_index = start_index + page_size
-
- # 获取当前页的订单数据
- current_page_orders = dingdan_query[start_index:end_index]
- current_page_count = len(current_page_orders)
-
- # 7. 构建返回数据 - 只返回前端需要的字段
- order_list = []
- for order in current_page_orders:
- order_data = {
- # 核心字段 - 必须与前端完全对应
- 'dingdan_id': order.dingdan_id, # 订单ID
- 'zhuangtai': order.zhuangtai, # 订单状态码
- # ✅ 关键:这里返回的是打手分成,不是订单金额
- 'jine': float(order.dashou_fencheng) if order.dashou_fencheng else 0.0,
- 'jieshao': order.jieshao or '', # 订单介绍
- 'tupian': order.tupian or '', # 商品图片URL
- 'nicheng': order.nicheng or '', # 游戏昵称
- 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else ''
- }
- order_list.append(order_data)
-
-
-
- # 8. 判断是否还有更多数据
- # 条件1: 当前页获取了满page_size条数据
- # 条件2: 还有更多数据没取完
- has_more = current_page_count == page_size and end_index < total_count
-
- # 如果总数为0,直接没有更多
- if total_count == 0:
- has_more = False
-
-
-
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': {
- 'list': order_list, # 订单列表
- 'has_more': has_more, # 是否还有更多数据
- 'total': total_count # 总订单数(可选)
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- # 记录异常日志
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"打手订单获取失败: {str(e)}", exc_info=True)
-
-
-
- return Response({
- 'code': 4,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
-
-
-
-
-'''# views.py - 打手COS凭证接口(完整替换)
-import json
-import time
-from tencentcloud.common import credential
-from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
-from tencentcloud.sts.v20180813 import sts_client, models
-from django.conf import settings
-from rest_framework.views import APIView
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.response import Response
-from rest_framework import status
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-class DashouCOSZhengshuView(APIView):
- """打手获取COS上传凭证接口(使用腾讯云STS临时密钥)"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- try:
-
-
- # 1. 接收参数
- dingdan_id = request.data.get('dingdan_id', '').strip()
- if not dingdan_id:
- return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST)
-
- # 2. 业务验证逻辑(保持你的原有逻辑)
- try:
- from .models import Dingdan
- dingdan = Dingdan.objects.get(
- dingdan_id=dingdan_id,
- jiedan_dashou_id=request.user.yonghuid,
- zhuangtai=2
- )
-
- except Dingdan.DoesNotExist:
- return Response({'code': 5, 'msg': '订单无权限或状态不对', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
- except Exception as e:
-
- return Response({'code': 500, 'msg': '服务器验证异常', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 3. 获取腾讯云STS临时密钥
- try:
- # 初始化固定凭证
- cred = credential.Credential(
- settings.COS_SECRET_ID,
- settings.COS_SECRET_KEY
- )
-
- # 创建STS客户端
- http_profile = HttpProfile()
- http_profile.endpoint = "sts.tencentcloudapi.com"
- client_profile = ClientProfile()
- client_profile.httpProfile = http_profile
-
- client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
-
- # 构建策略(允许上传到订单目录)
- policy = {
- "version": "2.0",
- "statement": [
- {
- "effect": "allow",
- "action": [
- "cos:PutObject",
- "cos:PostObject",
- "cos:InitiateMultipartUpload",
- "cos:UploadPart",
- "cos:CompleteMultipartUpload"
- ],
- "resource": [
- f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
- ]
- }
- ]
- }
-
- # 请求STS临时凭证
- req = models.GetFederationTokenRequest()
- req.Name = f"dashou_{dingdan_id}_{int(time.time())}"
- req.Policy = json.dumps(policy)
- req.DurationSeconds = 7200 # 2小时有效期
-
- resp = client.GetFederationToken(req)
-
- # 提取临时密钥
- credentials = resp.Credentials
-
-
- except TencentCloudSDKException as e:
- logger.error(f"获取STS临时密钥失败: {str(e)}")
- return Response({
- 'code': 500,
- 'msg': f'获取上传凭证失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 4. 构建响应数据(严格遵循小程序COS SDK要求)
- current_time = int(time.time())
- response_data = {
- 'credentials': {
- 'tmpSecretId': credentials.TmpSecretId, # 临时SecretId
- 'tmpSecretKey': credentials.TmpSecretKey, # 临时SecretKey
- 'sessionToken': credentials.Token, # 必须的Token
- },
- 'startTime': current_time,
- 'expiredTime': current_time + 7200,
- 'bucket': settings.COS_BUCKET,
- 'region': settings.COS_REGION,
- 'upload_dir': f'order/{dingdan_id}/'
- }
-
-
-
- # 5. 返回成功
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': response_data
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f'COS凭证接口未知错误: {str(e)}', exc_info=True)
- return Response({
- 'code': 500,
- 'msg': f'服务器内部错误: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
-
-#
-# views.py - 打手COS凭证接口(完整替换)
-import json
-import time
-from tencentcloud.common import credential
-from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
-from tencentcloud.sts.v20180813 import sts_client, models
-from django.conf import settings
-from rest_framework.views import APIView
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.response import Response
-from rest_framework import status
-import logging
-
-logger = logging.getLogger(__name__)
-
-class DashouCOSZhengshuView(APIView):
- """打手获取COS上传凭证接口(调试版本)"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 接收参数(修复 int 无 strip 问题)
- dingdan_id = str(request.data.get('dingdan_id', '')).strip()
- yongtu = request.data.get('yongtu', 'dingdan')
-
- # 2. 条件判断修正:除了 fakuan 之外,其他用途均需提供 dingdan_id
- if yongtu != 'fakuan' and not dingdan_id:
- return Response(
- {'code': 400, 'msg': '订单ID不能为空', 'error': 'dingdan_id is required when yongtu is not fakuan'},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 3. 业务验证逻辑(根据用途分别验证)
- if yongtu == 'chufa':
- try:
- from .models import Chufajilu
- chufa_record = Chufajilu.objects.filter(
- dingdan_id=dingdan_id,
- dashouid=request.user.yonghuid,
- sqzhuangtai__in=[0, 3]
- ).first()
- if not chufa_record:
- return Response(
- {'code': 403, 'msg': '处罚记录无权限或状态不对',
- 'error': f'No punishment record found for user {request.user.yonghuid} on order {dingdan_id}'},
- status=status.HTTP_403_FORBIDDEN
- )
- except Exception as e:
- logger.error(f"查处罚记录异常: {str(e)}")
- return Response(
- {'code': 500, 'msg': '服务器内部错误', 'error': str(e)},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
- elif yongtu == 'fakuan':
- # 罚款申诉不需要订单验证
- pass
- else:
- # 默认为订单相关,需要验证订单且打手必须接单
- try:
- from .models import Dingdan
- dingdan = Dingdan.objects.get(
- dingdan_id=dingdan_id,
- jiedan_dashou_id=request.user.yonghuid,
- zhuangtai__in=[2, 8]
- )
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found or not assigned to user {request.user.yonghuid}'},
- status=status.HTTP_403_FORBIDDEN
- )
-
- # 4. 获取腾讯云STS临时密钥
- try:
- # 初始化固定凭证
- cred = credential.Credential(
- settings.COS_SECRET_ID,
- settings.COS_SECRET_KEY
- )
-
- # 创建STS客户端
- http_profile = HttpProfile()
- http_profile.endpoint = "sts.tencentcloudapi.com"
- client_profile = ClientProfile()
- client_profile.httpProfile = http_profile
- client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
-
- # 构建策略资源
- resources = []
- if yongtu == 'chufa':
- resources.append(
- f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/dssstp/*"
- )
- elif yongtu == 'fakuan':
- resources.append(
- f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/dashoufakuan/dashoufakuanshensu/*"
- )
- else:
- resources.append(
- f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
- )
-
- policy = {
- "version": "2.0",
- "statement": [{
- "effect": "allow",
- "action": [
- "cos:PutObject",
- "cos:PostObject",
- "cos:InitiateMultipartUpload",
- "cos:UploadPart",
- "cos:CompleteMultipartUpload"
- ],
- "resource": resources
- }]
- }
-
- req = models.GetFederationTokenRequest()
- req.Name = f"dashou_{dingdan_id}_{int(time.time())}"
- req.Policy = json.dumps(policy)
- req.DurationSeconds = 7200
-
- resp = client.GetFederationToken(req)
- credentials = resp.Credentials
-
- except TencentCloudSDKException as e:
- logger.error(f"获取STS临时密钥失败: {str(e)}")
- return Response({
- 'code': 500,
- 'msg': f'获取上传凭证失败: {str(e)}',
- 'error_detail': str(e)
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 5. 构建响应
- current_time = int(time.time())
- upload_dir = 'fakuan/dashoufakuan/dashoufakuanshensu/' if yongtu == 'fakuan' else (
- 'a_long/chfajltp/dssstp/' if yongtu == 'chufa' else f'order/{dingdan_id}/'
- )
- response_data = {
- 'credentials': {
- 'tmpSecretId': credentials.TmpSecretId,
- 'tmpSecretKey': credentials.TmpSecretKey,
- 'sessionToken': credentials.Token,
- },
- 'startTime': current_time,
- 'expiredTime': current_time + 7200,
- 'bucket': settings.COS_BUCKET,
- 'region': settings.COS_REGION,
- 'upload_dir': upload_dir,
- }
- return Response({'code': 0, 'msg': '成功', 'data': response_data})
-
- except Exception as e:
- # 捕捉所有未预期的异常,返回完整 traceback
- logger.error(f'COS凭证接口未知错误: {traceback.format_exc()}')
- return Response({
- 'code': 500,
- 'msg': f'服务器内部错误: {str(e)}',
- 'error_detail': traceback.format_exc()
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-'''class DashouCOSZhengshuView(APIView):
- """打手获取COS上传凭证接口(使用腾讯云STS临时密钥)"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 接收参数
- dingdan_id = str(request.data.get('dingdan_id', '')).strip()
- yongtu = request.data.get('yongtu', 'dingdan') # 🔴【新增】用途参数:dingdan=订单,chufa=处罚
-
- if not dingdan_id and yongtu is not 'fakuan':
- return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST)
-
-
-
- # 2. 业务验证逻辑
- try:
- from .models import Dingdan
- # 如果是处罚相关上传,验证处罚权限
- if yongtu == 'chufa':
- # 验证处罚记录是否存在且属于当前打手
- from .models import Chufajilu
- chufa_record = Chufajilu.objects.filter(
- dingdan_id=dingdan_id,
- dashouid=request.user.yonghuid,
- sqzhuangtai__in=[0, 3] # 待处罚或申诉中状态
- ).first()
-
- if not chufa_record:
- return Response({'code': 5, 'msg': '处罚记录无权限或状态不对', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
- elif yongtu == 'fakuan':
- pass
- else:
- # 原有订单验证逻辑
- dingdan = Dingdan.objects.get(
- dingdan_id=dingdan_id,
- jiedan_dashou_id=request.user.yonghuid,
- zhuangtai__in=[2,8]
- #zhuangtai=2
- )
-
- except Dingdan.DoesNotExist:
- return Response({'code': 5, 'msg': '此功能暂未开放', 'data': None})
- except Exception as e:
- logger.error(f"订单验证异常: {str(e)}")
- return Response({'code': 500, 'msg': '服务器验证异常', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 3. 获取腾讯云STS临时密钥
- try:
- # 初始化固定凭证
- cred = credential.Credential(
- settings.COS_SECRET_ID,
- settings.COS_SECRET_KEY
- )
-
- # 创建STS客户端
- http_profile = HttpProfile()
- http_profile.endpoint = "sts.tencentcloudapi.com"
- client_profile = ClientProfile()
- client_profile.httpProfile = http_profile
-
- client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
-
- # 🔴【修复】构建策略(允许上传到多个路径)
- resources = []
-
- if yongtu == 'chufa':
- # 处罚申诉图片路径
- resources.append(
- f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/dssstp/*"
- )
-
- elif yongtu == 'fakuan':
- # 🔥 罚款申诉图片专用路径
- resources.append(f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/dashoufakuan/dashoufakuanshensu/*")
- else:
- # 订单相关图片路径
- resources.append(
- f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
- )
-
- policy = {
- "version": "2.0",
- "statement": [
- {
- "effect": "allow",
- "action": [
- "cos:PutObject",
- "cos:PostObject",
- "cos:InitiateMultipartUpload",
- "cos:UploadPart",
- "cos:CompleteMultipartUpload"
- ],
- "resource": resources
- }
- ]
- }
-
- # 请求STS临时凭证
- req = models.GetFederationTokenRequest()
- req.Name = f"dashou_{dingdan_id}_{int(time.time())}"
- req.Policy = json.dumps(policy)
- req.DurationSeconds = 7200 # 2小时有效期
-
- resp = client.GetFederationToken(req)
-
- # 提取临时密钥
- credentials = resp.Credentials
-
- except TencentCloudSDKException as e:
- logger.error(f"获取STS临时密钥失败: {str(e)}")
- return Response({
- 'code': 500,
- 'msg': f'获取上传凭证失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 4. 构建响应数据
- current_time = int(time.time())
- response_data = {
- 'credentials': {
- 'tmpSecretId': credentials.TmpSecretId,
- 'tmpSecretKey': credentials.TmpSecretKey,
- 'sessionToken': credentials.Token,
- },
- 'startTime': current_time,
- 'expiredTime': current_time + 7200,
- 'bucket': settings.COS_BUCKET,
- 'region': settings.COS_REGION,
- 'upload_dir': 'fakuan/dashoufakuan/dashoufakuanshensu/' if yongtu == 'fakuan' else (
- 'a_long/chfajltp/dssstp/' if yongtu == 'chufa' else f'order/{dingdan_id}/')
- }
-
- # 5. 返回成功
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': response_data
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f'COS凭证接口未知错误: {str(e)}', exc_info=True)
- return Response({
- 'code': 500,
- 'msg': f'服务器内部错误: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
-
-
-class ShangjiaCOSZhengshuView(APIView):
- """商家获取COS上传凭证接口(用于处罚申诉图片上传)"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 接收参数
- dingdan_id = request.data.get('dingdan_id', '').strip()
- yongtu = request.data.get('yongtu', 'chufa') # 用途:chufa=处罚申诉
-
- if not dingdan_id:
- return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- # 2. 🔴【修改】验证商家身份 - 通过反向查询shop_profile判断是否为商家
- try:
- # 检查是否有商家扩展表
- shangjia_profile = request.user.shop_profile
-
- # 验证商家状态是否为1(正常)
- if shangjia_profile.zhuangtai != 1:
- return Response({'code': 3, 'msg': '商家账号状态异常,无法操作', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
-
- # 🔴【重要】获取商家ID(从商家扩展表中获取)
- shangjia_id = request.user.yonghuid # 假设商家扩展表中有yonghuid字段
-
- except AttributeError:
- # 没有shop_profile属性,说明不是商家
- return Response({'code': 3, 'msg': '非商家用户无权限', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
-
- # 3. 🔴【修改】验证订单权限 - 通过商家订单扩展表验证
- try:
- from .models import Dingdan, DingdanShangjia
-
- # 获取订单
- dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
-
- # 🔴【重要】通过反向查询商家扩展表验证订单是否属于当前商家
- try:
- shangjia_kuozhan = dingdan.shangjia_kuozhan # 使用反向关系名
-
- # 验证商家ID是否匹配
- if str(shangjia_kuozhan.shangjia_id) != str(shangjia_id):
- return Response({'code': 2, 'msg': '订单不属于当前商家', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
-
- # 验证订单状态是否允许申请处罚(根据前端逻辑)
- allowed_zhuangtai = [2, 4, 5, 6, 8]
- if dingdan.zhuangtai not in allowed_zhuangtai:
- return Response({'code': 4, 'msg': '当前订单状态不允许申请处罚', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- # 🔴【新增】检查是否已存在处罚申请(通过Chufajilu模型)
- # 注意:这里假设Chufajilu模型存在,并且有shangjia_id字段
- try:
- from .models import Chufajilu
- existing_chufa = Chufajilu.objects.filter(
- dingdan_id=dingdan_id,
- qingqiuid=shangjia_id,
- sqzhuangtai__in=[0, 1] # 审核中或已处罚状态
- ).exists()
-
- if existing_chufa:
- return Response({'code': 5, 'msg': '该订单已有处罚申请,请勿重复上传', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
- except ImportError:
- # 如果没有Chufajilu模型,跳过这个检查
- pass
-
- except DingdanShangjia.DoesNotExist:
- # 没有商家扩展记录,说明这不是商家订单
- return Response({'code': 2, 'msg': '非商家订单无法操作', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
-
- except Dingdan.DoesNotExist:
- return Response({'code': 2, 'msg': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND)
- except Exception as e:
- logger.error(f"订单验证异常: {str(e)}")
- return Response({'code': 500, 'msg': '服务器验证异常', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 4. 获取腾讯云STS临时密钥
- try:
- # 初始化固定凭证
- cred = credential.Credential(
- settings.COS_SECRET_ID,
- settings.COS_SECRET_KEY
- )
-
- # 创建STS客户端
- http_profile = HttpProfile()
- http_profile.endpoint = "sts.tencentcloudapi.com"
- client_profile = ClientProfile()
- client_profile.httpProfile = http_profile
-
- client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
-
- # 🔴【修改】构建策略 - 只允许上传到商家处罚证据图片目录
- resources = [
- f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/sjzjtp/*"
- ]
-
- policy = {
- "version": "2.0",
- "statement": [
- {
- "effect": "allow",
- "action": [
- "cos:PutObject",
- "cos:PostObject",
- "cos:InitiateMultipartUpload",
- "cos:UploadPart",
- "cos:CompleteMultipartUpload"
- ],
- "resource": resources
- }
- ]
- }
-
- # 请求STS临时凭证
- req = models.GetFederationTokenRequest()
- # 🔴【修改】命名包含商家ID
- req.Name = f"shangjia_{shangjia_id}_{dingdan_id}_{int(time.time())}"
- req.Policy = json.dumps(policy)
- req.DurationSeconds = 7200 # 2小时有效期
-
- resp = client.GetFederationToken(req)
-
- # 提取临时密钥
- credentials = resp.Credentials
-
- except TencentCloudSDKException as e:
- logger.error(f"商家获取STS临时密钥失败: {str(e)}")
- return Response({
- 'code': 500,
- 'msg': f'获取上传凭证失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 5. 构建响应数据
- current_time = int(time.time())
- response_data = {
- 'credentials': {
- 'tmpSecretId': credentials.TmpSecretId,
- 'tmpSecretKey': credentials.TmpSecretKey,
- 'sessionToken': credentials.Token,
- },
- 'startTime': current_time,
- 'expiredTime': current_time + 7200,
- 'bucket': settings.COS_BUCKET,
- 'region': settings.COS_REGION,
- # 🔴【修改】商家专用上传目录
- 'upload_dir': 'a_long/chfajltp/sjzjtp/',
- # 🔴【新增】返回商家ID和订单ID,便于前端生成文件名
- 'dingdan_id': dingdan_id,
- 'shangjia_id': shangjia_id,
- # 🔴【新增】可选:返回商家昵称,便于前端显示
- 'shangjia_nicheng': shangjia_profile.nicheng if hasattr(shangjia_profile, 'nicheng') else ''
- }
-
- # 6. 返回成功
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': response_data
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f'商家COS凭证接口未知错误: {str(e)}', exc_info=True)
- return Response({
- 'code': 500,
- 'msg': f'服务器内部错误: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-class DashouTijiaoView(APIView):
- """打手提交订单完成接口"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 获取参数
- data = request.data
- dingdan_id = data.get('dingdan_id', '')
- liuyan = data.get('liuyan', '')
- jiaofu_tupian_urls = data.get('jiaofu_tupian_urls', [])
-
- # 2. 参数验证
- if not dingdan_id:
- return Response({
- 'code': 1,
- 'msg': '订单ID不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- if not isinstance(jiaofu_tupian_urls, list):
- return Response({
- 'code': 2,
- 'msg': '图片URL列表格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 限制图片数量(最多9张,最少1张)
- if len(jiaofu_tupian_urls) == 0:
- return Response({
- 'code': 3,
- 'msg': '至少需要上传一张图片',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- if len(jiaofu_tupian_urls) > 9:
- return Response({
- 'code': 4,
- 'msg': '最多只能上传9张图片',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 留言长度限制(50字)
- if liuyan and len(liuyan) > 50:
- return Response({
- 'code': 5,
- 'msg': '留言最多50个字',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 获取当前用户并验证打手身份
- current_user = request.user
- yonghuid = current_user.yonghuid
-
- try:
- dashou_profile = current_user.dashou_profile
- except UserDashou.DoesNotExist:
- return Response({
- 'code': 6,
- 'msg': '用户不是打手身份',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 4. 验证订单状态和权限
- try:
- dingdan = Dingdan.objects.get(
- dingdan_id=dingdan_id,
- jiedan_dashou_id=yonghuid,
- zhuangtai=2 # 必须是进行中的订单
- )
- except Dingdan.DoesNotExist:
- return Response({
- 'code': 7,
- 'msg': '订单不存在或没有提交权限',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 5. 存储图片到Dashoutupian表
- for tupian_url in jiaofu_tupian_urls:
- # 创建打手提交图片记录
- Dashoutupian.objects.create(
- dingdan_id=dingdan_id,
- dashou=yonghuid,
- tupian=tupian_url,
- create_time=timezone.now()
- )
-
- # 6. 更新订单主表
- dingdan.dashou_liuyan = liuyan
- dingdan.zhuangtai = 8 # 状态改为结算中
- dingdan.update_time = timezone.now()
- dingdan.save()
-
- # 7. 更新打手扩展表状态
- dashou_profile.zhuangtai = 1 # 改为数字1(根据你之前说的)
- dashou_profile.save()
-
- from dengji.services import record_dashou_biaoxian
- # ========== 新增:记录打手提交时间 ==========
- record_dashou_biaoxian(
- dingdan_id=dingdan_id,
- dashou_id=yonghuid,
- xingwei=2, # 提交
- tijiao_time=timezone.now()
- )
- # ===========================================
-
- # 8. 返回成功响应
- return Response({
- 'code': 0,
- 'msg': '订单提交成功',
- 'data': {
- 'dingdan_id': dingdan_id,
- 'new_zhuangtai': 8, # 新的订单状态
- 'dashou_zhuangtai': 1 # 打手新的状态
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"打手提交订单失败: {str(e)}", exc_info=True)
-
- return Response({
- 'code': 8,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-
-
-
-
-class DashouDingdanXiangqingView(APIView):
- """打手订单详情获取接口"""
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- try:
- # 1. 获取订单ID
- dingdan_id = request.data.get('dingdan_id', '')
- if not dingdan_id:
- return Response({
- 'code': 1,
- 'msg': '订单ID不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 2. 获取当前用户并验证权限
- current_user = request.user
- yonghuid = current_user.yonghuid
-
- try:
- dashou_profile = current_user.dashou_profile
- except UserDashou.DoesNotExist:
- return Response({
- 'code': 2,
- 'msg': '用户不是打手身份',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 3. 查询订单主表
- try:
- dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response({
- 'code': 3,
- 'msg': '订单不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
-
- # 4. 验证订单是否属于当前打手
- if dingdan.jiedan_dashou_id != yonghuid:
- return Response({
- 'code': 4,
- 'msg': '没有权限查看此订单',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)
-
- # 5. 构建基础订单数据
- dingdan_data = {
- 'dingdan_id': dingdan.dingdan_id,
- 'zhuangtai': dingdan.zhuangtai,
- 'jine': float(dingdan.dashou_fencheng) if dingdan.dashou_fencheng else 0.0,
- 'jieshao': dingdan.jieshao or '',
- 'tupian': dingdan.tupian or '',
- 'nicheng': dingdan.nicheng or '',
- 'beizhu': dingdan.beizhu or '',
- 'create_time': dingdan.create_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan.create_time else '',
- 'tuikuan_liyou':dingdan.tkly or '',
- 'dashou_liuyan': dingdan.dashou_liuyan or '',
- 'fuwudashou_id': dingdan.jiedan_dashou_id or '',
- 'fadanpingtai': dingdan.fadan_pingtai or ''
- }
-
- # 6. 查询打手提交的图片
- dashou_tupian_list = Dashoutupian.objects.filter(
- dingdan_id=dingdan_id,
- dashou=yonghuid
- ).order_by('create_time')
-
- jiaofu_tupian_urls = []
- for tupian_record in dashou_tupian_list:
- if tupian_record.tupian:
- jiaofu_tupian_urls.append(tupian_record.tupian)
-
- dingdan_data['dashou_jiaofu'] = jiaofu_tupian_urls
-
- # 7. 查询商家扩展表信息
- shangjia_info = {}
- try:
- shangjia_kuozhan = dingdan.shangjia_kuozhan
- shangjia_info = {
- 'shangjia_id': shangjia_kuozhan.shangjia_id or '',
- 'shangjia_nicheng': shangjia_kuozhan.sjnicheng or '',
- 'shangjia_liuyan': shangjia_kuozhan.shangjia_pingjia or '',
- #'tuikuan_liyou': '',
- 'chufa_liyou': shangjia_kuozhan.cfliyou or '',
- 'chufa_zhuangtai': shangjia_kuozhan.sqzhuangtai or 0,
- #'chufa_jieguo': shangjia_kuozhan.chufa_jieguo or '',
- 'bohui_liyou': shangjia_kuozhan.bhliyou or ''
- }
- except DingdanShangjia.DoesNotExist:
- # 没有商家扩展信息,返回空值
- shangjia_info = {
- 'shangjia_id': '',
- 'shangjia_nicheng': '',
- 'shangjia_liuyan': '',
- 'tuikuan_liyou': '',
- 'chufa_liyou': '',
- 'chufa_zhuangtai': 0,
- 'chufa_jieguo': '',
- 'bohui_liyou': ''
- }
-
- # 8. 查询平台扩展表信息(如果有的话)
- pingtai_info = {}
- try:
- pingtai_kuozhan = dingdan.pingtai_kuozhan
- pingtai_info = {
- 'laoban_id': pingtai_kuozhan.laoban_id or '',
- 'laoban_pingjia': pingtai_kuozhan.laoban_pingjia or ''
- }
- except DingdanPingtai.DoesNotExist:
- pingtai_info = {
- 'laoban_id': '',
- 'laoban_pingjia': ''
- }
-
- # 9. 合并所有数据
- response_data = {
- **dingdan_data,
- **shangjia_info,
- **pingtai_info
- }
-
- return Response({
- 'code': 0,
- 'msg': '成功',
- 'data': response_data
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- import logging
- logger = logging.getLogger(__name__)
- logger.error(f"获取打手订单详情失败: {str(e)}", exc_info=True)
-
- return Response({
- 'code': 5,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-# dingdan/views.py
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.parsers import JSONParser
-import logging
-
-
-logger = logging.getLogger(__name__)
-
-
-class AdGetDingdanXiangQing(APIView):
- """
- 后台订单详情获取接口
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /dingdan/adddxq
- 参数: {"zhanghao": "管理员手机号", "dingdan_id": "订单ID"}
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- """
- 获取订单详情数据
- 返回: 订单主表数据 + 扩展表数据 + 打手图片列表
- """
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
-
- if not qianzhanghao or not dingdan_id:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证 (IsAuthenticated已处理)
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 查询订单主表
- try:
- from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia, Dashoutupian
-
- # 查询订单主表 - 使用select_related预加载关联表
- dingdan_obj = Dingdan.objects.select_related(
- 'pingtai_kuozhan', 'shangjia_kuozhan'
- ).get(dingdan_id=dingdan_id)
-
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 7. 构建返回数据 - 主表字段
- response_data = {
- # 基础信息字段
- 'dingdan_id': dingdan_obj.dingdan_id or '',
- 'jieshao': dingdan_obj.jieshao or '',
- 'zhuangtai': dingdan_obj.zhuangtai or 0,
- 'leixing': dingdan_obj.leixing_id or 0, # 前端需要 leixing,对应 leixing_id
- 'fadanpingtai': dingdan_obj.fadan_pingtai or 0, # 前端需要 fadanpingtai,对应 fadan_pingtai
- 'jiage': float(dingdan_obj.jine) if dingdan_obj.jine else 0.00, # 前端需要 jiage,对应 jine
- 'tupian_url': dingdan_obj.tupian or '', # 前端需要 tupian_url,对应 tupian
- 'beizhu': dingdan_obj.beizhu or '',
-
- # 服务信息字段
- 'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '',
- 'dashou_fencheng': float(dingdan_obj.dashou_fencheng) if dingdan_obj.dashou_fencheng else 0.00,
- 'zhiding_id': dingdan_obj.zhiding_id or '',
- 'dashou_liuyan': dingdan_obj.dashou_liuyan or '',
-
- # 时间字段
- 'chuangjianshijian': dingdan_obj.create_time.strftime(
- '%Y-%m-%d %H:%M:%S') if dingdan_obj.create_time else '',
- 'genggaishijian': dingdan_obj.update_time.strftime(
- '%Y-%m-%d %H:%M:%S') if dingdan_obj.update_time else '',
- }
-
- # 8. 根据发单平台查询扩展表数据
- fadan_pingtai = dingdan_obj.fadan_pingtai
-
- if fadan_pingtai == 1:
- # 平台发单 - 查询平台订单扩展表
- try:
- # 使用反向关联 pingtai_kuozhan
- pingtai_kuozhan = dingdan_obj.pingtai_kuozhan
-
- response_data.update({
- 'laoban_id': pingtai_kuozhan.laoban_id or '',
- 'nicheng': dingdan_obj.nicheng or '', # 这个字段在订单主表
- 'laoban_pingjia': pingtai_kuozhan.laoban_pingjia or '',
- })
- except DingdanPingtai.DoesNotExist:
- # 扩展表不存在,返回空值
- response_data.update({
- 'laoban_id': '',
- 'nicheng': dingdan_obj.nicheng or '',
- 'laoban_pingjia': '',
- })
-
- elif fadan_pingtai == 2:
- # 商家发单 - 查询商家订单扩展表
- try:
- # 使用反向关联 shangjia_kuozhan
- shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
-
- response_data.update({
- 'shangjia_id': shangjia_kuozhan.shangjia_id or '',
- 'sjnicheng': shangjia_kuozhan.sjnicheng or '',
- 'shangjia_pingjia': shangjia_kuozhan.shangjia_pingjia or '',
- 'cfliyou': shangjia_kuozhan.cfliyou or '', # 商家处罚理由
- 'bhliyou': shangjia_kuozhan.bhliyou or '', # 驳回理由
- })
- except DingdanShangjia.DoesNotExist:
- # 扩展表不存在,返回空值
- response_data.update({
- 'shangjia_id': '',
- 'sjnicheng': '',
- 'shangjia_pingjia': '',
- 'cfliyou': '',
- 'bhliyou': '',
- })
-
- # 9. 查询打手提交的图片
- try:
- # 查询该订单的所有打手提交图片
- dashou_tupian_list = Dashoutupian.objects.filter(
- dingdan_id=dingdan_id
- ).values_list('tupian', flat=True)
-
- # 转换为列表,过滤空值
- dashou_images = [img for img in dashou_tupian_list if img]
- response_data['dashou_images'] = dashou_images
- except Exception as e:
- logger.warning(f"查询打手图片失败: {str(e)}")
- response_data['dashou_images'] = []
-
- # 🔥 新增:查询退款记录表中的商家退款理由
- try:
- # 导入退款记录模型
- from dingdan.models import Tuikuanjilu
-
- # 查询该订单的退款记录(一个订单可能有多次退款申请,我们取最新的)
- tuikuan_record = Tuikuanjilu.objects.filter(
- dingdan_id=dingdan_id
- ).order_by('-create_time').first() # 按创建时间倒序,取最新的
-
- if tuikuan_record and tuikuan_record.liyou:
- # 如果有退款记录且理由不为空,添加到返回数据
- response_data['tuikuan_liyou'] = tuikuan_record.liyou
- else:
- # 没有找到退款记录或理由为空
- response_data['tuikuan_liyou'] = ''
- except Tuikuanjilu.DoesNotExist:
- # 退款记录表不存在该订单的记录
- response_data['tuikuan_liyou'] = ''
- except Exception as e:
- # 查询过程中出现其他异常
- logger.warning(f"查询退款记录失败: {str(e)}")
- response_data['tuikuan_liyou'] = ''
-
- # 10. 返回成功响应
- return Response({
- 'code': 0,
- 'message': '获取成功',
- 'data': response_data
- })
-
- except Exception as e:
- logger.error(f"获取订单详情异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-
-
-# 获取日志器
-logger = logging.getLogger(__name__)
-
-
-class AdGengHuanDaShou(APIView):
- """
- 后台更换打手接口
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /dingdan/adddgh
- 参数: {
- "zhanghao": "管理员账号",
- "dingdan_id": "订单ID",
- "new_dashou_id": "新打手ID"
- }
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- """
- 更换订单的打手
- 核心逻辑:
- 1. 验证管理员身份
- 2. 验证订单存在且状态为进行中(2)或结算中(8)
- 3. 验证新打手ID存在且是打手身份
- 4. 验证新打手状态正常(zhuangtai=1且zhanghaozhuangtai=1)
- 5. 根据订单的抢单要求类型进行验证:
- - 会员抢单(1):验证打手有对应会员且未过期
- - 押金抢单(2):验证打手押金≥订单押金
- 6. 更新新打手状态和统计数据
- 7. 更新原打手状态和统计数据
- 8. 更新订单信息
- """
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
- new_dashou_id = request.data.get('new_dashou_id', '').strip()
-
- if not qianzhanghao or not dingdan_id or not new_dashou_id:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 查询订单主表
- try:
- from dingdan.models import Dingdan
- dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 7. 验证订单状态是否为进行中(2)或结算中(8)
- if dingdan_obj.zhuangtai not in [2, 8]:
- return Response(
- {'code': 400, 'message': '订单状态不允许更换打手', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 8. 验证新打手ID是否存在,并且是打手身份
- try:
- new_dashou_user = UserMain.objects.get(
- yonghuid=new_dashou_id,
- #user_type='dashou'
- )
- except UserMain.DoesNotExist:
- return Response(
- {'code': 404, 'message': '打手不存在或不是打手身份', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 9. 获取新打手扩展表
- try:
- new_dashou_profile = new_dashou_user.dashou_profile
- except AttributeError:
- return Response(
- {'code': 404, 'message': '打手信息不完整', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 10. 验证新打手状态:zhuangtai=1且zhanghaozhuangtai=1
- if new_dashou_profile.zhuangtai != 1 or new_dashou_profile.zhanghaozhuangtai != 1:
- return Response(
- {'code': 400, 'message': '打手状态不允许接单', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 11. 根据订单的抢单要求类型进行验证
- yaoqiuleixing = dingdan_obj.yaoqiuleixing
-
- if yaoqiuleixing == 1: # 会员抢单
- # 获取订单的会员ID
- huiyuan_id = dingdan_obj.huiyuan_id
- if not huiyuan_id:
- return Response(
- {'code': 400, 'message': '订单会员ID为空', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 查询会员购买记录
- try:
- from shangpin.models import Huiyuangoumai
- huiyuan_goumai = Huiyuangoumai.objects.get(
- huiyuan_id=huiyuan_id,
- yonghu_id=new_dashou_id # 注意:yonghu_id存储的是用户ID(yonghuid)
- )
- except Huiyuangoumai.DoesNotExist:
- return Response(
- {'code': 400, 'message': '打手未购买该订单要求的会员', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 检查会员是否过期
- # ✅ 可以调用!因为jiance_shifou_daoqi()是Huiyuangoumai模型的实例方法
- is_expired = huiyuan_goumai.jiance_shifou_daoqi()
- if is_expired or huiyuan_goumai.huiyuan_zhuangtai != 1:
- return Response(
- {'code': 400, 'message': '打手的会员已过期', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- elif yaoqiuleixing == 2: # 押金抢单
- # 获取订单的押金(yongjin字段)
- order_yajin = dingdan_obj.yongjin
- if order_yajin is None:
- return Response(
- {'code': 400, 'message': '订单押金为空', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 验证打手押金是否大于等于订单押金
- if new_dashou_profile.yajin < order_yajin:
- return Response(
- {'code': 400, 'message': '打手押金不足', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
- else:
- # 未知的抢单要求类型
- return Response(
- {'code': 400, 'message': '订单抢单要求类型错误', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 12. 所有验证通过,开始更换打手(使用事务保证一致性)
- with transaction.atomic():
- # 12.1 更新新打手扩展表
- new_dashou_profile.zhuangtai = 0 # 接单中
- new_dashou_profile.jiedanzongliang += 1
- new_dashou_profile.jinrijiedan += 1
- new_dashou_profile.jinyuejiedan += 1
- new_dashou_profile.save()
-
- # 12.2 如果原订单有接单打手,则更新原打手状态
- old_dashou_id = dingdan_obj.jiedan_dashou_id
- if old_dashou_id:
- try:
- # 查询原打手主表
- old_dashou_user = UserMain.objects.get(
- yonghuid=old_dashou_id,
- #user_type='dashou'
- )
-
- # 获取原打手扩展表
- old_dashou_profile = old_dashou_user.dashou_profile
-
- # 更新原打手状态
- old_dashou_profile.zhuangtai = 1 # 空闲
- old_dashou_profile.tuikuanliang += 1 # 退款订单总量+1
- old_dashou_profile.save()
-
- logger.info(f"成功更新原打手{old_dashou_id}状态")
- except UserMain.DoesNotExist:
- # 原打手不存在,继续执行不影响更换
- logger.warning(f"原打手{old_dashou_id}不存在,跳过更新")
- except AttributeError:
- # 原打手扩展表不存在,继续执行不影响更换
- logger.warning(f"原打手{old_dashou_id}扩展信息不存在,跳过更新")
- except Exception as e:
- # 其他异常,记录日志但继续执行
- logger.error(f"更新原打手状态异常: {str(e)}", exc_info=True)
-
- # 12.3 更新订单表
- dingdan_obj.jiedan_dashou_id = new_dashou_id
- dingdan_obj.zhuangtai = 2 # 进行中状态
- dingdan_obj.save()
-
- # 13. 返回成功响应
- return Response({
- 'code': 0,
- 'message': '更换打手成功',
- 'data': None
- })
-
- except Exception as e:
- logger.error(f"更换打手接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-import logging
-
-# 获取日志器
-logger = logging.getLogger(__name__)
-
-
-class AdQiangZhiJieDan(APIView):
- """
- 后台强制结单接口
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /dingdan/adddqzjd
- 参数: {
- "zhanghao": "管理员账号",
- "dingdan_id": "订单ID"
- }
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- """
- 强制结单接口
- 核心逻辑:
- 1. 验证管理员身份
- 2. 验证订单状态为2(进行中)、8(结算中)或4(退款审核中)才能强制结单
- 3. 根据发单平台(1=平台发单, 2=商家发单)分别处理
- 4. 更新订单状态为3(已完成)
- 5. 给打手结算分成金额
- 6. 如果是商家发单,还需要更新商家成交订单数量
- """
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
-
- if not qianzhanghao or not dingdan_id:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 查询订单主表
- try:
- from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia
- dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 7. 验证订单状态是否为2、8或4
- if dingdan_obj.zhuangtai not in [2, 8, 4]:
- return Response(
- {'code': 400, 'message': '订单状态不允许强制结单', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 8. 获取订单的重要字段
- jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
- dashou_fencheng = dingdan_obj.dashou_fencheng # 打手分成金额
- fadan_pingtai = dingdan_obj.fadan_pingtai # 发单平台
-
- # 9. 验证打手分成金额是否存在
- if dashou_fencheng is None:
- return Response(
- {'code': 400, 'message': '订单缺少打手分成金额', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 10. 使用事务保证数据一致性
- with transaction.atomic():
- # 10.1 更新订单状态为3(已完成)
- dingdan_obj.zhuangtai = 3
- dingdan_obj.save()
-
- # 10.2 处理打手结算
- if jiedan_dashou_id:
- try:
- # 查询打手用户主表
- dashou_user = UserMain.objects.get(
- yonghuid=jiedan_dashou_id,
- #user_type='dashou'
- )
-
- # 获取打手扩展表
- dashou_profile = dashou_user.dashou_profile
-
- # 更新打手扩展表数据
- # ✅ 确认字段:chengjiaozongliang 成交单总量
- dashou_profile.chengjiaozongliang += 1
-
- # ✅ 确认字段:yue 可提现余额
- dashou_profile.yue += dashou_fencheng
-
- # ✅ 确认字段:zonge 打单赚取总额
- dashou_profile.zonge += dashou_fencheng
-
- # ✅ 确认字段:jinrishouyi 今日收益金额
- dashou_profile.jinrishouyi += dashou_fencheng
-
- # ✅ 确认字段:jinyueshouyi 今月收益金额
- dashou_profile.jinyueshouyi += dashou_fencheng
-
- dashou_profile.save()
- logger.info(f"强制结单成功,打手{jiedan_dashou_id}结算分成:{dashou_fencheng}")
-
- except UserMain.DoesNotExist:
- # 打手用户不存在,记录日志但不影响强制结单
- logger.warning(f"强制结单:打手{jiedan_dashou_id}不存在,跳过打手结算")
- except AttributeError:
- # 打手扩展表不存在,记录日志但不影响强制结单
- logger.warning(f"强制结单:打手{jiedan_dashou_id}扩展信息不存在,跳过打手结算")
-
- # 10.3 如果是商家发单,还需要更新商家扩展表
- if fadan_pingtai == 2:
- try:
- # 获取商家订单扩展表
- shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
- shangjia_id = shangjia_kuozhan.shangjia_id
-
- if shangjia_id:
- # 查询商家用户主表
- shangjia_user = UserMain.objects.get(
- yonghuid=shangjia_id,
- #user_type='shop'
- )
-
- # 获取商家扩展表
- shangjia_profile = shangjia_user.shop_profile
-
- # ✅ 确认字段:chengjiao 商家成交订单总量
- shangjia_profile.chengjiao += 1
- shangjia_profile.save()
-
- logger.info(f"强制结单成功,商家{shangjia_id}成交订单+1")
-
- except DingdanShangjia.DoesNotExist:
- # 商家订单扩展表不存在,跳过商家更新
- logger.warning(f"强制结单:订单{dingdan_id}商家扩展表不存在,跳过商家更新")
- except UserMain.DoesNotExist:
- # 商家用户不存在,跳过商家更新
- logger.warning(f"强制结单:商家{shangjia_id}不存在,跳过商家更新")
- except AttributeError:
- # 商家扩展表不存在,跳过商家更新
- logger.warning(f"强制结单:商家{shangjia_id}扩展信息不存在,跳过商家更新")
-
- # 12. 更新平台收益流水(仅在平台发单时)
- if dingdan_obj.fadan_pingtai == 1:
- try:
- from dingdan.utils import update_platform_profit # 导入函数
- update_platform_profit(dingdan_id)
- except Exception as e:
- # 平台收益更新失败不影响主流程,但记录日志
- logger.error(f"强制结单后更新平台收益失败: {str(e)}")
-
- # 11. 返回成功响应
- return Response({
- 'code': 0,
- 'message': '强制结单成功',
- 'data': None
- })
-
- except Exception as e:
- logger.error(f"强制结单接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-class AdJuJueTuiKuan(APIView):
- """
- 后台拒绝退款接口
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /dingdan/adddjj
- 参数: {
- "zhanghao": "管理员账号",
- "dingdan_id": "订单ID",
- "jujue_liyou": "拒绝退款理由(可选)"
- }
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- """
- 拒绝退款接口
- 核心逻辑:
- 1. 验证管理员身份
- 2. 拒绝退款后订单状态改为6(退款失败)
- 3. 给打手结算分成金额
- 4. 如果是商家发单,更新商家成交订单数量
- 5. 更新退款记录表,状态改为2(驳回)
- """
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
- jujue_liyou = request.data.get('jujue_liyou', '').strip()
-
- if not qianzhanghao or not dingdan_id:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 查询订单主表
- try:
- from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
- dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 7. 获取订单的重要字段
- jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
- dashou_fencheng = dingdan_obj.dashou_fencheng # 打手分成金额
- fadan_pingtai = dingdan_obj.fadan_pingtai # 发单平台
-
- # 8. 验证打手分成金额是否存在
- if dashou_fencheng is None:
- return Response(
- {'code': 400, 'message': '订单缺少打手分成金额', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 9. 使用事务保证数据一致性
- with transaction.atomic():
- # 9.1 更新订单状态为6(退款失败)
- dingdan_obj.zhuangtai = 6
- dingdan_obj.save()
-
- # 9.2 处理打手结算
- if jiedan_dashou_id:
- try:
- # 查询打手用户主表
- dashou_user = UserMain.objects.get(
- yonghuid=jiedan_dashou_id,
- #user_type='dashou'
- )
-
- # 获取打手扩展表
- dashou_profile = dashou_user.dashou_profile
-
- # 更新打手扩展表数据
- # ✅ 确认字段:chengjiaozongliang 成交单总量
- dashou_profile.chengjiaozongliang += 1
-
- # ✅ 确认字段:yue 可提现余额
- dashou_profile.yue += dashou_fencheng
-
- # ✅ 确认字段:zonge 打单赚取总额
- dashou_profile.zonge += dashou_fencheng
-
- # ✅ 确认字段:jinrishouyi 今日收益金额
- dashou_profile.jinrishouyi += dashou_fencheng
-
- # ✅ 确认字段:jinyueshouyi 今月收益金额
- dashou_profile.jinyueshouyi += dashou_fencheng
-
- dashou_profile.save()
- logger.info(f"拒绝退款成功,打手{jiedan_dashou_id}结算分成:{dashou_fencheng}")
-
- except UserMain.DoesNotExist:
- # 打手用户不存在,记录日志但不影响操作
- logger.warning(f"拒绝退款:打手{jiedan_dashou_id}不存在,跳过打手结算")
- except AttributeError:
- # 打手扩展表不存在,记录日志但不影响操作
- logger.warning(f"拒绝退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手结算")
-
- # 9.3 如果是商家发单,还需要更新商家扩展表
- if fadan_pingtai == 2:
- try:
- # 获取商家订单扩展表
- shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
- shangjia_id = shangjia_kuozhan.shangjia_id
-
- if shangjia_id:
- # 查询商家用户主表
- shangjia_user = UserMain.objects.get(
- yonghuid=shangjia_id,
- #user_type='shop'
- )
-
- # 获取商家扩展表
- shangjia_profile = shangjia_user.shop_profile
-
- # ✅ 确认字段:chengjiao 商家成交订单总量
- shangjia_profile.chengjiao += 1
- shangjia_profile.save()
-
- logger.info(f"拒绝退款成功,商家{shangjia_id}成交订单+1")
-
- except DingdanShangjia.DoesNotExist:
- # 商家订单扩展表不存在,跳过商家更新
- logger.warning(f"拒绝退款:订单{dingdan_id}商家扩展表不存在,跳过商家更新")
- except UserMain.DoesNotExist:
- # 商家用户不存在,跳过商家更新
- logger.warning(f"拒绝退款:商家{shangjia_id}不存在,跳过商家更新")
- except AttributeError:
- # 商家扩展表不存在,跳过商家更新
- logger.warning(f"拒绝退款:商家{shangjia_id}扩展信息不存在,跳过商家更新")
-
- # 9.4 更新退款记录表
- try:
- # 查询退款记录表
- tuikuan_jilu = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
-
- # ✅ 确认字段:sqzhuangtai 申请状态,改为2(驳回)
- tuikuan_jilu.sqzhuangtai = 2
-
- # ✅ 确认字段:bhliyou 驳回理由
- tuikuan_jilu.bhliyou = jujue_liyou
-
- # ✅ 确认字段:chuliid 处理人ID(使用管理员账号,即手机号)
- tuikuan_jilu.chuliid = qianzhanghao
-
- tuikuan_jilu.save()
- logger.info(f"拒绝退款成功,更新退款记录表,订单:{dingdan_id}")
-
- except Tuikuanjilu.DoesNotExist:
- # 退款记录表不存在,记录日志但不影响主流程
- logger.warning(f"拒绝退款:订单{dingdan_id}退款记录不存在,跳过更新")
-
- # 10. 返回成功响应
- return Response({
- 'code': 0,
- 'message': '拒绝退款成功',
- 'data': None
- })
-
- except Exception as e:
- logger.error(f"拒绝退款接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-class AdTongYiTuiKuanShangJia(APIView):
- """
- 后台同意退款接口(商家发单)
- 权限: JWT Token认证 + 管理员身份验证 + 密码验证
- 前端调用: POST /dingdan/adddsjtk
- 参数: {
- "zhanghao": "管理员账号",
- "dingdan_id": "订单ID",
- "tuikuan_liyou": "退款理由(可选)",
- "pay_password": "支付密码(openid)"
- }
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- """
- 同意退款接口(商家发单)
- 核心逻辑:
- 1. 验证管理员身份
- 2. 验证支付密码(openid)
- 3. 检查订单状态为4(退款审核中)
- 4. 更新订单状态为5(已退款)
- 5. 更新打手退款订单数量
- 6. 更新商家退款订单数量和余额(加上订单金额)
- 7. 更新退款记录表,状态改为1(成功)
- """
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
- tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
- pay_password = request.data.get('pay_password', '').strip()
-
- if not qianzhanghao or not dingdan_id or not pay_password:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 验证支付密码(对比openid)
- if pay_password != dangqianyonghu.openid:
- return Response(
- {'code': 401, 'message': '支付密码错误', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 7. 查询订单主表
- try:
- from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
- dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 8. 验证订单状态为4(退款审核中)
- if dingdan_obj.zhuangtai != 4:
- return Response(
- {'code': 400, 'message': '订单状态不是退款审核中', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 9. 验证订单为商家发单
- if dingdan_obj.fadan_pingtai != 2:
- return Response(
- {'code': 400, 'message': '此接口只处理商家发单的退款', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 10. 获取订单的重要字段
- jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
- jine = dingdan_obj.jine # 订单金额(不是打手分成)
-
- # 11. 验证订单金额是否存在
- if jine is None:
- return Response(
- {'code': 400, 'message': '订单缺少订单金额', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 12. 获取商家ID
- try:
- shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
- shangjia_id = shangjia_kuozhan.shangjia_id
- except DingdanShangjia.DoesNotExist:
- return Response(
- {'code': 400, 'message': '订单商家信息不存在', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 13. 使用事务保证数据一致性
- with transaction.atomic():
- # 13.1 更新订单状态为5(已退款)
- dingdan_obj.zhuangtai = 5
- dingdan_obj.save()
-
- # 13.2 更新打手扩展表(退款订单总量+1)
- if jiedan_dashou_id:
- try:
- # 查询打手用户主表
- dashou_user = UserMain.objects.get(
- yonghuid=jiedan_dashou_id,
- #user_type='dashou'
- )
-
- # 获取打手扩展表
- dashou_profile = dashou_user.dashou_profile
-
- # ✅ 确认字段:tuikuanliang 退款订单总量
- dashou_profile.tuikuanliang += 1
-
- dashou_profile.save()
- logger.info(f"同意退款成功,打手{jiedan_dashou_id}退款订单+1")
-
- except UserMain.DoesNotExist:
- # 打手用户不存在,记录日志但不影响操作
- logger.warning(f"同意退款:打手{jiedan_dashou_id}不存在,跳过打手更新")
- except AttributeError:
- # 打手扩展表不存在,记录日志但不影响操作
- logger.warning(f"同意退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手更新")
-
- # 13.3 更新商家扩展表
- if shangjia_id:
- try:
- # 查询商家用户主表
- shangjia_user = UserMain.objects.get(
- yonghuid=shangjia_id,
- #user_type='shop'
- )
-
- # 获取商家扩展表
- shangjia_profile = shangjia_user.shop_profile
-
- # ✅ 确认字段:tuikuan 退款订单总量
- shangjia_profile.tuikuan += 1
-
- # ✅ 确认字段:yue 商家余额(加上订单金额)
- shangjia_profile.yue += jine
-
- shangjia_profile.save()
- logger.info(f"同意退款成功,商家{shangjia_id}退款订单+1,余额增加:{jine}")
-
- except UserMain.DoesNotExist:
- # 商家用户不存在,记录日志但不影响操作
- logger.warning(f"同意退款:商家{shangjia_id}不存在,跳过商家更新")
- except AttributeError:
- # 商家扩展表不存在,记录日志但不影响操作
- logger.warning(f"同意退款:商家{shangjia_id}扩展信息不存在,跳过商家更新")
-
- # 13.4 更新退款记录表
- try:
- # 查询退款记录表
- tuikuan_jilu = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
-
- # ✅ 确认字段:sqzhuangtai 申请状态,改为1(成功)
- tuikuan_jilu.sqzhuangtai = 1
-
- # 可以添加同意理由字段,如:tongyi_liyou
- # tuikuan_jilu.tongyi_liyou = tuikuan_liyou
-
- # ✅ 确认字段:chuliid 处理人ID(使用管理员账号,即手机号)
- tuikuan_jilu.chuliid = qianzhanghao
-
- tuikuan_jilu.save()
- logger.info(f"同意退款成功,更新退款记录表,订单:{dingdan_id}")
-
- except Tuikuanjilu.DoesNotExist:
- # 退款记录表不存在,记录日志但不影响主流程
- logger.warning(f"同意退款:订单{dingdan_id}退款记录不存在,跳过更新")
-
- # 14. 返回成功响应
- return Response({
- 'code': 0,
- 'message': '同意退款成功',
- 'data': None
- })
-
- except Exception as e:
- logger.error(f"同意退款接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-
-
-
-
-
-
-
-# dingdan/views.py
-import logging
-import time
-import random
-import hashlib
-import requests
-import xml.etree.ElementTree as ET
-from decimal import Decimal
-from django.db import transaction
-from django.conf import settings
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.parsers import JSONParser
-
-from yonghu.models import UserMain, UserDashou, UserBoss
-from dingdan.models import Dingdan, DingdanPingtai, Tuikuanjilu
-from peizhi.models import Szjilu
-
-logger = logging.getLogger(__name__)
-
-
-class AdTongYiTuiKuanPingTai(APIView):
- """
- 后台同意退款接口(平台发单)
- 权限: JWT Token认证 + 管理员身份验证 + 密码验证
- 前端调用: POST /dingdan/adddpttk
- 参数: {
- "zhanghao": "管理员账号",
- "dingdan_id": "订单ID",
- "tuikuan_liyou": "退款理由(可选)",
- "pay_password": "支付密码(openid)"
- }
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
- tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
- pay_password = request.data.get('pay_password', '').strip()
-
- if not qianzhanghao or not dingdan_id or not pay_password:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if str(getattr(dangqianyonghu, 'phone', '')) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 验证支付密码(对比openid)
- if pay_password != dangqianyonghu.openid:
- return Response(
- {'code': 401, 'message': '支付密码错误', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 7. 查询订单主表(加锁)
- with transaction.atomic():
- try:
- dingdan_obj = Dingdan.objects.select_related('pingtai_kuozhan').select_for_update().get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 8. 验证订单状态为1、7、2、8、4(允许退款的状态)
- if dingdan_obj.zhuangtai not in [1, 7, 2, 8, 4]:
- return Response(
- {'code': 400, 'message': '订单状态不允许退款', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 9. 验证订单为平台发单
- if dingdan_obj.fadan_pingtai != 1:
- return Response(
- {'code': 400, 'message': '此接口只处理平台发单的退款', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 10. 获取订单的重要字段
- jiedan_dashou_id = dingdan_obj.jiedan_dashou_id
- jine = dingdan_obj.jine
- if jine is None:
- return Response(
- {'code': 400, 'message': '订单缺少订单金额', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 11. 获取老板ID(从平台订单扩展表)
- if not hasattr(dingdan_obj, 'pingtai_kuozhan') or not dingdan_obj.pingtai_kuozhan:
- return Response(
- {'code': 400, 'message': '订单老板信息不存在', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
- laoban_id = dingdan_obj.pingtai_kuozhan.laoban_id
-
- # 12. 调用微信退款接口(优先使用 wechat_transaction_id)
- transaction_id = getattr(dingdan_obj, 'wechat_transaction_id', None)
- weixin_refund_result = self.call_weixin_refund(
- out_trade_no=dingdan_id,
- jine=jine,
- transaction_id=transaction_id
- )
-
- if not weixin_refund_result.get('success'):
- logger.error(f"微信退款失败,订单:{dingdan_id},错误:{weixin_refund_result.get('msg')}")
- return Response(
- {'code': 400, 'message': weixin_refund_result.get('msg', '退款失败')},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 13. 更新订单状态为5(已退款)
- dingdan_obj.zhuangtai = 5
- dingdan_obj.save()
-
- # 14. 更新打手扩展表
- if jiedan_dashou_id:
- try:
- dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
- dashou_profile = dashou_user.dashou_profile
- dashou_profile.zhuangtai = 1 # 打手状态设为空闲
- dashou_profile.tuikuanliang += 1
- dashou_profile.save()
- except (UserMain.DoesNotExist, AttributeError):
- logger.warning(f"平台退款:打手{jiedan_dashou_id}不存在或扩展信息缺失,跳过")
-
- # 15. 更新老板扩展表
- if laoban_id:
- try:
- laoban_user = UserMain.objects.get(yonghuid=laoban_id)
- laoban_profile = laoban_user.boss_profile
- laoban_profile.alltui += 1
- laoban_profile.zonge -= jine
- if laoban_profile.zonge < 0:
- laoban_profile.zonge = 0
- laoban_profile.save()
- except (UserMain.DoesNotExist, AttributeError):
- logger.warning(f"平台退款:老板{laoban_id}不存在或扩展信息缺失,跳过")
-
- # 16. 更新收支记录表(id=1)
- try:
-
- #更新支出记录
- from dingdan.utils import update_daily_payout
- update_daily_payout(jine)
-
-
- szjilu_obj = Szjilu.objects.get(id=1)
- szjilu_obj.zongsy -= jine
- szjilu_obj.zongzc += jine
- szjilu_obj.jrzc += jine
- szjilu_obj.save()
- except Szjilu.DoesNotExist:
- logger.error(f"平台退款:收支记录表(id=1)不存在,跳过")
- except Exception as e:
- logger.error(f"更新收支记录表异常: {str(e)}")
-
- # 17. 更新退款记录表
- try:
- tuikuan_jilu, created = Tuikuanjilu.objects.get_or_create(
- dingdan_id=dingdan_id,
- defaults={
- 'dashouid': jiedan_dashou_id if jiedan_dashou_id else '',
- 'qingqiuid': laoban_id if laoban_id else '',
- 'chuliid': qianzhanghao,
- 'liyou': tuikuan_liyou if tuikuan_liyou else '给老板退款',
- 'sqzhuangtai': 1,
- 'jine': jine,
- 'dashou_fencheng': dingdan_obj.dashou_fencheng or 0,
- 'jieshao': dingdan_obj.jieshao or '',
- 'beizhu': '给老板退款',
- 'nicheng': dingdan_obj.nicheng or '',
- }
- )
- if not created:
- tuikuan_jilu.sqzhuangtai = 1
- if tuikuan_liyou:
- tuikuan_jilu.liyou = tuikuan_liyou
- tuikuan_jilu.chuliid = qianzhanghao
- tuikuan_jilu.save()
- except Exception as e:
- logger.error(f"更新退款记录表异常: {str(e)}")
-
- logger.info(f"平台退款成功,订单:{dingdan_id},退款单号:{weixin_refund_result.get('refund_id')}")
-
- return Response({'code': 0, 'message': '退款处理成功', 'data': None})
-
- except Exception as e:
- logger.error(f"平台退款接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
- def call_weixin_refund(self, out_trade_no, jine, transaction_id=None):
- """
- 调用微信退款接口
- :param out_trade_no: 商户订单号
- :param jine: 退款金额
- :param transaction_id: 微信支付订单号(优先使用)
- :return: {'success': True/False, 'msg': '消息', 'refund_id': '退款单号'}
- """
- try:
- # 检查配置
- required_settings = ['WEIXIN_APPID', 'WEIXIN_MCHID', 'WEIXIN_SHANGHUMIYAO', 'WEIXIN_CERT_PATH', 'WEIXIN_KEY_PATH']
- for setting in required_settings:
- if not hasattr(settings, setting) or not getattr(settings, setting):
- return {'success': False, 'msg': f'缺少微信支付配置: {setting}'}
-
- # 生成退款单号
- refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}"
- total_fee_in_fen = int(float(jine) * 100)
-
- # 构造请求参数
- refund_data = {
- 'appid': settings.WEIXIN_APPID,
- 'mch_id': settings.WEIXIN_MCHID,
- 'nonce_str': hashlib.md5(str(time.time()).encode()).hexdigest(),
- 'out_refund_no': refund_id,
- 'total_fee': total_fee_in_fen,
- 'refund_fee': total_fee_in_fen,
- 'op_user_id': settings.WEIXIN_MCHID,
- 'refund_account': 'REFUND_SOURCE_UNSETTLED_FUNDS'
- }
- # 优先使用 transaction_id,否则用 out_trade_no
- if transaction_id:
- refund_data['transaction_id'] = transaction_id
- else:
- refund_data['out_trade_no'] = out_trade_no
-
- # 生成签名
- sorted_params = sorted(refund_data.items())
- sign_string = '&'.join([f"{k}={v}" for k, v in sorted_params if v])
- sign_string += f"&key={settings.WEIXIN_SHANGHUMIYAO}"
- sign = hashlib.md5(sign_string.encode()).hexdigest().upper()
- refund_data['sign'] = sign
-
- xml_data = self.dict_to_xml(refund_data)
- url = "https://api.mch.weixin.qq.com/secapi/pay/refund"
- response = requests.post(
- url,
- data=xml_data.encode('utf-8'),
- headers={'Content-Type': 'application/xml'},
- cert=(settings.WEIXIN_CERT_PATH, settings.WEIXIN_KEY_PATH),
- timeout=30
- )
-
- if response.status_code == 200:
- result = self.xml_to_dict(response.content)
- if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
- logger.info(f"微信退款成功,订单:{out_trade_no},退款单号:{refund_id}")
- return {'success': True, 'msg': '退款申请提交成功', 'refund_id': refund_id}
- else:
- error_msg = result.get('err_code_des', result.get('return_msg', '退款申请失败'))
- logger.error(f"微信退款失败,订单:{out_trade_no},错误:{error_msg}")
- return {'success': False, 'msg': f'微信退款失败: {error_msg}'}
- else:
- logger.error(f"微信退款接口调用失败,状态码:{response.status_code}")
- return {'success': False, 'msg': '微信退款接口调用失败'}
-
- except requests.exceptions.Timeout:
- logger.error(f"微信退款接口请求超时,订单:{out_trade_no}")
- return {'success': False, 'msg': '微信退款接口请求超时'}
- except Exception as e:
- logger.error(f"调用微信退款接口异常: {str(e)}")
- return {'success': False, 'msg': f'退款处理异常: {str(e)}'}
-
- def dict_to_xml(self, dict_data):
- xml = [""]
- for k, v in dict_data.items():
- xml.append(f"<{k}>{v}{k}>")
- xml.append("")
- return "".join(xml)
-
- def xml_to_dict(self, xml_data):
- try:
- root = ET.fromstring(xml_data)
- return {child.tag: child.text for child in root}
- except ET.ParseError as e:
- logger.error(f"XML解析失败: {str(e)}")
- return {'return_code': 'FAIL', 'return_msg': 'XML解析失败'}
-
-
-
-
-'''from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.parsers import JSONParser
-from rest_framework import status
-from django.db import transaction
-from django.conf import settings
-import logging
-import time
-import random
-import hashlib
-import requests
-import xml.etree.ElementTree as ET
-
-# 获取日志器
-logger = logging.getLogger(__name__)
-
-
-
-class AdTongYiTuiKuanPingTai(APIView):
- """
- 后台同意退款接口(平台发单)
- 权限: JWT Token认证 + 管理员身份验证 + 密码验证
- 前端调用: POST /dingdan/adddpttk
- 参数: {
- "zhanghao": "管理员账号",
- "dingdan_id": "订单ID",
- "tuikuan_liyou": "退款理由(可选)",
- "pay_password": "支付密码(openid)"
- }
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- """
- 同意退款接口(平台发单)
- 核心逻辑:
- 1. 验证管理员身份和支付密码
- 2. 验证订单状态为2、8或4(进行中、结算中、退款审核中)
- 3. 调用微信退款接口
- 4. 如果微信受理成功,更新数据库(订单、打手、老板、收支、退款记录)
- 5. 如果微信受理失败,返回错误,数据库不变
- """
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
- tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
- pay_password = request.data.get('pay_password', '').strip()
-
- if not qianzhanghao or not dingdan_id or not pay_password:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 验证支付密码(对比openid)
- if pay_password != dangqianyonghu.openid:
- return Response(
- {'code': 401, 'message': '支付密码错误', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 7. 查询订单主表
- try:
- from dingdan.models import Dingdan, DingdanPingtai, Tuikuanjilu
- from peizhi.models import Szjilu
-
- # 使用select_related预加载平台订单扩展表
- dingdan_obj = Dingdan.objects.select_related('pingtai_kuozhan').get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 8. 验证订单状态为2、8或4
- if dingdan_obj.zhuangtai not in [1,7,2, 8, 4]:
- return Response(
- {'code': 400, 'message': '订单状态不允许退款', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 9. 验证订单为平台发单
- if dingdan_obj.fadan_pingtai != 1:
- return Response(
- {'code': 400, 'message': '此接口只处理平台发单的退款', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 10. 获取订单的重要字段
- jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
- jine = dingdan_obj.jine # 订单金额(全额退款)
-
- # 11. 验证订单金额是否存在
- if jine is None:
- return Response(
- {'code': 400, 'message': '订单缺少订单金额', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 12. 获取老板ID(从平台订单扩展表)
- try:
- pingtai_kuozhan = dingdan_obj.pingtai_kuozhan
- laoban_id = pingtai_kuozhan.laoban_id
- except DingdanPingtai.DoesNotExist:
- return Response(
- {'code': 400, 'message': '订单老板信息不存在', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # ========== 修改点开始 ==========
- # 13. 先调用微信退款接口,成功后才更新数据库
- weixin_refund_result = self.call_weixin_refund(dingdan_id, jine)
-
- if not weixin_refund_result.get('success'):
- # 微信退款失败,直接返回错误,数据库不更新
- logger.error(f"微信退款失败,订单:{dingdan_id},错误:{weixin_refund_result.get('msg')}")
- return Response(
- {'code': 400, 'message': weixin_refund_result.get('msg', '退款失败')},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 14. 微信退款受理成功,开始事务更新数据库
- with transaction.atomic():
- # 14.1 更新订单状态为5(已退款)
- dingdan_obj.zhuangtai = 5
- dingdan_obj.save()
-
- # 14.2 更新打手扩展表(退款订单总量+1)
- if jiedan_dashou_id:
- try:
- # 查询打手用户主表
- dashou_user = UserMain.objects.get(
- yonghuid=jiedan_dashou_id,
- #user_type='dashou'
- )
- dashou_profile = dashou_user.dashou_profile
- dashou_profile.tuikuanliang += 1
- dashou_profile.save()
- except UserMain.DoesNotExist:
- logger.warning(f"平台退款:打手{jiedan_dashou_id}不存在,跳过打手更新")
- except AttributeError:
- logger.warning(f"平台退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手更新")
-
- # 14.3 更新老板扩展表
- if laoban_id:
- try:
- laoban_user = UserMain.objects.get(
- yonghuid=laoban_id,
- #user_type='normal'
- )
- laoban_profile = laoban_user.boss_profile
- laoban_profile.alltui += 1
- laoban_profile.zonge -= jine
- if laoban_profile.zonge < 0:
- laoban_profile.zonge = 0
- laoban_profile.save()
- except UserMain.DoesNotExist:
- logger.warning(f"平台退款:老板{laoban_id}不存在,跳过老板更新")
- except AttributeError:
- logger.warning(f"平台退款:老板{laoban_id}扩展信息不存在,跳过老板更新")
-
- # 14.4 更新收支记录表(id=1的记录)
- try:
- szjilu_obj = Szjilu.objects.get(id=1)
- szjilu_obj.zongsy -= jine
- szjilu_obj.zongzc += jine
- szjilu_obj.jrzc += jine
- szjilu_obj.save()
- except Szjilu.DoesNotExist:
- logger.error(f"平台退款:收支记录表(id=1)不存在,跳过更新")
- except Exception as e:
- logger.error(f"更新收支记录表异常: {str(e)}")
-
- # 14.5 更新退款记录表
- try:
- # 查询退款记录表,是否已有该订单的记录
- try:
- tuikuan_jilu = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
- tuikuan_jilu.sqzhuangtai = 1
- if tuikuan_liyou:
- tuikuan_jilu.liyou = tuikuan_liyou
- tuikuan_jilu.chuliid = qianzhanghao
- tuikuan_jilu.save()
- except Tuikuanjilu.DoesNotExist:
- # 退款记录表不存在,创建新的记录
- Tuikuanjilu.objects.create(
- dingdan_id=dingdan_id,
- dashouid=jiedan_dashou_id if jiedan_dashou_id else '',
- qingqiuid=laoban_id if laoban_id else '',
- chuliid=qianzhanghao,
- liyou=tuikuan_liyou if tuikuan_liyou else '给老板退款',
- sqzhuangtai=1,
- jine=jine,
- dashou_fencheng=dingdan_obj.dashou_fencheng if dingdan_obj.dashou_fencheng else 0,
- jieshao=dingdan_obj.jieshao if dingdan_obj.jieshao else '',
- beizhu='给老板退款',
- nicheng=dingdan_obj.nicheng if dingdan_obj.nicheng else '',
- )
- except Exception as e:
- logger.error(f"更新退款记录表异常: {str(e)}")
-
- logger.info(f"平台退款成功,订单:{dingdan_id},退款单号:{weixin_refund_result.get('refund_id')}")
- # ========== 修改点结束 ==========
-
- # 15. 返回成功响应
- return Response({
- 'code': 0,
- 'message': '退款处理成功',
- 'data': None
- })
-
- except Exception as e:
- logger.error(f"平台退款接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-
- def call_weixin_refund(self, dingdan_id, jine):
- """
- 调用微信退款接口
- 参考你提供的接口,但根据现有模型和配置调整
-
- 需要在settings.py中配置以下参数:
- WEIXIN_APPID = '你的小程序appid'
- WEIXIN_MCHID = '你的商户号'
- WEIXIN_SHANGHUMIYAO = '你的商户密钥'
- WEIXIN_CERT_PATH = '证书路径/cert/apiclient_cert.pem'
- WEIXIN_KEY_PATH = '证书路径/cert/apiclient_key.pem'
-
- 返回:{'success': True/False, 'msg': '消息'}
- """
- try:
- # 检查必要的配置
- required_settings = ['WEIXIN_APPID', 'WEIXIN_MCHID', 'WEIXIN_SHANGHUMIYAO', 'WEIXIN_CERT_PATH',
- 'WEIXIN_KEY_PATH']
- for setting in required_settings:
- if not hasattr(settings, setting) or not getattr(settings, setting):
- return {'success': False, 'msg': f'缺少微信支付配置: {setting}'}
-
- # 生成退款单号
- refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}"
-
- # 金额转换为分(必须是整数)
- total_fee_in_fen = int(float(jine) * 100)
-
- # 准备微信退款参数
- refund_data = {
- 'appid': settings.WEIXIN_APPID,
- 'mch_id': settings.WEIXIN_MCHID,
- 'nonce_str': hashlib.md5(str(time.time()).encode()).hexdigest(),
- 'out_trade_no': dingdan_id, # 商户订单号(我们的订单ID)
- 'out_refund_no': refund_id, # 商户退款单号
- 'total_fee': total_fee_in_fen, # 订单总金额(分)
- 'refund_fee': total_fee_in_fen, # 退款金额(分),全额退款
- 'op_user_id': settings.WEIXIN_MCHID, # 操作员ID,默认为商户号
- 'refund_account': 'REFUND_SOURCE_UNSETTLED_FUNDS' # 退款资金来源
- }
-
- # 生成签名
- sorted_params = sorted(refund_data.items())
- sign_string = '&'.join([f"{key}={value}" for key, value in sorted_params if value])
- sign_string += f"&key={settings.WEIXIN_SHANGHUMIYAO}"
- sign = hashlib.md5(sign_string.encode()).hexdigest().upper()
- refund_data['sign'] = sign
-
- # 将字典转换为XML
- xml_data = self.dict_to_xml(refund_data)
-
- # 微信退款API地址
- weixin_refund_url = "https://api.mch.weixin.qq.com/secapi/pay/refund"
-
- # 设置超时时间
- timeout = 30
-
- # 调用微信退款API(需要证书)
- headers = {'Content-Type': 'application/xml'}
- response = requests.post(
- weixin_refund_url,
- data=xml_data.encode('utf-8'),
- headers=headers,
- cert=(settings.WEIXIN_CERT_PATH, settings.WEIXIN_KEY_PATH),
- timeout=timeout
- )
-
- # 解析返回结果
- if response.status_code == 200:
- result = self.xml_to_dict(response.content)
-
- if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
- logger.info(f"微信退款成功,订单:{dingdan_id},退款单号:{refund_id}")
- return {'success': True, 'msg': '退款申请提交成功', 'refund_id': refund_id}
- else:
- error_msg = result.get('err_code_des', result.get('return_msg', '退款申请失败'))
- logger.error(f"微信退款失败,订单:{dingdan_id},错误:{error_msg}")
- return {'success': False, 'msg': f'微信退款失败: {error_msg}'}
- else:
- logger.error(f"微信退款接口调用失败,状态码:{response.status_code}")
- return {'success': False, 'msg': '微信退款接口调用失败'}
-
- except requests.exceptions.Timeout:
- logger.error(f"微信退款接口请求超时,订单:{dingdan_id}")
- return {'success': False, 'msg': '微信退款接口请求超时'}
- except Exception as e:
- logger.error(f"调用微信退款接口异常: {str(e)}")
- return {'success': False, 'msg': f'退款处理异常: {str(e)}'}
-
-
-
- def dict_to_xml(self, dict_data):
- """将字典转换为XML字符串"""
- xml = [""]
- for k, v in dict_data.items():
- xml.append(f"<{k}>{v}{k}>")
- xml.append("")
- return "".join(xml)
-
- def xml_to_dict(self, xml_data):
- """将XML转换为字典"""
- try:
- root = ET.fromstring(xml_data)
- result = {}
- for child in root:
- result[child.tag] = child.text
- return result
- except ET.ParseError as e:
- logger.error(f"XML解析失败: {str(e)}")
- return {'return_code': 'FAIL', 'return_msg': 'XML解析失败'}'''
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-logger = logging.getLogger(__name__)
-
-
-class DashouHuoquLeixingView(APIView):
- """
- 打手获取商品类型接口 - 抢单页面使用
- 接口地址:POST /dingdan/dsqdhqddlx
- 功能:返回所有正常的商品类型,已按排序权重排序
-
- 返回格式(与前端原数据格式完全一致):
- {
- "code": 200,
- "msg": "成功",
- "data": {
- "list": [
- {
- "id": 1,
- "jieshao": "王者荣耀",
- "tupian_url": "leixing/wzry.jpg",
- "yaoqiuleixing": null,
- "huiyuan_id": null,
- "yongjin": null,
- "paixu": 100,
- "shenhezhuangtai": 1
- },
- // ... 更多类型
- ]
- }
- }
- """
-
- # 🔴 使用JWT Token认证
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- """
- 处理POST请求,返回商品类型列表
-
- 请求参数:无特殊参数,只验证用户身份
-
- 安全验证:
- 1. JWT Token验证用户身份
- 2. 只返回 shenhezhuangtai=1(正常状态)的类型
- 3. 数据已排序:paixu降序 → id升序
- """
- try:
- logger.info(f"打手获取商品类型请求 - 用户ID: {request.user.yonghuid}")
-
- # 🔴 导入商品类型模型
- from shangpin.models import ShangpinLeixing
-
- # 🔴 查询所有正常状态的商品类型
- # 条件:shenhezhuangtai=1(正常状态)
- # 排序:先按 paixu 降序(数值大的在前),再按 id 升序
- leixing_queryset = ShangpinLeixing.objects.filter(
- shenhezhuangtai=1 # 🔴 只取正常状态,排除审核专用
- ).order_by('-paixu', 'id')
-
- # 🔴 构建返回数据列表
- leixing_list = []
- for leixing in leixing_queryset:
- leixing_list.append({
- "id": leixing.id,
- "jieshao": leixing.jieshao or "",
- "tupian_url": leixing.tupian_url or "",
- "yaoqiuleixing": leixing.yaoqiuleixing,
- "huiyuan_id": leixing.huiyuan_id,
- "yongjin": float(leixing.yongjin) if leixing.yongjin else None,
- "paixu": leixing.paixu or 0,
- "shenhezhuangtai": leixing.shenhezhuangtai or 1
- })
-
- # 🔴 记录日志
- logger.info(f"返回商品类型数据:{len(leixing_list)}条,用户: {request.user.yonghuid}")
-
- # 🔴 返回成功响应
- # 注意:保持与前端原数据格式完全一致
- return Response({
- "code": 200,
- "msg": "成功",
- "data": {
- "list": leixing_list # 🔴 保持原字段名 "list"
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- # 🔴 异常处理
- logger.error(f"打手获取商品类型失败 - 用户ID: {request.user.yonghuid}, 错误: {str(e)}", exc_info=True)
-
- return Response({
- "code": 500,
- "msg": f"服务器内部错误: {str(e)}",
- "data": None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-
-
-
-
-
-
-
-logger = logging.getLogger(__name__)
-
-from peizhi.models import Club ,OrderTypeMapping
-
-logger = logging.getLogger(__name__)
-
-
-
-
-class PartnerSyncOrderView(APIView):
- """
- 接收对方平台派单通知接口
- 对方平台商家派单成功后,异步调用此接口,将订单信息同步过来。
- """
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
- parser_classes = [JSONParser]
-
- def post(self, request):
- # 1. 验证 token
- token = request.data.get('token')
- if token != settings.CROSS_PLATFORM_TOKEN:
- logger.warning(f"跨平台派单 token 无效: {token}")
- return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
-
- # 2. 获取必要参数
- our_club_id = request.data.get('our_club_id') # 对方平台发送的我方俱乐部ID(即我方在对方表中的 partner_club_id)
- partner_club_id = request.data.get('partner_club_id') # 对方平台的俱乐部ID(对方自己的ID)
- partner_order_id = request.data.get('partner_order_id')
- jine = request.data.get('jine')
- dashou_fencheng = request.data.get('dashou_fencheng')
- jieshao = request.data.get('jieshao')
- beizhu = request.data.get('beizhu', '')
- create_time_str = request.data.get('create_time')
- nicheng = request.data.get('nicheng')
- leixing_id = request.data.get('leixing_id') # 对方平台的商品类型ID
- partner_yonghu_id = request.data.get('user_id') # 对方下单用户ID
-
- # 必需字段校验
- required_fields = [our_club_id, partner_club_id, partner_order_id, jine, dashou_fencheng,
- jieshao, create_time_str, nicheng, leixing_id]
- if not all(required_fields):
- logger.error(f"跨平台派单参数缺失: {request.data}")
- return Response({'code': 400, 'message': 'Missing required parameters'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 验证我方俱乐部是否存在且同意接收订单
- try:
- my_club = Club.objects.get(partner_club_id=our_club_id)
- except Club.DoesNotExist:
- logger.warning(f"我方俱乐部不存在,partner_club_id={our_club_id}")
- return Response({'code': 404, 'message': 'Club not found'}, status=status.HTTP_404_NOT_FOUND)
-
- if not my_club.is_receive_enabled:
- logger.warning(f"我方俱乐部 {my_club.club_id} 未同意接收订单")
- return Response({'code': 403, 'message': 'Receiving not allowed'}, status=status.HTTP_403_FORBIDDEN)
-
- # 4. 查找订单类型映射
- try:
- mapping = OrderTypeMapping.objects.get(
- partner_club_id=our_club_id, # 对方在我方的标识
- partner_type_id=leixing_id
- )
- our_type_id = mapping.our_type_id
- except OrderTypeMapping.DoesNotExist:
- logger.error(f"未找到订单类型映射: 对方俱乐部={our_club_id}, 对方类型ID={leixing_id}")
- return Response({'code': 400, 'message': 'Order type mapping not found'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 5. 获取我方商品类型信息(用于获取会员ID、佣金等)
- try:
- our_leixing = ShangpinLeixing.objects.get(id=our_type_id)
- except ShangpinLeixing.DoesNotExist:
- logger.error(f"我方商品类型不存在: {our_type_id}")
- return Response({'code': 400, 'message': 'Local product type not found'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 6. 幂等检查:如果已存在相同 partner_order_id 的订单,直接返回(附带 status='existed')
- existing = Dingdan.objects.filter(partner_order_id=partner_order_id).first()
- if existing:
- logger.info(f"跨平台派单幂等返回: partner_order_id={partner_order_id} 已存在")
- return Response({
- 'code': 0,
- 'data': {
- 'local_order_id': existing.dingdan_id,
- 'status': 'existed' # 明确告知已存在
- }
- })
-
- # 7. 生成我方订单ID(确保唯一性)
- def generate_dingdan_id():
- timestamp = int(time.time() * 1000)
- process_id = os.getpid() % 1000
- random_num = random.randint(1000, 9999)
- return f"SJ{timestamp}{process_id:03d}{random_num}"
-
- # 8. 获取代理商家头像(用于订单图片)
- agent_user = UserMain.objects.filter(yonghuid=settings.AGENT_SHANGJIA_ID).first()
- agent_avatar = agent_user.avatar if agent_user else ''
-
- # 9. 计算我方实际打手分成金额:对方传来的打手分成 × 我方接收费率
- try:
- actual_dashou_fencheng = Decimal(dashou_fencheng) * my_club.receive_rate
- except Exception:
- actual_dashou_fencheng = Decimal(dashou_fencheng)
-
- # 10. 事务创建订单
- try:
- with transaction.atomic():
- new_id = generate_dingdan_id()
- # 创建主订单
- dingdan = Dingdan.objects.create(
- dingdan_id=new_id,
- zhuangtai=1, # 普通订单(待接单)
- fadan_pingtai=2, # 商家发单
- jine=jine, # 原始订单金额
- dashou_fencheng=actual_dashou_fencheng, # 调整后的打手分成
- jieshao=jieshao,
- beizhu=beizhu,
- create_time=create_time_str,
- yaoqiuleixing=our_leixing.yaoqiuleixing,
- nicheng=nicheng,
- huiyuan_id=our_leixing.huiyuan_id,
- yongjin=our_leixing.yongjin,
- leixing_id=our_type_id,
- partner_order_id=partner_order_id, # 存储对方订单ID
- is_cross=1,
- dispatch_type=2, # 对方派单
- partner_club_id=our_club_id, # 对方俱乐部ID
- tupian=agent_avatar, # 使用代理商家头像作为订单图片
- )
- # 创建商家扩展表(使用代理商家)
- DingdanShangjia.objects.create(
- dingdan=dingdan,
- shangjia_id=settings.AGENT_SHANGJIA_ID,
- sjnicheng='跨平台代理',
- partner_yonghu_id=partner_yonghu_id or '',
- )
-
- # 成功创建后更新每日派单统计(方向=对方派单)
- try:
- update_daily_dispatch_stat(
- direction=2, # 对方派单
- partner_club_id=our_club_id,
- dispatch_amount=Decimal(jine), # 统计使用原始订单金额
-
- )
- except Exception as e:
- logger.error(f"更新派单统计失败: {e}", exc_info=True)
-
- logger.info(f"跨平台订单同步成功: partner_order_id={partner_order_id}, local_id={new_id}")
-
-
-
- #🟢【7. 新增】支付成功后的广播通知(异步,不阻塞主流程)
- from dingdan.tongzhi_tasks import dingdan_guangbo
-
-
-
- # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用)
- order_info = {
- 'dingdan_id': dingdan.dingdan_id,
- 'game_type': self._get_game_type_name(dingdan.leixing_id),
- 'amount': str(dingdan.jine),
- 'order_desc': (dingdan.jieshao or '')[:50],
- }
- dingdan_guangbo.delay(order_info) # 瞬间返回,不阻塞请求
-
-
-
-
- return Response({
- 'code': 0,
- 'data': {
- 'local_order_id': new_id,
- 'status': 'created'
- }
- })
-
- except Exception as e:
- logger.error(f"跨平台订单同步失败: {str(e)}", exc_info=True)
- return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- def _get_game_type_name(self, leixing_id):
- try:
- from shangpin.models import ShangpinLeixing
- gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
- return gt.jieshao if gt else '游戏订单'
- except Exception as e:
- logger.error(f"获取游戏类型名称异常: {e}")
- return '游戏订单'
-
-
-
-
-from dingdan.utils import update_daily_dispatch_stat
-
-logger = logging.getLogger(__name__)
-
-
-
-
-
-
-class PartnerCheckClaimView(APIView):
- """
- 接收对方平台抢单询问(对方打手抢我方派出的订单)
- 如果允许抢单:
- 1. 订单状态改为2(已接单)
- 2. 记录对方打手ID、对方俱乐部ID、对方订单ID
- 3. 更新派单成交统计(success_count/success_amount)
- """
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
- parser_classes = [JSONParser]
-
- def post(self, request):
- token = request.data.get('token')
- if token != settings.CROSS_PLATFORM_TOKEN:
- logger.warning(f"token无效: {token}")
- return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
-
- wofangdingdan_id = request.data.get('wofangdingdan_id')
- partner_order_id = request.data.get('partner_order_id')
- our_club_id = request.data.get('our_club_id')
- dashou_id = request.data.get('dashou_id')
- sj_id = request.data.get('sj_id')
-
- if not all([wofangdingdan_id, partner_order_id, our_club_id, dashou_id, sj_id]):
- logger.error(f"参数缺失: {request.data}")
- return Response({'code': 400, 'message': 'Missing parameters'}, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- with transaction.atomic():
- # 查询我方俱乐部配置
- try:
- club = Club.objects.select_for_update().get(partner_club_id=our_club_id)
- except Club.DoesNotExist:
- logger.warning(f"俱乐部配置不存在 partner_club_id={our_club_id}")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- if not club.is_interop_enabled:
- logger.warning(f"俱乐部 {club.club_id} 未开启互通")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- # 锁定订单
- try:
- order = Dingdan.objects.select_for_update().get(dingdan_id=wofangdingdan_id)
- except Dingdan.DoesNotExist:
- logger.warning(f"订单不存在: {wofangdingdan_id}")
- return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
-
- # 只有状态1可抢
- if order.zhuangtai != 1:
- logger.info(f"订单 {order.dingdan_id} 状态 {order.zhuangtai} 不可抢")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- # 商品类型检查
- try:
- leixing = ShangpinLeixing.objects.get(id=order.leixing_id)
- except ShangpinLeixing.DoesNotExist:
- logger.warning(f"商品类型不存在: {order.leixing_id}")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- if not leixing.is_cross_enabled:
- logger.warning(f"商品类型 {leixing.jieshao} 未开启跨平台")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- # 执行抢单:状态改为2(已接单)
- order.zhuangtai = 2
- order.jiedan_dashou_id = dashou_id
- order.partner_club_id = our_club_id
- order.partner_order_id = partner_order_id
- if order.is_cross != 1:
- order.is_cross = 1
- # dispatch_type 保持原值(1-我方派单),不变
- order.save()
-
- # 更新派单成交统计(我方派单被成功接单)
- update_daily_dispatch_stat(
- direction=1,
- partner_club_id=our_club_id,
- claimed_amount=order.jine,
-
- )
-
- logger.info(f"对方抢单成功: 订单 {order.dingdan_id}, 对方打手 {dashou_id}")
- return Response({'code': 0, 'data': {'can_claim': True}})
-
- except Exception as e:
- logger.error(f"异常: {e}", exc_info=True)
- return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-class PartnerNotifyClaimView(APIView):
- """
- 接收对方平台抢单成功通知(对方派单被他们自己的打手抢了)
- 我方根据 partner_order_id 找到订单,如果状态为1则改为20,不更新任何统计。
- """
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
- parser_classes = [JSONParser]
-
- def post(self, request):
- token = request.data.get('token')
- if token != settings.CROSS_PLATFORM_TOKEN:
- logger.warning(f"token无效: {token}")
- return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
-
- partner_order_id = request.data.get('partner_order_id')
- if not partner_order_id:
- logger.error("缺少 partner_order_id")
- return Response({'code': 400, 'message': 'Missing partner_order_id'}, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- with transaction.atomic():
- order = Dingdan.objects.select_for_update().get(partner_order_id=partner_order_id)
- # 只有状态为1(未被抢)时才改为20
- if order.zhuangtai == 1:
- order.zhuangtai = 20
- order.save()
- logger.info(f"订单 {order.dingdan_id} 状态改为20(被其他俱乐部抢走)")
- else:
- logger.info(f"订单 {order.dingdan_id} 当前状态 {order.zhuangtai},无需修改")
- return Response({'code': 0, 'message': 'OK'})
- except Dingdan.DoesNotExist:
- logger.warning(f"订单不存在 partner_order_id={partner_order_id}")
- return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
- except Exception as e:
- logger.error(f"异常: {e}", exc_info=True)
- return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-'''class PartnerSyncOrderView(APIView):
- """
- 接收对方平台派单通知接口
- 对方平台商家派单成功后,异步调用此接口,将订单信息同步过来。
- 请求方式: POST
- 接口地址: /dingdan/partner_sync_order
- """
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
- parser_classes = [JSONParser]
-
- def post(self, request):
- # 1. 验证 token
- token = request.data.get('token')
- if token != settings.CROSS_PLATFORM_TOKEN:
- logger.warning(f"跨平台派单 token 无效: {token}")
- return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
-
- # 2. 获取必要参数
- our_club_id = request.data.get('our_club_id') # 对方平台发送的我方俱乐部ID(即对方在 Club 表中 partner_club_id)
- partner_club_id = request.data.get('partner_club_id') # 对方平台的俱乐部ID(对方自己的ID)
- partner_order_id = request.data.get('partner_order_id')
- jine = request.data.get('jine')
- dashou_fencheng = request.data.get('dashou_fencheng')
- jieshao = request.data.get('jieshao')
- beizhu = request.data.get('beizhu', '')
- create_time_str = request.data.get('create_time')
- nicheng = request.data.get('nicheng')
- leixing_id = request.data.get('leixing_id') # 对方平台的商品类型ID
-
- # 必需字段校验
- required_fields = [our_club_id, partner_club_id, partner_order_id, jine, dashou_fencheng,
- jieshao, create_time_str, nicheng, leixing_id]
- if not all(required_fields):
- logger.error(f"跨平台派单参数缺失: {request.data}")
- return Response({'code': 400, 'message': 'Missing required parameters'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 验证我方俱乐部是否存在且同意接收订单
- # 注意:our_club_id 是对方发送过来的,代表我方在对方系统中的ID,所以用 partner_club_id 匹配
- try:
- my_club = Club.objects.get(partner_club_id=our_club_id)
- except Club.DoesNotExist:
- logger.warning(f"我方俱乐部不存在,partner_club_id={our_club_id}")
- return Response({'code': 404, 'message': 'Club not found'}, status=status.HTTP_404_NOT_FOUND)
-
- if not my_club.is_receive_enabled:
- logger.warning(f"我方俱乐部 {my_club.club_id} 未同意接收订单")
- return Response({'code': 403, 'message': 'Receiving not allowed'}, status=status.HTTP_403_FORBIDDEN)
-
- # 4. 查找订单类型映射
- try:
- mapping = OrderTypeMapping.objects.get(
- partner_club_id=our_club_id,
- partner_type_id=leixing_id
- )
- our_type_id = mapping.our_type_id
- except OrderTypeMapping.DoesNotExist:
- logger.error(f"未找到订单类型映射: 对方俱乐部={our_club_id}, 对方类型ID={leixing_id}")
- return Response({'code': 400, 'message': 'Order type mapping not found'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 5. 获取我方商品类型信息(用于获取会员ID、佣金等)
- try:
- our_leixing = ShangpinLeixing.objects.get(id=our_type_id)
- except ShangpinLeixing.DoesNotExist:
- logger.error(f"我方商品类型不存在: {our_type_id}")
- return Response({'code': 400, 'message': 'Local product type not found'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 6. 幂等检查:如果已存在相同 partner_order_id 的订单,直接返回
- existing = Dingdan.objects.filter(partner_order_id=partner_order_id).first()
- if existing:
- logger.info(f"跨平台派单幂等返回: partner_order_id={partner_order_id} 已存在")
- return Response({
- 'code': 0,
- 'data': {'local_order_id': existing.dingdan_id}
- })
-
- # 7. 生成我方订单ID
- def generate_dingdan_id():
- import time, random, os
- timestamp = int(time.time() * 1000)
- process_id = os.getpid() % 1000
- random_num = random.randint(1000, 9999)
- return f"SJ{timestamp}{process_id:03d}{random_num}"
-
- # 8. 获取代理商家头像(用于订单图片)
- agent_user = UserMain.objects.filter(yonghuid=settings.AGENT_SHANGJIA_ID).first()
- agent_avatar = agent_user.avatar if agent_user else ''
-
- # 9. 事务创建订单
- try:
- with transaction.atomic():
- new_id = generate_dingdan_id()
- # 创建主订单
- dingdan = Dingdan.objects.create(
- dingdan_id=new_id,
- zhuangtai=1, # 普通订单(默认)
- fadan_pingtai=2, # 商家发单
- jine=jine,
- dashou_fencheng=dashou_fencheng,
- jieshao=jieshao,
- beizhu=beizhu,
- create_time=create_time_str,
- yaoqiuleixing=our_leixing.yaoqiuleixing,
- nicheng=nicheng,
- huiyuan_id=our_leixing.huiyuan_id,
- yongjin=our_leixing.yongjin,
- leixing_id=our_type_id,
- partner_order_id=partner_order_id,
- is_cross=1,
- dispatch_type=2, # 对方派给我方
- partner_club_id=our_club_id, # 对方的俱乐部ID
- tupian=agent_avatar, # 使用代理商家头像作为订单图片
- )
- # 创建商家扩展表(使用代理商家)
- DingdanShangjia.objects.create(
- dingdan=dingdan,
- shangjia_id=settings.AGENT_SHANGJIA_ID,
- sjnicheng='跨平台代理'
- )
- logger.info(f"跨平台订单同步成功: partner_order_id={partner_order_id}, local_id={new_id}")
- return Response({
- 'code': 0,
- 'data': {'local_order_id': new_id}
- },status = 200)
- except Exception as e:
- logger.error(f"跨平台订单同步失败: {str(e)}", exc_info=True)
- return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-from dingdan.utils import update_daily_dispatch_stat
-
-logger = logging.getLogger(__name__)
-
-
-
-
-
-
-class PartnerCheckClaimView(APIView):
- """
- 接收对方平台抢单询问接口
- 对方平台打手抢单前,调用此接口询问订单是否可抢。
- 请求方式: POST
- 接口地址: /dingdan/partner_check_claim
- 请求参数:
- token: 认证令牌
- partner_order_id: 对方平台订单ID(即我方记录的 partner_order_id)
- partner_club_id: 对方俱乐部ID(对方在我方系统中的标识)
- dashou_id: 对方平台打手ID
- sj_id: 对方平台商家ID(带前缀,如 SjAGENT001)
- 返回:
- code: 0成功
- data: {
- can_claim: true/false
- }
- 注意:此接口会原子地检查并更新订单状态,必须使用行锁。
- """
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
- parser_classes = [JSONParser]
-
- def post(self, request):
- # 1. 验证 token
- token = request.data.get('token')
- if token != settings.CROSS_PLATFORM_TOKEN:
- logger.warning(f"跨平台抢单询问 token 无效: {token}")
- return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
-
- # 2. 获取必要参数
- duifangdingdan_id = request.data.get('wofangdingdan_id')
- partner_order_id = request.data.get('partner_order_id') # 我方订单ID
- partner_club_id = request.data.get('our_club_id') # 对方俱乐部ID
- dashou_id = request.data.get('dashou_id') # 对方打手ID
- sj_id = request.data.get('sj_id') # 对方商家ID(带前缀,如 SjAGENT001)
-
- if not all([partner_order_id, partner_club_id, dashou_id, sj_id]):
- logger.error(f"跨平台抢单询问参数缺失: {request.data}")
- return Response({'code': 400, 'message': 'Missing parameters'}, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- with transaction.atomic():
- # 3. 查询我方俱乐部配置(通过对方的 partner_club_id)
- try:
- club = Club.objects.select_for_update().get(partner_club_id=partner_club_id)
- except Club.DoesNotExist:
- logger.warning(f"跨平台抢单询问: 我方俱乐部配置不存在 partner_club_id={partner_club_id}")
- return Response({'code': 404, 'message': 'Club not found'}, status=status.HTTP_404_NOT_FOUND)
-
- # 检查俱乐部是否已开启平台互通(我方同意接收对方订单)
- if not club.is_interop_enabled:
- logger.warning(f"俱乐部 {club.club_id} 未开启接收跨平台订单,拒绝抢单")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- # 4. 查询订单并锁定
- try:
- order = Dingdan.objects.select_for_update().get(dingdan_id=partner_order_id)
- except Dingdan.DoesNotExist:
- logger.warning(f"跨平台抢单询问: 订单不存在 dingdan_id={partner_order_id}")
- return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
-
- # 5. 检查订单状态(必须为1)
- if order.zhuangtai != 1:
- logger.info(f"跨平台抢单失败: 订单状态 {order.zhuangtai},不可抢")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- # 6. 检查订单对应的商品类型是否支持跨平台抢单
- try:
- leixing = ShangpinLeixing.objects.get(id=order.leixing_id)
- except ShangpinLeixing.DoesNotExist:
- logger.warning(f"订单 {order.dingdan_id} 的商品类型 {order.leixing_id} 不存在")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- if not leixing.is_cross_enabled:
- logger.warning(f"商品类型 {leixing.jieshao} 未开启跨平台抢单")
- return Response({'code': 0, 'data': {'can_claim': False}})
-
- # 7. 所有检查通过,执行抢单操作
- # 更新订单主表
- order.zhuangtai = 2 # 已接单
- order.jiedan_dashou_id = dashou_id # 对方打手ID
- order.is_cross = 1 # 标记为跨平台订单
- order.dispatch_type = 1 # 我方派单,对方抢单
- order.partner_club_id = partner_club_id # 记录对方俱乐部ID
- order.partner_order_id = duifangdingdan_id#记录对方订单id
- # 存储对方商家ID(带前缀)到商家扩展表的 shangjia_id(如果原来有则更新)
- # 注意:商家扩展表已存在,需要更新或创建?原订单可能已有 shangjia_kuozhan,我们直接更新
- order.save()
-
-
-
- # 8. 记录每日派单统计(使用新统计函数,不再更新 Club 表)
- fencheng = order.dashou_fencheng or Decimal('0.00')
- amount = fencheng * club.dashou_rate
- update_daily_dispatch_stat(
- dispatch_date=timezone.now().date(),
- direction=2, # 对方派单
- partner_club_id=partner_club_id,
- amount=amount,
- count=1
- )
- # 9. 返回成功,告知对方可以抢单
- logger.info(f"跨平台抢单成功: 订单 {order.dingdan_id}, 对方打手 {dashou_id}, 俱乐部 {club.club_id}")
- return Response({'code': 0, 'data': {'can_claim': True}})
-
- except Exception as e:
- logger.error(f"跨平台抢单询问异常: {str(e)}", exc_info=True)
- return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-
-
-
-class PartnerNotifyClaimView(APIView):
- """
- 接收对方平台抢单成功通知接口
- 对方平台打手抢单成功后,调用此接口,我方处理本地订单。
- 请求方式: POST
- 接口地址: /dingdan/partner_notify_claim
- 请求参数:
- token: 认证令牌,需与settings.CROSS_PLATFORM_TOKEN一致
- order_id: 对方平台的订单ID(即我方记录的 partner_order_id)
- 返回:
- code: 0成功,其他失败
- message: 描述信息
- """
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
- parser_classes = [JSONParser]
-
- def post(self, request):
- # 1. 验证token
- token = request.data.get('token')
- if token != settings.CROSS_PLATFORM_TOKEN:
- logger.warning(f"跨平台抢单通知 token 无效: {token}")
- return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
-
- order_id = request.data.get('local_order_id') # 对方平台的订单ID(即我方的 partner_order_id)
- if not order_id:
- logger.error("跨平台抢单通知参数缺失: order_id")
- return Response({'code': 400, 'message': 'Missing order_id'}, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- with transaction.atomic():
- # 2. 根据 partner_order_id 查找订单
- order = Dingdan.objects.select_for_update().get(partner_order_id=order_id)
-
- # 3. 根据订单状态处理
- if order.zhuangtai == 1:
- # 订单未被抢,直接删除(包括关联的商家扩展表)
- # 先删除扩展表
- DingdanShangjia.objects.filter(dingdan=order).delete()
- # 再删除主订单
- order.delete()
- logger.info(f"跨平台抢单通知:订单状态为1,已删除,partner_order_id={order_id}")
- return Response({'code': 0, 'message': 'Order deleted'})
- else:
- # 订单状态不是1(已被抢或异常),改为状态15(异常订单)
- order.zhuangtai = 15
- order.save()
- logger.info(f"跨平台抢单通知:订单状态异常,已改为15,partner_order_id={order_id}")
- return Response({'code': 0, 'message': 'Order marked as abnormal'})
-
- except Dingdan.DoesNotExist:
- logger.warning(f"跨平台抢单通知:订单不存在 partner_order_id={order_id}")
- return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
- except Exception as e:
- logger.error(f"跨平台抢单通知异常: {str(e)}", exc_info=True)
- return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
-
-
-
-
-
-
-
-
-
-class PartnerRefundNotifyView(APIView):
- """
- 接收对方平台退款通知接口
- 对方平台商家申请退款时,调用此接口通知我方,我方将本地对应订单改为退款状态。
- 请求方式: POST
- 接口地址: /dingdan/partner_refund_notify
- 请求参数:
- token: 认证令牌,需与settings.CROSS_PLATFORM_TOKEN一致
- order_id: 对方平台的订单ID(即我方记录的 partner_order_id)
- 返回:
- code: 0成功,其他失败
- message: 描述信息
- """
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
- parser_classes = [JSONParser]
-
- def post(self, request):
- # 1. 验证token
- token = request.data.get('token')
- if token != settings.CROSS_PLATFORM_TOKEN:
- logger.warning(f"跨平台退款通知 token 无效: {token}")
- return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
-
- order_id = request.data.get('order_id') # 对方平台的订单ID(即我方的 partner_order_id)
- if not order_id:
- logger.error("跨平台退款通知参数缺失: order_id")
- return Response({'code': 400, 'message': 'Missing order_id'}, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- with transaction.atomic():
- # 2. 根据 partner_order_id 查找订单(必须存在且为跨平台订单)
- order = Dingdan.objects.select_for_update().get(partner_order_id=order_id, is_cross=1)
- # 如果订单已完成,不允许退款
- if order.zhuangtai == 3:
- logger.warning(f"跨平台退款通知:订单 {order_id} 已完成,不可退款")
- return Response({'code': 400, 'message': 'Order already completed, cannot refund'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 3. 如果订单状态不是5,则改为5(已退款),并更新打手信息
- if order.zhuangtai != 5:
- # 记录原打手ID
- old_dashou_id = order.jiedan_dashou_id
- if old_dashou_id:
- try:
- # 查询打手用户主表
- dashou_user = UserMain.objects.get(yonghuid=old_dashou_id)
- # 通过反向关系获取打手扩展表
- if hasattr(dashou_user, 'dashou_profile'):
- dashou_profile = dashou_user.dashou_profile
- dashou_profile.zhuangtai = 1 # 恢复打手状态
- dashou_profile.tuikuanliang += 1 # 退款订单量+1
- dashou_profile.save()
- logger.info(f"退款通知:打手 {old_dashou_id} 状态已恢复,退款订单+1")
- except UserMain.DoesNotExist:
- logger.warning(f"退款通知:打手用户不存在 {old_dashou_id}")
- except Exception as e:
- logger.error(f"退款通知:更新打手信息失败 {str(e)}")
-
- # 更新订单状态为5
- order.zhuangtai = 5
- # 可选:清空接单打手ID
- #order.jiedan_dashou_id = None
- order.save()
- logger.info(f"跨平台退款通知成功:订单 {order_id} 已改为退款状态")
- else:
- logger.info(f"跨平台退款通知:订单 {order_id} 已是退款状态,无需重复处理")
-
- return Response({'code': 0, 'message': 'Refund processed successfully'})
-
- except Dingdan.DoesNotExist:
- logger.warning(f"跨平台退款通知:订单不存在或不是跨平台订单,partner_order_id={order_id}")
- return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
- except Exception as e:
- logger.error(f"跨平台退款通知异常: {str(e)}", exc_info=True)
- return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-
-logger = logging.getLogger(__name__)
-
-
-class AdJuJueJieSuan(APIView):
- """
- 后台拒绝结算接口(平台发单)
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /dingdan/adddjjjs
- 参数: {
- "zhanghao": "管理员账号(手机号)",
- "dingdan_id": "订单ID",
- "tkly": "拒绝结算理由(必填)"
- }
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- try:
- # 1. 获取参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
- tkly = request.data.get('tkly', '').strip()
-
- if not all([qianzhanghao, dingdan_id, tkly]):
- return Response(
- {'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id, tkly)', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT用户验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号匹配(管理员账号通常为手机号)
- if str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 查询订单
- try:
- dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 6. 校验订单条件:平台发单(1)且状态为2或8
- if dingdan.fadan_pingtai != 1 or dingdan.zhuangtai not in [2, 8]:
- return Response(
- {'code': 400, 'message': '仅平台发单且状态为进行中(2)或结算中(8)的订单可拒绝结算', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- jiedan_dashou_id = dingdan.jiedan_dashou_id
- if not jiedan_dashou_id:
- return Response(
- {'code': 400, 'message': '订单暂无接单打手', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- with transaction.atomic():
- # 7. 更新订单状态为5(已退款),并保存拒绝理由
- dingdan.zhuangtai = 5
- dingdan.tkly = tkly
- dingdan.save()
-
- # 8. 更新打手扩展表
- try:
- dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
- dashou_profile = dashou_user.dashou_profile
- dashou_profile.zhuangtai = 1 # 打手状态设为正常
- dashou_profile.tuikuanliang += 1 # 退款订单总量+1
- dashou_profile.save()
- except (UserMain.DoesNotExist, AttributeError) as e:
- logger.warning(f"拒绝结算:打手{jiedan_dashou_id}信息更新失败 - {str(e)}")
-
- return Response({'code': 0, 'message': '拒绝结算成功', 'data': None})
-
- except Exception as e:
- logger.error(f"拒绝结算接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-class AdZhuanYiDaTing(APIView):
- """
- 后台转移大厅接口
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /dingdan/adddzydt
- 参数: {
- "zhanghao": "管理员账号(手机号)",
- "dingdan_id": "订单ID"
- }
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- try:
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
-
- if not all([qianzhanghao, dingdan_id]):
- return Response(
- {'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id)', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- dangqianyonghu = request.user
-
- if str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- try:
- dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 校验订单状态:允许2、8、4
- if dingdan.zhuangtai not in [2, 8, 4]:
- return Response(
- {'code': 400, 'message': '仅状态为进行中(2)、结算中(8)或退款审核(4)的订单可转移大厅', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- jiedan_dashou_id = dingdan.jiedan_dashou_id
-
- with transaction.atomic():
- # 更新订单:状态改为1(下单中),清空接单打手ID
- dingdan.zhuangtai = 1
- dingdan.jiedan_dashou_id = None
- dingdan.save()
-
- # 如果有接单打手,更新其扩展表
- if jiedan_dashou_id:
- try:
- dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
- dashou_profile = dashou_user.dashou_profile
- dashou_profile.zhuangtai = 1
- dashou_profile.tuikuanliang += 1
- dashou_profile.save()
- except (UserMain.DoesNotExist, AttributeError) as e:
- logger.warning(f"转移大厅:打手{jiedan_dashou_id}信息更新失败 - {str(e)}")
-
- return Response({'code': 0, 'message': '转移大厅成功', 'data': None})
-
- except Exception as e:
- logger.error(f"转移大厅接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-class AdQuXiaoZhiDing(APIView):
- """
- 后台取消指定接口
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /dingdan/adddqxzd
- 参数: {
- "zhanghao": "管理员账号(手机号)",
- "dingdan_id": "订单ID"
- }
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- try:
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
-
- if not all([qianzhanghao, dingdan_id]):
- return Response(
- {'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id)', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- dangqianyonghu = request.user
-
- if str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- try:
- dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response(
- {'code': 404, 'message': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 校验订单状态:必须为7(指定中)
- if dingdan.zhuangtai != 7:
- return Response(
- {'code': 400, 'message': '仅状态为指定中(7)的订单可取消指定', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- with transaction.atomic():
- # 更新订单:状态改为1(下单中),清空指定ID
- dingdan.zhuangtai = 1
- dingdan.zhiding_id = None
- dingdan.save()
-
- return Response({'code': 0, 'message': '取消指定成功', 'data': None})
-
- except Exception as e:
- logger.error(f"取消指定接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
-
-
-
-
-
-
-
-
-class DashouXiugaiView(APIView):
- """
- 打手修改订单交付内容接口
- 请求方式:POST
- 请求路径:/dingdan/dsxiugaidd
- 权限:需携带有效JWT Token,且用户为打手身份
- 请求参数:
- dingdan_id : 订单ID(必填)
- liuyan : 新的留言内容(可选,最长50字)
- deleted_images : 要删除的图片URL列表(数组,每个元素为完整COS URL)
- new_images : 新增的图片URL列表(数组,每个元素为完整COS URL)
- 返回:
- 成功:{"code":0,"msg":"修改成功","data":null}
- 失败:返回相应错误码和提示
- """
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- # 获取请求数据
- data = request.data
- dingdan_id = data.get('dingdan_id', '')
- liuyan = data.get('liuyan', '')
- deleted_images = data.get('deleted_images', [])
- new_images = data.get('new_images', [])
-
- # ---------- 参数校验 ----------
- if not dingdan_id:
- return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- if not isinstance(deleted_images, list):
- return Response({'code': 2, 'msg': 'deleted_images 必须为数组', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- if not isinstance(new_images, list):
- return Response({'code': 3, 'msg': 'new_images 必须为数组', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- if liuyan and len(liuyan) > 50:
- return Response({'code': 4, 'msg': '留言最多50个字', 'data': None},
- status=status.HTTP_400_BAD_REQUEST)
-
- # ---------- 用户身份验证 ----------
- current_user = request.user
- yonghuid = current_user.yonghuid
-
- # 检查是否为打手
- try:
- dashou_profile = current_user.dashou_profile # 假设User模型有related_name='dashou_profile'
- except UserDashou.DoesNotExist:
- return Response({'code': 5, 'msg': '用户不是打手身份', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
-
- # ---------- 订单查询及权限验证 ----------
- try:
- # 订单必须属于该打手,且状态为4(退款中)或8(结算中)
- dingdan = Dingdan.objects.get(
- dingdan_id=dingdan_id,
- jiedan_dashou_id=yonghuid,
- zhuangtai__in=[4, 8] # 只允许修改退款中或结算中的订单
- )
- except Dingdan.DoesNotExist:
- return Response({'code': 6, 'msg': '订单不存在或没有修改权限', 'data': None},
- status=status.HTTP_403_FORBIDDEN)
-
- # ---------- 执行修改(使用事务保证原子性) ----------
- try:
- with transaction.atomic():
- # 1. 处理删除的图片:从 Dashoutupian 表中删除记录
- if deleted_images:
- # 根据订单ID、打手ID和图片URL删除记录
- delete_count, _ = Dashoutupian.objects.filter(
- dingdan_id=dingdan_id,
- dashou=yonghuid,
- tupian__in=deleted_images
- ).delete()
- # 可选:记录删除数量
- logger.info(f"订单 {dingdan_id} 删除图片 {delete_count} 张")
-
- # 2. 处理新增的图片:插入新记录
- if new_images:
- # 批量创建
- new_records = []
- for url in new_images:
- new_records.append(Dashoutupian(
- dingdan_id=dingdan_id,
- dashou=yonghuid,
- tupian=url,
- create_time=timezone.now()
- ))
- Dashoutupian.objects.bulk_create(new_records)
- logger.info(f"订单 {dingdan_id} 新增图片 {len(new_records)} 张")
-
- # 3. 更新订单留言(如果发生变化)
- # 注意:即使留言为空字符串,也允许更新,表示清空留言
- if 'liuyan' in data: # 判断是否传了liuyan字段(可能为空字符串)
- dingdan.dashou_liuyan = liuyan
- dingdan.update_time = timezone.now()
- dingdan.save(update_fields=['dashou_liuyan', 'update_time'])
-
- # 全部成功,返回成功响应
- return Response({
- 'code': 0,
- 'msg': '修改成功',
- 'data': None
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- logger.error(f"修改订单失败: {str(e)}", exc_info=True)
- return Response({
- 'code': 7,
- 'msg': '服务器内部错误',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
-
-
-
-
-
-
-
-from django.core.exceptions import ObjectDoesNotExist
-from dingdan.tongzhi_tasks import dingdan_guangbo
-
-class ZxsjghdsView(APIView):
- """
- 商家更换打手接口
- 路径:POST /dingdan/zxsjghds
- 权限:仅限已认证商家(通过JWT)
- 功能:
- 1. 将原订单状态改为已退款(5),释放接单打手(若有)
- 2. 更新打手统计(退款量、状态、每日统计)
- 3. 写入退款记录
- 4. 若为跨平台订单,通知对方平台退款(必须成功)
- 5. 基于原订单生成新订单(状态直接为1),复制核心数据
- 6. 不操作商家余额、退款次数等(替换订单)
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- # ========== 1. 获取当前商家用户 ==========
- current_user = request.user
- try:
- yonghuid = current_user.yonghuid
- except AttributeError:
- return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- # ========== 2. 提取订单ID ==========
- dingdan_id = request.data.get('dingdan_id', '').strip()
- if not dingdan_id:
- return Response({'code': 2, 'msg': '订单ID不能为空'}, status=status.HTTP_400_BAD_REQUEST)
-
- # ========== 3. 查询订单并校验权限 ==========
- try:
- # 关联查询商家扩展表,确保当前商家是该订单的发布者
- shangjia_kuozhan = DingdanShangjia.objects.select_related('dingdan').get(
- dingdan__dingdan_id=dingdan_id,
- shangjia_id=yonghuid
- )
- except DingdanShangjia.DoesNotExist:
- return Response({'code': 3, 'msg': '订单不存在或您无权操作'}, status=status.HTTP_403_FORBIDDEN)
-
- old_order = shangjia_kuozhan.dingdan
-
- # ========== 4. 状态校验(仅允许进行中或结算中) ==========
- if old_order.zhuangtai not in [2, 8]:
- return Response({'code': 4, 'msg': '当前订单状态不允许更换打手'}, status=status.HTTP_400_BAD_REQUEST)
-
- # ========== 5. 收集订单关键数据(用于新订单复制) ==========
- # 这些字段将在退款处理后用于创建新订单
- jine = old_order.jine
- dashou_fencheng = old_order.dashou_fencheng
- leixing_id = old_order.leixing_id
- yaoqiuleixing = old_order.yaoqiuleixing
- huiyuan_id = old_order.huiyuan_id
- yongjin = old_order.yongjin
- jieshao = old_order.jieshao
- nicheng = old_order.nicheng
- beizhu = old_order.beizhu
- user1_id = old_order.user1_id
- tupian = old_order.tupian
- fadan_pingtai = old_order.fadan_pingtai
-
- # 跨平台相关
- is_cross = old_order.is_cross
- partner_club_id = old_order.partner_club_id
- partner_order_id = old_order.partner_order_id
- need_notify_partner = is_cross and partner_club_id and partner_order_id
-
- dashou_id = old_order.jiedan_dashou_id # 可能为空
-
- # ========== 6. 退款处理(事务块1:退款原订单 + 打手统计 + 跨平台通知) ==========
- try:
- with transaction.atomic():
- # 6.1 订单状态更新为已退款
- old_order.zhuangtai = 5
- old_order.save(update_fields=['zhuangtai'])
-
- # 6.2 释放打手
- if dashou_id:
- try:
- dashou_user = UserMain.objects.get(yonghuid=dashou_id)
- dashou_profile = dashou_user.dashou_profile
- dashou_profile.tuikuanliang = (dashou_profile.tuikuanliang or 0) + 1
- dashou_profile.zhuangtai = 1 # 空闲
- dashou_profile.save(update_fields=['tuikuanliang', 'zhuangtai'])
- except (UserMain.DoesNotExist, ObjectDoesNotExist):
- logger.warning(f"打手 {dashou_id} 不存在或扩展表缺失,跳过更新")
-
- # 6.3 更新退款记录
- Tuikuanjilu.objects.update_or_create(
- dingdan_id=dingdan_id,
- defaults={
- 'dashouid': dashou_id or '',
- 'qingqiuid': yonghuid, # 申请人为当前商家
- 'chuliid': yonghuid, # 处理人也是商家自己
- 'liyou': '商家更换打手',
- 'sqzhuangtai': 1, # 状态 1=成功
- 'jine': jine or 0,
- 'dashou_fencheng': dashou_fencheng or 0,
- 'jieshao': jieshao or '',
- 'beizhu': '更换打手系统自动退款',
- 'nicheng': nicheng or '',
- }
- )
-
- # 6.4 打手每日统计(退款行为)
- if dashou_id:
- try:
- from houtai.utils import update_dashou_daily_by_action
- update_dashou_daily_by_action(
- yonghuid=dashou_id,
- amount=dashou_fencheng,
- action=3 # 3=退款
- )
- except Exception as e:
- logger.error(f"更新打手每日统计失败: {e}")
- # 统计失败不应阻断主流程,但可记录
-
- # 6.5 跨平台退款通知
- if need_notify_partner:
- try:
- from peizhi.models import ClubConfig, Club
- my_club_config = ClubConfig.objects.filter(is_self=1).first()
- if not my_club_config:
- raise Exception("我方俱乐部配置不存在")
- self_club_id = my_club_config.club_id
- club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
- partner_domain = club_rel.partner_domain.rstrip('/')
-
- notify_url = f"{partner_domain}/houtai/dfddtk"
- payload = {
- 'order_id': partner_order_id,
- 'club_id': self_club_id,
- 'status': 5,
- 'reason': '商家更换打手退款'
- }
- resp = requests.post(notify_url, json=payload, timeout=5)
- if resp.status_code != 200:
- transaction.set_rollback(True)
- logger.error(f"对方平台接口HTTP错误: {resp.status_code}")
- return Response({'code': 500, 'msg': '通知对方平台失败'})
- resp_json = resp.json()
- if resp_json.get('code') != 0:
- transaction.set_rollback(True)
- logger.error(f"对方平台退款通知失败: {resp_json.get('msg')}")
- return Response({'code': 500, 'msg': f"对方平台通知失败: {resp_json.get('msg')}"})
-
- # 更新跨平台扩展数据
- from dingdan.models import CrossPlatformOrderData
- CrossPlatformOrderData.objects.update_or_create(
- dingdan_id=dingdan_id,
- defaults={
- 'partner_order_id': partner_order_id,
- 'partner_club_id': partner_club_id,
- 'partner_order_status': 5,
- }
- )
- except Exception as e:
- transaction.set_rollback(True)
- logger.error(f"通知对方平台异常: {e}")
- return Response({'code': 500, 'msg': f'通知对方平台失败: {str(e)}'})
-
- except Exception as e:
- logger.error(f"退款处理事务失败: {e}", exc_info=True)
- return Response({'code': 500, 'msg': '退款处理失败'})
-
- # ========== 7. 生成新订单(事务块2) ==========
- try:
- with transaction.atomic():
- # 7.1 生成新的订单ID
- def generate_dingdan_id():
- timestamp = int(time.time() * 1000)
- process_id = os.getpid() % 1000
- random_num = random.randint(1000, 9999)
- return f"SJ{timestamp}{process_id:03d}{random_num}"
-
- new_dingdan_id = generate_dingdan_id()
- # 确保唯一
- while Dingdan.objects.filter(dingdan_id=new_dingdan_id).exists():
- new_dingdan_id = generate_dingdan_id()
-
- # 7.2 创建新订单(复制数据,状态为 1)
- new_order = Dingdan.objects.create(
- dingdan_id=new_dingdan_id,
- zhuangtai=1, # 已下单
- fadan_pingtai=fadan_pingtai,
- jine=jine,
- dashou_fencheng=dashou_fencheng,
- leixing_id=leixing_id,
- yaoqiuleixing=yaoqiuleixing,
- huiyuan_id=huiyuan_id,
- yongjin=yongjin,
- jieshao=jieshao,
- nicheng=nicheng,
- beizhu=beizhu,
- user1_id=user1_id, # 商家标识
- tupian=tupian,
- # 不继承:zhiding_id, jiedan_dashou_id, dashou_liuyan 等
- )
-
- # 7.3 创建商家扩展表(关联新订单)
- DingdanShangjia.objects.create(
- dingdan=new_order,
- shangjia_id=yonghuid,
- sjnicheng=shangjia_kuozhan.sjnicheng or '',
- )
-
- # 7.4 异步通知打手(广播新订单)
- try:
- order_info = {
- 'dingdan_id': new_dingdan_id,
- 'game_type': self._get_game_type_name(leixing_id),
- 'amount': str(jine),
- 'order_desc': (jieshao or '')[:50],
- }
- dingdan_guangbo.delay(order_info)
- except Exception as e:
- logger.error(f"广播通知失败: {e}")
-
- # 7.5 跨平台同步(如果适用)
- try:
- sync_order_to_partners(new_dingdan_id)
- except Exception as e:
- logger.error(f"跨平台同步失败: {e}")
-
- # 注意:此处不更新商家余额、发布量等统计,因为是替换订单
-
- logger.info(f"商家{yonghuid}更换打手成功,旧订单{dingdan_id}已退款,新订单{new_dingdan_id}")
-
- return Response({
- 'code': 0,
- 'msg': '更换成功',
- 'data': {
- 'xin_dingdan_id': new_dingdan_id,
- }
- })
-
- except Exception as e:
- logger.error(f"生成新订单事务失败: {e}", exc_info=True)
- return Response({'code': 500, 'msg': '生成新订单失败'})
-
- def _get_game_type_name(self, leixing_id):
- """根据类型ID获取游戏类型名称"""
- try:
- gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
- return gt.jieshao if gt else '游戏订单'
- except Exception:
- return '游戏订单'
-
-
-
-# ===================== 视图:商家罚款申请 =====================
-# 文件路径:dingdan/views.py 或单独文件,根据项目结构放置
-
-
-
-
-class ShangjiaFakuanApplyView(APIView):
- """
- 商家罚款申请接口
- 路径:POST /dingdan/sjfksq
- 权限:JWT认证的商家用户
- 参数:
- dingdan_id - 订单ID (必填)
- chufa_liyou - 罚款原因 (必填)
- fakuanjine - 罚款金额,元 (必填)
- yingxiang_qiangdan - 是否影响抢单 1是 0否 (必填)
- 说明:
- 1. 自动从订单中获取被打手ID,防止商家篡改目标。
- 2. 同一订单同一打手不允许重复罚款。
- 3. 跨平台订单若为我方派单,则通知对方平台处罚;否则本地生成罚单。
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- # ========== 1. 身份验证 ==========
- current_user = request.user
- try:
- shangjia_id = current_user.yonghuid # 商家的用户ID
- except AttributeError:
- return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- # ========== 2. 参数提取与校验 ==========
- dingdan_id = request.data.get('dingdan_id', '').strip()
- chufa_liyou = request.data.get('chufa_liyou', '').strip()
- fakuanjine = request.data.get('fakuanjine', 0)
- yingxiang_qiangdan = int(request.data.get('yingxiang_qiangdan', 1))
-
- if not dingdan_id or not chufa_liyou or not fakuanjine:
- return Response({'code': 2, 'msg': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST)
-
- try:
- fakuanjine = float(fakuanjine)
- if fakuanjine <= 0:
- raise ValueError
- except (ValueError, TypeError):
- return Response({'code': 2, 'msg': '罚款金额格式错误'}, status=status.HTTP_400_BAD_REQUEST)
-
- # ========== 3. 获取订单,校验归属商家 ==========
- try:
- # 关联查询商家扩展表,确保订单属于当前商家
- shangjia_kuozhan = DingdanShangjia.objects.select_related('dingdan').get(
- dingdan__dingdan_id=dingdan_id,
- shangjia_id=shangjia_id
- )
- order = shangjia_kuozhan.dingdan
- except DingdanShangjia.DoesNotExist:
- return Response({'code': 3, 'msg': '订单不存在或您无权操作'}, status=status.HTTP_403_FORBIDDEN)
-
- # ========== 4. 获取被打手ID ==========
- dashou_id = order.jiedan_dashou_id
- if not dashou_id:
- return Response({'code': 4, 'msg': '该订单暂无接单打手,无法罚款'}, status=status.HTTP_400_BAD_REQUEST)
-
- # ========== 5. 重复罚款检查 ==========
- if Fadan.objects.filter(beichufa_id=dashou_id, guanliandingdan_id=dingdan_id).exists():
- return Response({'code': 5, 'msg': '该打手在此订单已被罚款,不能重复申请'}, status=status.HTTP_400_BAD_REQUEST)
-
- # ========== 6. 跨平台信息 ==========
- is_cross = order.is_cross
- partner_order_id = order.partner_order_id or ''
- partner_club_id = order.partner_club_id or ''
- dispatch_type = order.dispatch_type or 0 # 1=我方派单,2=对方派单
-
- # 是否需要通知对方平台:我方派单且存在对方订单ID
- need_notify_partner = is_cross and partner_order_id and dispatch_type == 1
-
- # ========== 7. 本地创建罚单(可重用) ==========
- def create_local_fadan():
- Fadan.objects.create(
- beichufa_id=dashou_id,
- shenqing_chufa=shangjia_id, # 申请人为当前商家
- shenfen=1, # 被打者身份:1=打手
- chufaliyou=chufa_liyou,
- fakuanjine=fakuanjine,
- guanliandingdan_id=dingdan_id,
- zhuangtai=1, # 待缴纳
- yingxiang_qiangdan=yingxiang_qiangdan,
- shenqingren_shenfen=5, # 申请人是商家
- partner_club_id='',
- )
-
- # ========== 8. 本地处罚分支 ==========
- if not need_notify_partner:
- with transaction.atomic():
- create_local_fadan()
- logger.info(f"商家{shangjia_id}对订单{dingdan_id}打手{dashou_id}罚款成功,金额{fakuanjine}")
- return Response({'code': 0, 'msg': '罚款申请已提交'})
-
- # ========== 9. 跨平台通知分支 ==========
- try:
- # 9.1 获取我方俱乐部ID
- my_club = ClubConfig.objects.filter(is_self=1).first()
- if not my_club:
- return Response({'code': 500, 'msg': '系统配置错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
- self_club_id = my_club.club_id
-
- # 9.2 获取对方平台域名
- club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
- partner_domain = club_rel.partner_domain.rstrip('/')
- except ObjectDoesNotExist:
- return Response({'code': 500, 'msg': '对方平台信息未配置'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 9.3 构造通知参数
- notify_url = f"{partner_domain}/houtai/kffkdssq_notify" # 与后台接口保持一致
- payload = {
- 'order_id': partner_order_id,
- 'club_id': self_club_id,
- 'chufaliyou': chufa_liyou,
- 'fakuanjine': fakuanjine,
- 'yingxiang_qiangdan': yingxiang_qiangdan,
- 'shenqingren_shenfen': 5, # 商家身份
- 'operator': shangjia_id, # 操作人ID
- }
-
- # 9.4 带重试的通知
- timeout = 3
- retries = 2
- success = False
- for attempt in range(retries):
- try:
- resp = requests.post(notify_url, json=payload, timeout=timeout)
- if resp.status_code == 200:
- resp_data = resp.json()
- if resp_data.get('code') == 0:
- success = True
- break
- else:
- logger.warning(f"对方平台罚款通知失败,响应: {resp_data}")
- else:
- logger.warning(f"对方平台HTTP错误: {resp.status_code}")
- except Exception as e:
- logger.warning(f"通知对方平台异常,第{attempt+1}次尝试: {e}")
- continue
-
- if not success:
- return Response({'code': 500, 'msg': '通知对方平台失败,请稍后重试'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 9.5 通知成功后,本地也创建罚单(标记跨平台)
- with transaction.atomic():
- Fadan.objects.create(
- beichufa_id=dashou_id,
- shenqing_chufa=shangjia_id,
- shenfen=1,
- chufaliyou=chufa_liyou,
- fakuanjine=fakuanjine,
- guanliandingdan_id=dingdan_id,
- zhuangtai=1,
- yingxiang_qiangdan=yingxiang_qiangdan,
- shenqingren_shenfen=5,
- partner_club_id=partner_club_id, # 记录对方俱乐部ID
- )
-
- logger.info(f"商家{shangjia_id}跨平台罚款成功: {dingdan_id}")
- return Response({'code': 0, 'msg': '罚款已提交至对方平台'})
-
-
-
-
-
-
-
-
-
-
-class ShangjiaFakuaiXiugaiView(APIView):
- """
- 商家端统一修改接口:罚款修改/撤销/再次申请,订单退款撤销/修改理由
- URL: /dingdan/sjfkxgycx
- """
- permission_classes = [IsAuthenticated]
-
- def post(self, request):
- user = request.user
- # 验证商家身份
- try:
- shangjia = user.shop_profile
- except UserShangjia.DoesNotExist:
- return Response({'code': 403, 'msg': '您不是商家'}, status=status.HTTP_403_FORBIDDEN)
- if shangjia.zhuangtai != 1:
- return Response({'code': 403, 'msg': '商家状态异常'}, status=status.HTTP_403_FORBIDDEN)
-
- operation = request.data.get('operation')
- dingdan_id = request.data.get('dingdan_id')
- if not dingdan_id:
- return Response({'code': 400, 'msg': '缺少订单ID'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 验证订单属于该商家
- try:
- dingdan = Dingdan.objects.select_related('shangjia_kuozhan').get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
- if not hasattr(dingdan, 'shangjia_kuozhan') or dingdan.shangjia_kuozhan.shangjia_id != user.yonghuid:
- return Response({'code': 403, 'msg': '无权操作此订单'}, status=status.HTTP_403_FORBIDDEN)
-
- # 处理订单相关操作
- if operation == 'cancel_refund':
- return self._cancel_refund(dingdan, request.data.get('liyou', ''))
- elif operation == 'modify_refund_reason':
- return self._modify_refund_reason(dingdan, request.data.get('liyou', ''))
-
- # 处理罚款相关操作
- fadan_id = request.data.get('fadan_id')
- if not fadan_id:
- return Response({'code': 400, 'msg': '缺少罚单ID'}, status=status.HTTP_400_BAD_REQUEST)
- try:
- fadan = Fadan.objects.get(id=fadan_id)
- except Fadan.DoesNotExist:
- return Response({'code': 404, 'msg': '罚单不存在'}, status=status.HTTP_404_NOT_FOUND)
- if fadan.guanliandingdan_id != dingdan_id:
- return Response({'code': 403, 'msg': '罚单与订单不匹配'}, status=status.HTTP_403_FORBIDDEN)
-
- if operation == 'modify_penalty':
- return self._modify_penalty(fadan, request.data.get('liyou', ''), request.data.get('jine'))
- elif operation == 'cancel_penalty':
- return self._cancel_penalty(fadan, request.data.get('liyou', ''))
- elif operation == 'resubmit_penalty':
- return self._resubmit_penalty(fadan, request.data.get('liyou', ''), request.data.get('jine'))
- else:
- return Response({'code': 400, 'msg': '无效的操作类型'}, status=status.HTTP_400_BAD_REQUEST)
-
- def _cancel_refund(self, dingdan, liyou):
- """撤销退款申请(状态4 → 状态8,清空退款理由)"""
- if dingdan.zhuangtai != 4:
- return Response({'code': 400, 'msg': '只有退款中的订单可以撤销退款申请'})
- with transaction.atomic():
- dingdan.zhuangtai = 8
- dingdan.tkly = '' # 清空退款理由
- dingdan.save()
- # 记录撤销理由可存入日志(如需要可另建日志表,此处简单处理)
- logger.info(f"商家撤销退款订单 {dingdan.dingdan_id},理由: {liyou}")
- return Response({'code': 0, 'msg': '已撤销退款申请'})
-
- def _modify_refund_reason(self, dingdan, liyou):
- """修改退款理由(仅状态4时)"""
- if dingdan.zhuangtai != 4:
- return Response({'code': 400, 'msg': '只有退款中的订单可以修改退款理由'})
- if not liyou.strip():
- return Response({'code': 400, 'msg': '退款理由不能为空'})
- dingdan.tkly = liyou[:500]
- dingdan.save()
- return Response({'code': 0, 'msg': '退款理由修改成功'})
-
- def _modify_penalty(self, fadan, liyou, jine_str):
- """修改罚款(仅状态1待缴纳 或 3申诉中)"""
- if fadan.zhuangtai not in (1, 3):
- return Response({'code': 400, 'msg': '当前罚单状态不可修改'})
- if not liyou.strip():
- return Response({'code': 400, 'msg': '罚款理由不能为空'})
- try:
- jine = Decimal(jine_str)
- if jine <= 0:
- raise ValueError
- except:
- return Response({'code': 400, 'msg': '罚款金额必须为正数'})
- with transaction.atomic():
- fadan.chufaliyou = liyou[:500]
- fadan.fakuanjine = jine
- # 如果之前是驳回状态修改,需要重置状态为待缴纳?不,修改不应改变状态
- fadan.save()
- return Response({'code': 0, 'msg': '罚款修改成功'})
-
- def _cancel_penalty(self, fadan, liyou):
- """撤销罚款(状态1或3 → 状态4 已驳回)"""
- if fadan.zhuangtai not in (1, 3):
- return Response({'code': 400, 'msg': '当前罚单状态不可撤销'})
- if not liyou.strip():
- return Response({'code': 400, 'msg': '驳回理由不能为空'})
- with transaction.atomic():
- fadan.zhuangtai = 4 # 已驳回
- fadan.bohuiliyou = liyou[:500]
- fadan.save()
- return Response({'code': 0, 'msg': '罚款已撤销(驳回)'})
-
- def _resubmit_penalty(self, fadan, liyou, jine_str):
- """再次申请罚款(状态4 → 状态1,修改理由和金额)"""
- if fadan.zhuangtai != 4:
- return Response({'code': 400, 'msg': '只有已驳回的罚单可再次申请'})
- if not liyou.strip():
- return Response({'code': 400, 'msg': '罚款理由不能为空'})
- try:
- jine = Decimal(jine_str)
- if jine <= 0:
- raise ValueError
- except:
- return Response({'code': 400, 'msg': '罚款金额必须为正数'})
- with transaction.atomic():
- fadan.zhuangtai = 1 # 待缴纳
- fadan.chufaliyou = liyou[:500]
- fadan.fakuanjine = jine
- fadan.bohuiliyou = '' # 清空驳回理由
- fadan.save()
+import hmac
+import os
+from dingdan.utils import sync_order_to_partners
+from django.db.models import F
+from django.shortcuts import render
+from rest_framework.permissions import AllowAny # 新增这行
+from rest_framework.throttling import AnonRateThrottle
+from dingdan.utils import sync_order_to_partners
+# dingdan/views.py
+import traceback
+import time
+import random
+import hashlib
+import xml.etree.ElementTree as ET
+import requests
+import uuid
+from decimal import Decimal
+from django.db import transaction
+from django.conf import settings
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import permissions, status
+from tencentcloud.common.profile.client_profile import ClientProfile
+from tencentcloud.common.profile.http_profile import HttpProfile
+
+from shangpin.models import Shangpin, ShangpinLeixing, Huiyuangoumai
+from dingdan.models import Dingdan, DingdanPingtai, Lilubiao, Pingfen, DingdanShangjia, Chufajilu, Liaotian, Chufatupian
+from utils.weixin_broadcast import WeixinBroadcastSender
+from yonghu.models import UserMain, UserDashou, UserShangjia
+
+from shangdian.utils import calculate_pingtai_and_dianpu_shouyi,validate_shangpin_and_dianpu
+from shangpin.utils import update_shangpin_daily_stat
+from shangdian.utils import update_dianpu_daily_stat # 注意:您之前写的店铺统计方法名是 update_dianpu_daily_stat
+
+from houtai.utils import update_dashou_daily_by_action
+
+# dingdan/views.py (CreateOrderView)
+
+
+# dingdan/views.py (WechatPayNotifyView)
+
+import threading
+import hashlib
+import xml.etree.ElementTree as ET
+from decimal import Decimal
+from django.db import transaction, models
+from django.http import HttpResponse
+from rest_framework.views import APIView
+from rest_framework.permissions import AllowAny
+from rest_framework.throttling import AnonRateThrottle
+import logging
+from rest_framework import permissions
+
+from shangpin.models import Shangpin
+
+from yonghu.models import UserMain, UserBoss
+
+
+from django.db import transaction
+from django.http import HttpResponse
+from rest_framework.views import APIView
+
+from rest_framework.throttling import AnonRateThrottle
+from rest_framework.permissions import AllowAny
+
+from peizhi.models import Szjilu
+
+logger = logging.getLogger(__name__)
+
+
+# dingdan/views.py
+import threading
+import hashlib
+import logging
+import traceback
+import xml.etree.ElementTree as ET
+from decimal import Decimal
+from django.db import transaction, models
+from django.http import HttpResponse
+from django.conf import settings
+from rest_framework.views import APIView
+from rest_framework.permissions import AllowAny
+from rest_framework.throttling import AnonRateThrottle
+
+# 假设这些模型已正确导入
+from dingdan.models import Dingdan, DingdanPingtai
+from shangpin.models import Shangpin
+from yonghu.models import UserMain, UserBoss
+from peizhi.models import Szjilu
+
+
+logger = logging.getLogger(__name__)
+
+
+# dingdan/views.py (PaymentFailView)
+
+class PaymentFailView(APIView):
+ """
+ 前端支付失败接口 - 处理用户主动取消或支付失败
+ 只允许删除状态为9(未支付)的订单
+ """
+ permission_classes = [permissions.IsAuthenticated]
+
+ def post(self, request):
+ try:
+ dingdanid = request.data.get('dingdanid')
+ if not dingdanid:
+ return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None})
+
+ with transaction.atomic():
+ # 锁定订单防止并发
+ dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=dingdanid).first()
+ if not dingdan:
+ return Response({'code': 2, 'msg': '订单不存在', 'data': None})
+
+ if dingdan.user1_id != f"Boss{request.user.yonghuid}":
+ return Response({'code': 3, 'msg': '无权操作此订单', 'data': None})
+
+ # 只允许删除未支付订单(状态9)
+ if dingdan.zhuangtai != 9:
+ return Response({'code': 3, 'msg': '只能删除未支付的订单', 'data': None})
+
+ # 删除扩展表和主表
+ DingdanPingtai.objects.filter(dingdan=dingdan).delete()
+ dingdan.delete()
+
+ return Response({'code': 0, 'msg': '订单已删除', 'data': None})
+
+ except Exception as e:
+ return Response({'code': 99, 'msg': f'删除订单失败: {str(e)}', 'data': None})
+
+
+import string
+import requests
+import xmltodict
+import logging
+from decimal import Decimal
+from django.db import transaction, models
+from django.core.cache import cache
+from django.conf import settings
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import permissions, status
+from rest_framework.parsers import JSONParser
+
+from yonghu.models import UserMain, UserBoss
+from shangpin.models import Shangpin
+from dingdan.models import Dingdan, DingdanPingtai
+from peizhi.models import Szjilu
+
+logger = logging.getLogger(__name__)
+
+
+from dingdan.utils import update_daily_income
+import logging
+import traceback
+
+logger = logging.getLogger(__name__)
+
+class WechatPayNotifyView(APIView):
+ """微信支付回调接口 - 接收微信官方支付结果通知(含详细调试日志)"""
+ throttle_classes = [AnonRateThrottle]
+ permission_classes = [AllowAny]
+
+ def post(self, request):
+ # 记录原始请求数据(调试用)
+ xml_data = request.body.decode('utf-8')
+
+ # 最外层异常捕获,确保任何错误都能被记录并返回适当响应
+ try:
+ # 1. 解析 XML
+ try:
+ root = ET.fromstring(xml_data)
+ except ET.ParseError as e:
+ logger.error(f"XML解析失败: {e}")
+ return self._gen_response_xml('FAIL', 'XML解析失败')
+
+ # 2. 获取关键参数
+ return_code = root.find('return_code').text if root.find('return_code') is not None else ''
+ result_code = root.find('result_code').text if root.find('result_code') is not None else ''
+ out_trade_no = root.find('out_trade_no').text if root.find('out_trade_no') is not None else ''
+ transaction_id = root.find('transaction_id').text if root.find('transaction_id') is not None else ''
+
+ # 3. 签名验证
+ if not self._verify_signature(xml_data):
+ logger.warning("签名验证失败")
+ return self._gen_response_xml('FAIL', '签名验证失败')
+
+ # 4. 根据支付结果处理
+ if return_code == 'SUCCESS' and result_code == 'SUCCESS':
+ self._handle_success(out_trade_no, transaction_id, root)
+ else:
+ logger.info(f"支付失败或异常,return_code={return_code}, result_code={result_code}")
+ self._handle_failure(out_trade_no)
+
+ # 返回微信要求的成功响应(必须返回SUCCESS,即使内部处理失败也要返回,否则微信会重复通知)
+ return self._gen_response_xml('SUCCESS', 'OK')
+
+ except Exception as e:
+ # 捕获任何未处理的异常,记录完整堆栈
+ logger.error(f"回调处理发生未捕获异常: {str(e)}")
+ logger.error(traceback.format_exc())
+ return self._gen_response_xml('FAIL', f'服务器内部错误: {str(e)}')
+
+ def _verify_signature(self, xml_data):
+ """微信支付签名验证(MD5)"""
+ try:
+ root = ET.fromstring(xml_data)
+ sign = root.find('sign').text
+ # 构建待签名字符串(排除sign字段)
+ data = {child.tag: child.text for child in root if child.tag != 'sign'}
+ # 按key排序
+ sorted_keys = sorted(data.keys())
+ stringA = '&'.join([f"{k}={data[k]}" for k in sorted_keys])
+ stringSignTemp = f"{stringA}&key={settings.WEIXIN_SHANGHUMIYAO}"
+ calc_sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
+ logger.debug(f"计算签名: {calc_sign}, 微信签名: {sign}")
+ return calc_sign == sign
+ except Exception as e:
+ logger.error(f"签名验证过程异常: {e}")
+ logger.error(traceback.format_exc())
+ return False
+
+ def _gen_response_xml(self, return_code, return_msg):
+ """生成返回给微信的XML响应"""
+ xml = f"""
+
+
+"""
+ logger.info(f"返回给微信的响应: {xml}")
+ return HttpResponse(xml, content_type='application/xml')
+
+ @transaction.atomic
+ def _handle_success(self, out_trade_no, transaction_id, root):
+ """处理支付成功"""
+ logger.info(f"进入 _handle_success, out_trade_no={out_trade_no}, transaction_id={transaction_id}")
+
+ # 锁定订单防止并发
+ try:
+ dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=out_trade_no).first()
+ except Exception as e:
+ logger.error(f"查询订单异常: {e}")
+ raise
+
+ if not dingdan:
+ logger.error(f"订单不存在: {out_trade_no}")
+ return
+
+ logger.info(f"订单当前状态: {dingdan.zhuangtai}")
+
+ # 幂等处理:只有状态为9(未支付)才处理,已处理则跳过
+ if dingdan.zhuangtai != 9:
+ logger.info(f"订单 {out_trade_no} 已处理过,状态={dingdan.zhuangtai},跳过")
+ return
+
+ # 保存微信交易号
+ if transaction_id:
+ dingdan.wechat_transaction_id = transaction_id
+ logger.info(f"保存微信交易号: {transaction_id}")
+ else:
+ logger.warning("微信未返回transaction_id")
+
+ # 更新订单状态
+ if dingdan.zhiding_id and dingdan.zhiding_id.strip():
+ dingdan.zhuangtai = 7
+ logger.info("指定单,状态设为7")
+ else:
+ dingdan.zhuangtai = 1
+ logger.info("普通单,状态设为1")
+
+ dingdan.save()
+
+ logger.info(f"订单状态已更新为 {dingdan.zhuangtai}")
+
+ # 删除同一用户同一商品的其他未支付订单(改为逻辑删除,状态25)
+ try:
+ abandoned_count = Dingdan.objects.filter(
+ user1_id=dingdan.user1_id,
+ shangpin_id=dingdan.shangpin_id,
+ zhuangtai=9
+ ).exclude(dingdan_id=out_trade_no).update(zhuangtai=25)
+ logger.info(f"将 {abandoned_count} 个未支付订单标记为已废弃(25)")
+ except Exception as e:
+ logger.error(f"标记其他未支付订单异常: {e}")
+
+ # 更新老板扩展表
+ try:
+ laoban_id = None
+ if hasattr(dingdan, 'pingtai_kuozhan'):
+ laoban_id = dingdan.pingtai_kuozhan.laoban_id
+ if laoban_id:
+ user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
+ if user_main and hasattr(user_main, 'boss_profile'):
+ boss = user_main.boss_profile
+ boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
+ boss.alldingdan = (boss.alldingdan or 0) + 1
+ boss.save()
+ logger.info(f"更新老板 {laoban_id} 数据成功")
+ except Exception as e:
+ logger.error(f"更新老板数据异常: {e}")
+
+ # 更新商品库存
+ try:
+ if dingdan.shangpin_id:
+ Shangpin.objects.filter(id=dingdan.shangpin_id).update(
+ kucun=F('kucun') - 1
+ )
+ logger.info(f"商品 {dingdan.shangpin_id} 库存减1")
+ except Exception as e:
+ logger.error(f"更新商品库存异常: {e}")
+
+
+ # ========== 新增:商品和店铺每日统计 ==========
+ try:
+ # 商品每日统计(只要有商品ID就执行)
+ if dingdan.shangpin_id:
+ # 从扩展表获取已计算好的收益值(下单时已存储)
+ pingtai_shouyi = Decimal('0.00')
+ dianpu_shouyi = Decimal('0.00')
+ if hasattr(dingdan, 'pingtai_kuozhan'):
+ pingtai_shouyi = dingdan.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
+ dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
+
+ update_shangpin_daily_stat(
+ shangpin_id=dingdan.shangpin_id,
+ caozuo_leixing=1, # 1 = 下单
+ dingdan_jiage=dingdan.jine,
+ pingtai_shouyi=pingtai_shouyi,
+ dianpu_zongshouyi=dianpu_shouyi
+ )
+ logger.info(f"商品统计更新完成,商品ID: {dingdan.shangpin_id}")
+
+ # 店铺每日统计(仅当存在店铺ID时执行)
+ dianpu_id = None
+ if hasattr(dingdan, 'pingtai_kuozhan'):
+ dianpu_id = dingdan.pingtai_kuozhan.dianpu_id
+ if dianpu_id:
+ dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
+ update_dianpu_daily_stat(
+ dianpu_id=dianpu_id,
+ caozuo_leixing=1, # 1 = 下单
+ dingdan_jiage=dingdan.jine,
+ dianpu_shouyi=dianpu_shouyi
+ )
+ logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}")
+ except Exception as e:
+ logger.error(f"商品/店铺统计更新失败: {e}")
+ logger.error(traceback.format_exc())
+ # 统计失败不影响主流程
+ # =============================================
+
+
+ # 更新收支记录(总累计)
+ self._update_szjilu(dingdan.jine)
+
+ # 更新每日收入统计
+ update_daily_income(dingdan.jine)
+
+ # 跨平台同步
+ sync_order_to_partners(dingdan.dingdan_id)
+
+ #🟢【7. 新增】支付成功后的广播通知(异步,不阻塞主流程)
+ from dingdan.tongzhi_tasks import dingdan_guangbo
+
+ # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用)
+ order_info = {
+ 'dingdan_id': dingdan.dingdan_id,
+ 'game_type': self._get_game_type_name(dingdan.leixing_id),
+ 'amount': str(dingdan.jine),
+ 'order_desc': (dingdan.jieshao or '')[:50],
+ }
+ dingdan_guangbo.delay(order_info) # 瞬间返回,不阻塞请求
+
+ # 异步广播通知
+ #self._broadcast_async(dingdan)
+
+ logger.info(f"订单 {out_trade_no} 支付成功处理完成")
+
+ @transaction.atomic
+ def _handle_failure(self, out_trade_no):
+ """处理支付失败"""
+ logger.info(f"进入 _handle_failure, out_trade_no={out_trade_no}")
+
+ try:
+ dingdan = Dingdan.objects.select_for_update().filter(dingdan_id=out_trade_no).first()
+ if not dingdan:
+ logger.warning(f"订单不存在: {out_trade_no}")
+ return
+
+ logger.info(f"订单当前状态: {dingdan.zhuangtai}")
+
+ # 仅当订单状态为9(未支付)时才标记失败,不直接删除
+ if dingdan.zhuangtai == 9:
+ dingdan.zhuangtai = 14 # 自定义支付失败状态
+ dingdan.save()
+ logger.info(f"订单 {out_trade_no} 支付失败,标记为14")
+ else:
+ logger.info(f"订单 {out_trade_no} 状态非9,不处理")
+ except Exception as e:
+ logger.error(f"处理支付失败异常: {e}")
+ logger.error(traceback.format_exc())
+
+ def _update_szjilu(self, jine):
+ """更新收支记录表(ID=1的记录)"""
+ try:
+ jine_decimal = Decimal(str(jine))
+ with transaction.atomic():
+ szjilu, created = Szjilu.objects.select_for_update().get_or_create(
+ id=1,
+ defaults={
+ 'zongsy': Decimal('0.00'),
+ 'zongls': Decimal('0.00'),
+ 'zongzc': Decimal('0.00'),
+ 'jrzc': Decimal('0.00'),
+ 'jrls': Decimal('0.00')
+ }
+ )
+ if created:
+ logger.info("创建ID=1的收支记录")
+ szjilu.zongsy += jine_decimal
+ szjilu.zongls += jine_decimal
+ szjilu.jrls += jine_decimal
+ szjilu.save()
+ logger.info(f"收支记录更新成功,增加金额: {jine}")
+ except Exception as e:
+ logger.error(f"更新收支记录失败: {e}")
+
+ def _broadcast_async(self, dingdan):
+ """异步广播通知"""
+ try:
+ def task():
+ try:
+ from utils.weixin_broadcast import WeixinBroadcastSender # 请根据实际路径修改
+ order_info = {
+ 'dingdan_id': dingdan.dingdan_id,
+ 'game_type': self._get_game_type_name(dingdan.leixing_id),
+ 'amount': str(dingdan.jine),
+ 'order_desc': (dingdan.jieshao or '')[:50],
+ }
+ logger.info(f"准备广播订单: {order_info}")
+ sender = WeixinBroadcastSender()
+ result = sender.broadcast_order(order_info)
+ if result.get('success'):
+ logger.info(f"订单广播完成: {result.get('msg')}")
+ else:
+ logger.warning(f"订单广播异常: {result.get('msg')}")
+ except Exception as e:
+ logger.error(f"广播任务异常: {e}")
+ logger.error(traceback.format_exc())
+
+ thread = threading.Thread(target=task)
+ thread.daemon = True
+ thread.start()
+ logger.info("广播线程已启动")
+ except Exception as e:
+ logger.error(f"启动广播线程异常: {e}")
+
+ def _get_game_type_name(self, leixing_id):
+ try:
+ from shangpin.models import ShangpinLeixing
+ gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
+ return gt.jieshao if gt else '游戏订单'
+ except Exception as e:
+ logger.error(f"获取游戏类型名称异常: {e}")
+ return '游戏订单'
+
+
+from django.core.cache import cache
+class PaymentVerifyView(APIView):
+ """
+ 订单支付成功复查接口 - 前端支付成功后调用,确认订单状态
+ 当订单状态为9时,主动查询微信支付状态,若已支付则更新本地状态
+ 需要token认证
+ """
+ permission_classes = [permissions.IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ try:
+ # ---------- 1. 获取参数 ----------
+ dingdanid = request.data.get('dingdanid')
+ if not dingdanid:
+ return Response({'code': 1, 'msg': '订单ID不能为空'})
+
+ # ---------- 2. 查询订单 ----------
+ dingdan = Dingdan.objects.filter(dingdan_id=dingdanid).first()
+ if not dingdan:
+ return Response({'code': 2, 'msg': '订单不存在'})
+
+ # ---------- 3. 验证订单归属(安全) ----------
+ current_user = request.user
+ user_identifier = f"Boss{current_user.yonghuid}"
+ if dingdan.user1_id != user_identifier:
+ logger.warning(f"用户 {current_user.yonghuid} 尝试复查他人订单 {dingdanid}")
+ return Response({'code': 403, 'msg': '无权访问'})
+
+ # ---------- 4. 如果订单状态已是1/7,直接返回成功 ----------
+ if dingdan.zhuangtai in [1, 7]:
+ return Response({
+ 'code': 0,
+ 'msg': '订单状态正常',
+ 'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai}
+ })
+
+ # ---------- 5. 如果状态为9,则主动查询微信支付状态 ----------
+ if dingdan.zhuangtai == 9:
+ # 使用分布式锁防止并发处理同一订单
+ lock_key = f"payment_verify_lock_{dingdanid}"
+ if not cache.add(lock_key, "locked", timeout=10):
+ # 已有其他进程在处理,让前端稍后重试
+ return Response({'code': 1, 'msg': '订单正在处理中,请稍后'})
+
+ try:
+ # 调用微信订单查询接口
+ trade_state, transaction_id = self._query_wechat_payment(dingdanid)
+
+ if trade_state == 'SUCCESS':
+ # 支付成功,执行本地更新
+ self._process_payment_success(dingdan, transaction_id, current_user.yonghuid)
+ # 重新获取订单以获取最新状态
+ dingdan.refresh_from_db()
+ return Response({
+ 'code': 0,
+ 'msg': '支付成功',
+ 'data': {'dingdanid': dingdanid, 'zhuangtai': dingdan.zhuangtai}
+ })
+ elif trade_state in ['NOTPAY', 'USERPAYING']:
+ # 用户尚未支付或支付中
+ return Response({'code': 1, 'msg': '支付中', 'data': {'zhuangtai': 9}})
+ else:
+ # 其他状态:CLOSED, REVOKED, PAYERROR 等,视为支付失败
+ return Response({'code': 2, 'msg': '支付失败'})
+
+ except Exception as e:
+ logger.error(f"查询微信支付异常: {e}", exc_info=True)
+ return Response({'code': 1, 'msg': '查询失败,请稍后重试'})
+ finally:
+ cache.delete(lock_key)
+ else:
+ # ---------- 6. 其他异常状态 ----------
+ status_msg = {
+ 2: '订单已取消',
+ 3: '订单已完成',
+ 4: '退款审核中',
+ 5: '已退款',
+ 6: '退款失败',
+ 8: '结算中',
+ 0: '订单已作废',
+ }.get(dingdan.zhuangtai, '订单状态异常')
+ return Response({'code': 3, 'msg': status_msg, 'data': {'zhuangtai': dingdan.zhuangtai}})
+
+ except Exception as e:
+ logger.error(f"订单复查接口异常: {e}", exc_info=True)
+ return Response({'code': 99, 'msg': f'系统错误: {str(e)}'})
+
+ def _process_payment_success(self, dingdan, transaction_id, operator_id):
+ """
+ 支付成功后的原子操作(与微信回调逻辑完全一致)
+ 在事务内完成所有状态更新和数据一致性操作
+ """
+ with transaction.atomic():
+ # 重新锁定订单,防止并发更新
+ dingdan = Dingdan.objects.select_for_update().get(pk=dingdan.pk)
+ if dingdan.zhuangtai != 9:
+ # 订单状态已被其他进程更新,直接返回(幂等)
+ return
+
+ # 更新订单状态
+ dingdan.zhuangtai = 7 if dingdan.zhiding_id and dingdan.zhiding_id.strip() else 1
+ dingdan.wechat_transaction_id = transaction_id
+ dingdan.clkf = operator_id # 可选:记录操作人ID
+ dingdan.save()
+
+ # 删除同一用户同一商品的其他未支付订单(逻辑删除,状态25)
+ Dingdan.objects.filter(
+ user1_id=dingdan.user1_id,
+ shangpin_id=dingdan.shangpin_id,
+ zhuangtai=9
+ ).exclude(dingdan_id=dingdan.dingdan_id).update(zhuangtai=25)
+
+ # 更新老板扩展表
+ laoban_id = dingdan.pingtai_kuozhan.laoban_id if hasattr(dingdan, 'pingtai_kuozhan') else None
+ if laoban_id:
+ user_main = UserMain.objects.filter(yonghuid=laoban_id).first()
+ if user_main and hasattr(user_main, 'boss_profile'):
+ boss = user_main.boss_profile
+ boss.zonge = (boss.zonge or 0) + (dingdan.jine or 0)
+ boss.alldingdan = (boss.alldingdan or 0) + 1
+ boss.save()
+
+ # 更新商品库存
+ if dingdan.shangpin_id:
+ Shangpin.objects.filter(id=dingdan.shangpin_id).update(
+ kucun=F('kucun') - 1
+ )
+
+
+ # ========== 新增:商品和店铺每日统计 ==========
+ try:
+ # 商品每日统计(只要有商品ID就执行)
+ if dingdan.shangpin_id:
+ # 从扩展表获取已计算好的收益值(下单时已存储)
+ pingtai_shouyi = Decimal('0.00')
+ dianpu_shouyi = Decimal('0.00')
+ if hasattr(dingdan, 'pingtai_kuozhan'):
+ pingtai_shouyi = dingdan.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
+ dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
+
+ update_shangpin_daily_stat(
+ shangpin_id=dingdan.shangpin_id,
+ caozuo_leixing=1, # 1 = 下单
+ dingdan_jiage=dingdan.jine,
+ pingtai_shouyi=pingtai_shouyi,
+ dianpu_zongshouyi=dianpu_shouyi
+ )
+ logger.info(f"商品统计更新完成,商品ID: {dingdan.shangpin_id}")
+
+ # 店铺每日统计(仅当存在店铺ID时执行)
+ dianpu_id = None
+ if hasattr(dingdan, 'pingtai_kuozhan'):
+ dianpu_id = dingdan.pingtai_kuozhan.dianpu_id
+ if dianpu_id:
+ dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
+ update_dianpu_daily_stat(
+ dianpu_id=dianpu_id,
+ caozuo_leixing=1, # 1 = 下单
+ dingdan_jiage=dingdan.jine,
+ dianpu_shouyi=dianpu_shouyi
+ )
+ logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}")
+ except Exception as e:
+ logger.error(f"商品/店铺统计更新失败: {e}")
+ logger.error(traceback.format_exc())
+ # 统计失败不影响主流程
+ # =============================================
+
+
+ # 更新收支记录
+ self._update_szjilu(dingdan.jine)
+
+ # 更新每日收入统计
+ update_daily_income(dingdan.jine)
+
+ # 跨平台同步(复查成功也需要通知)
+ sync_order_to_partners(dingdan.dingdan_id)
+
+ def _query_wechat_payment(self, out_trade_no):
+ """
+ 调用微信支付订单查询接口,返回 (trade_state, transaction_id)
+ 若查询失败,抛出异常
+ """
+ appid = settings.WEIXIN_APPID
+ mch_id = settings.WEIXIN_MCHID
+ key = settings.WEIXIN_SHANGHUMIYAO
+ nonce_str = self._generate_nonce_str()
+
+ params = {
+ 'appid': appid,
+ 'mch_id': mch_id,
+ 'out_trade_no': out_trade_no,
+ 'nonce_str': nonce_str,
+ }
+
+ # 生成签名
+ sorted_keys = sorted(params.keys())
+ stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys])
+ stringSignTemp = f"{stringA}&key={key}"
+ sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper()
+ params['sign'] = sign
+
+ xml_data = self._dict_to_xml(params)
+ url = 'https://api.mch.weixin.qq.com/pay/orderquery'
+ response = requests.post(url, data=xml_data, timeout=10)
+ result = xmltodict.parse(response.content)['xml']
+
+ if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
+ return result.get('trade_state'), result.get('transaction_id')
+ else:
+ err_msg = result.get('return_msg', '未知错误')
+ raise Exception(f"微信订单查询失败: {err_msg}")
+
+ def _update_szjilu(self, jine):
+ """更新收支记录表(ID=1的记录)"""
+ try:
+ jine_decimal = Decimal(str(jine))
+ with transaction.atomic():
+ szjilu, created = Szjilu.objects.select_for_update().get_or_create(
+ id=1,
+ defaults={
+ 'zongsy': Decimal('0.00'),
+ 'zongls': Decimal('0.00'),
+ 'zongzc': Decimal('0.00'),
+ 'jrzc': Decimal('0.00'),
+ 'jrls': Decimal('0.00')
+ }
+ )
+ if created:
+ logger.info("创建ID=1的收支记录")
+ szjilu.zongsy += jine_decimal
+ szjilu.zongls += jine_decimal
+ szjilu.jrls += jine_decimal
+ szjilu.save()
+ logger.info(f"收支记录更新成功,增加金额: {jine}")
+ except Exception as e:
+ logger.error(f"更新收支记录失败: {e}")
+ # 不抛出异常,因为收支记录不是核心流程,但记录错误供排查
+
+ def _generate_nonce_str(self):
+ """生成随机字符串,长度32"""
+ return ''.join(random.choices(string.ascii_letters + string.digits, k=32))
+
+ def _dict_to_xml(self, data):
+ """将字典转换为XML字符串"""
+ xml = ['']
+ for k, v in data.items():
+ xml.append(f'<{k}>{v}{k}>')
+ xml.append('')
+ return ''.join(xml)
+
+
+import os
+from django.db.models import F
+from django.shortcuts import render
+from rest_framework.permissions import AllowAny # 新增这行
+from rest_framework.throttling import AnonRateThrottle
+# dingdan/views.py
+import time
+import random
+import hashlib
+import xml.etree.ElementTree as ET
+import requests
+import uuid
+from decimal import Decimal
+from django.db import transaction
+from django.conf import settings
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import permissions, status
+from tencentcloud.common.profile.client_profile import ClientProfile
+from tencentcloud.common.profile.http_profile import HttpProfile
+
+from shangpin.models import Shangpin, ShangpinLeixing, Huiyuangoumai
+from dingdan.models import Dingdan, DingdanPingtai, Lilubiao, Pingfen,Chufatupian, DingdanShangjia, Chufajilu, Liaotian
+from yonghu.models import UserMain, UserDashou, UserShangjia
+
+
+class CreateOrderView(APIView):
+ """
+ 创建订单并调用微信支付接口
+ """
+ permission_classes = [permissions.IsAuthenticated]
+
+ def post(self, request):
+ try:
+ # 1. 获取前端传递的字段
+ shangpin_id = request.data.get('shangpin_id')
+ nicheng = request.data.get('nicheng', '').strip()
+ beizhu = request.data.get('beizhu', '').strip()
+ zhiding = request.data.get('zhiding', '').strip()
+ jine = request.data.get('jine', 0)
+
+ # 2. 验证必填字段
+ if not shangpin_id:
+ return Response({'code': 1, 'msg': '商品ID不能为空', 'data': None})
+
+ if not nicheng:
+ return Response({'code': 2, 'msg': '游戏昵称不能为空', 'data': None})
+
+ if len(nicheng) > 50:
+ return Response({'code': 3, 'msg': '游戏昵称过长', 'data': None})
+
+ # 3. 查询商品信息
+ shangpin = Shangpin.objects.filter(id=shangpin_id).first()
+ if not shangpin:
+ return Response({'code': 4, 'msg': '商品不存在', 'data': None})
+
+
+ is_valid, err_msg, _ = validate_shangpin_and_dianpu(shangpin_id)
+ if not is_valid:
+ return Response({'code': 5, 'msg': err_msg, 'data': None})
+
+ # 检查库存
+ if hasattr(shangpin, 'kucun') and shangpin.kucun <= 0:
+ return Response({'code': 5, 'msg': '商品库存不足', 'data': None})
+
+ # 获取商品价格
+ shangpin_jiage = getattr(shangpin, 'jiage', 0)
+ if not shangpin_jiage:
+ return Response({'code': 6, 'msg': '商品价格异常', 'data': None})
+ #获取商品图片
+ shangpin_tupian = getattr(shangpin, 'tupian_url', None)
+
+ # 4. 验证指定打手(如果存在)
+ zhiding_id_value = None
+ if zhiding:
+ # 查询主表中是否存在该打手
+ zhiding_user = UserMain.objects.filter(
+ yonghuid=zhiding,
+ #user_type='dashou'
+ ).first()
+
+ if not zhiding_user:
+ return Response({'code': 7, 'msg': '指定打手不存在', 'data': None})
+
+ # 查询打手扩展表
+ dashou_profile = UserDashou.objects.filter(user=zhiding_user).first()
+
+ if not dashou_profile:
+ return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None})
+
+ # 检查在线状态
+ if getattr(dashou_profile, 'zhanghaozhuangtai', 0) == 0:
+ return Response({'code': 8, 'msg': '指定用户不存在或已封禁', 'data': None})
+
+ zhiding_id_value = zhiding
+
+ # 5. 生成订单ID
+ timestamp = int(time.time())
+ timestamp_ns = int(time.time_ns() // 1000) % 1000000
+ random_num = random.randint(0, 99)
+ dingdanid = f"PT{timestamp:011d}{timestamp_ns:06d}{random_num:02d}"
+
+ # 6. 查询利率并计算分成
+ # 查询打手利率(字符'1')
+ dashou_lilu = Lilubiao.objects.filter(fadanpingtai='1').first()
+
+
+ # 🔥 新增:判断是否使用商品配置的额外分成
+ if shangpin.kaioi_ewai_dashou_fencheng and shangpin.ewai_dashou_fencheng is not None:
+ # 使用商品指定的额外分成金额
+ dashou_fencheng = shangpin.ewai_dashou_fencheng
+ else:
+ # 按原有逻辑计算打手分成
+ dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.lilu))
+
+
+ # 查询管事利率(字符'2')
+ #guanshi_lilu = Lilubiao.objects.filter(fadanpingtai='2').first()
+
+ if not dashou_lilu: #or not guanshi_lilu:
+ return Response({'code': 9, 'msg': '系统利率配置不完整', 'data': None})
+
+ # 计算打手分成
+ #dashou_fencheng = Decimal(str(shangpin_jiage)) * Decimal(str(dashou_lilu.lilu))
+
+
+ # 开始数据库事务
+ with transaction.atomic():
+ # 获取当前用户信息
+ current_user = request.user
+ laoban_yonghuid = getattr(current_user, 'yonghuid', '')
+ user_openid = getattr(current_user, 'openid', '')
+
+ if not user_openid:
+ return Response({'code': 10, 'msg': '用户openid不存在', 'data': None})
+
+ # 创建订单主表数据
+ order_data = {
+ 'dingdan_id': dingdanid,
+ 'zhuangtai': 9, # 未付款状态
+ 'jine': shangpin_jiage,
+ 'fadan_pingtai':1,
+ 'dashou_fencheng': dashou_fencheng,
+ 'zhiding_id': zhiding_id_value,
+ 'shangpin_id': shangpin_id,
+ 'yaoqiuleixing': getattr(shangpin, 'yaoqiuleixing', None),
+ 'huiyuan_id': getattr(shangpin, 'huiyuan_id', ''),
+ 'yongjin': getattr(shangpin, 'yongjin', 0),
+ 'jieshao': getattr(shangpin, 'jieshao', ''),
+ 'beizhu': beizhu,
+ 'nicheng': nicheng,
+ 'tupian':shangpin_tupian,
+ 'user1_id':f"Boss{request.user.yonghuid}",
+ 'leixing_id':shangpin.leixing_id
+ }
+
+ # 创建订单记录
+
+ dingdan = Dingdan.objects.create(**order_data)
+
+
+ # CreateOrderView.post 事务内部,替换原有的扩展表创建代码
+
+ dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong = calculate_pingtai_and_dianpu_shouyi(
+ shangpin_id=shangpin_id,
+ dingdan_jiage=shangpin_jiage,
+ dashou_fencheng=dashou_fencheng
+ )
+
+ pingtaikuozhan_data = {
+ 'dingdan': dingdan,
+ 'laoban_id': laoban_yonghuid,
+ 'pingtai_shouyi': pingtai_shouyi,
+ # ---------- 新增上级分红快照 ----------
+ 'shangji_dianpu_id': shangji_dianpu_id,
+ 'shangji_fenhong': shangji_fenhong if shangji_fenhong else Decimal('0.00'),
+ }
+ if dianpu_id:
+ pingtaikuozhan_data['dianpu_id'] = dianpu_id
+ pingtaikuozhan_data['dianpu_shouyi'] = dianpu_shouyi
+ else:
+ pingtaikuozhan_data['dianpu_shouyi'] = Decimal('0.00')
+
+ DingdanPingtai.objects.create(**pingtaikuozhan_data)
+
+
+ # 计算收益
+ '''dianpu_id, pingtai_shouyi, dianpu_shouyi = calculate_pingtai_and_dianpu_shouyi(
+ shangpin_id=shangpin_id,
+ dingdan_jiage=shangpin_jiage,
+ dashou_fencheng=dashou_fencheng
+ )
+
+ # 构建扩展表数据,平台收益必定保存,店铺收益和店铺ID仅当dianpu_id存在时才赋值
+ pingtaikuozhan_data = {
+ 'dingdan': dingdan,
+ 'laoban_id': laoban_yonghuid,
+ 'pingtai_shouyi': pingtai_shouyi,
+ }
+ if dianpu_id:
+ pingtaikuozhan_data['dianpu_id'] = dianpu_id
+ pingtaikuozhan_data['dianpu_shouyi'] = dianpu_shouyi
+ else:
+ # 无店铺时,店铺收益字段保持默认(0.00),也可显式设为0
+ pingtaikuozhan_data['dianpu_shouyi'] = Decimal('0.00')
+
+ DingdanPingtai.objects.create(**pingtaikuozhan_data)'''
+
+
+ # 7. 生成微信支付参数(向微信官方发送请求)
+ pay_params = self.generate_wechat_pay_params(
+ dingdanid=dingdanid,
+ jine=shangpin_jiage,
+ beizhu=getattr(settings, 'WEIXIN_PAY_DESCRIPTION'),
+ user_ip=self.get_client_ip(request),
+ openid=user_openid
+ )
+
+ if not pay_params:
+ # 手动抛出异常触发事务回滚
+ raise Exception('支付参数生成失败')
+
+ # 8. 返回成功响应
+ response_data = {
+ 'code': 0,
+ 'msg': '订单创建成功',
+ 'data': {
+ 'dingdanid': dingdanid,
+ 'payParams': pay_params
+ }
+ }
+
+ return Response(response_data)
+
+ except Exception as e:
+ # 这里会自动回滚事务
+ return Response({'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None})
+
+ def get_client_ip(self, request):
+ """获取客户端真实IP地址"""
+ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
+ if x_forwarded_for:
+ ip = x_forwarded_for.split(',')[0]
+ else:
+ ip = request.META.get('REMOTE_ADDR')
+ return ip
+
+ def generate_wechat_pay_params(self, dingdanid, jine, beizhu, user_ip, openid):
+ """
+ 调用微信支付统一下单接口,生成支付参数
+ 完全按照您提供的正确示例代码编写
+ """
+ try:
+ # 微信支付配置
+ APPID = getattr(settings, 'WEIXIN_APPID', '')
+ MCHID = getattr(settings, 'WEIXIN_MCHID', '')
+ KEY = getattr(settings, 'WEIXIN_SHANGHUMIYAO', '')
+ NOTIFY_URL = getattr(settings, 'WEIXIN_NOTIFY_URL', '')
+
+ if not all([APPID, MCHID, KEY, NOTIFY_URL]):
+ raise Exception('微信支付配置不完整')
+
+ # 生成随机字符串
+ import string
+ nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
+
+ # 金额(单位:分)
+ total_fee = int(float(jine) * 100)
+
+ # 交易类型
+ trade_type = 'JSAPI'
+
+ # 生成签名
+ sign_data = {
+ 'appid': APPID,
+ 'mch_id': MCHID,
+ 'nonce_str': nonce_str,
+ 'body': beizhu,
+ 'out_trade_no': dingdanid,
+ 'total_fee': total_fee,
+ 'spbill_create_ip': user_ip,
+ 'notify_url': NOTIFY_URL,
+ 'trade_type': trade_type,
+ 'openid': openid
+ }
+
+ # 按照参数名ASCII码从小到大排序
+ sorted_sign_data = sorted(sign_data.items(), key=lambda x: x[0])
+
+ # 拼接字符串
+ sign_string = '&'.join([f"{key}={value}" for key, value in sorted_sign_data if value])
+ sign_string += f'&key={KEY}'
+
+ # MD5签名
+ sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper()
+
+ # 构造XML数据
+ xml_data = f"""
+{APPID}
+{beizhu}
+{MCHID}
+{nonce_str}
+{NOTIFY_URL}
+{openid}
+{dingdanid}
+{user_ip}
+{total_fee}
+{trade_type}
+{sign}
+"""
+
+ # 调用微信统一下单接口
+ response = requests.post(
+ 'https://api.mch.weixin.qq.com/pay/unifiedorder',
+ data=xml_data.encode('utf-8'),
+ headers={'Content-Type': 'application/xml'},
+ timeout=10
+ )
+
+ # 解析返回的XML
+ root = ET.fromstring(response.content)
+
+ return_code = root.find('return_code').text
+ result_code = root.find('result_code').text
+
+ if return_code == 'SUCCESS' and result_code == 'SUCCESS':
+ prepay_id = root.find('prepay_id').text
+
+ # 生成前端支付参数
+ timestamp = str(int(time.time()))
+ nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
+ package = f'prepay_id={prepay_id}'
+
+ # 生成支付签名
+ pay_sign_data = {
+ 'appId': APPID,
+ 'timeStamp': timestamp,
+ 'nonceStr': nonce_str,
+ 'package': package,
+ 'signType': 'MD5'
+ }
+
+ sorted_pay_sign_data = sorted(pay_sign_data.items(), key=lambda x: x[0])
+ pay_sign_string = '&'.join([f"{key}={value}" for key, value in sorted_pay_sign_data])
+ pay_sign_string += f'&key={KEY}'
+ pay_sign = hashlib.md5(pay_sign_string.encode('utf-8')).hexdigest().upper()
+
+ # 返回支付参数(格式与前端完全匹配)
+ result = {
+ 'timeStamp': timestamp,
+ 'nonceStr': nonce_str,
+ 'package': package,
+ 'signType': 'MD5',
+ 'paySign': pay_sign
+ }
+
+ return result
+ else:
+ # 获取详细的错误信息
+ err_code = root.find('err_code')
+ err_code_des = root.find('err_code_des')
+ return_msg = root.find('return_msg')
+
+ error_details = f"return_code: {return_code}, "
+ if err_code is not None:
+ error_details += f"err_code: {err_code.text}, "
+ if err_code_des is not None:
+ error_details += f"err_code_des: {err_code_des.text}, "
+ if return_msg is not None:
+ error_details += f"return_msg: {return_msg.text}"
+
+ raise Exception(f"微信支付下单失败: {error_details}")
+
+ except Exception as e:
+ raise e
+
+
+# dingdan/views.py
+
+
+# dingdan/views.py
+
+
+from yonghu.models import UserMain
+
+# dingdan/views.py
+
+from rest_framework.views import APIView
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from django.db.models import Q
+
+from .models import Dingdan, DingdanPingtai
+
+
+class DingdanHuoquView(APIView):
+ """订单获取接口"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 获取当前登录用户
+ try:
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+ except Exception as e:
+ return Response({
+ 'code': 1,
+ 'msg': '用户认证失败',
+ 'data': None
+ })
+
+ # 获取前端参数
+ try:
+ data = request.data
+ zhuangtai_list = data.get('zhuangtai_list', [])
+ page = int(data.get('page', 1))
+ page_size = int(data.get('page_size', 10))
+ except Exception as e:
+ return Response({
+ 'code': 2,
+ 'msg': '参数格式错误',
+ 'data': None
+ })
+
+ # 参数验证
+ if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
+ return Response({
+ 'code': 3,
+ 'msg': '状态列表不能为空',
+ 'data': None
+ })
+
+ if page < 1:
+ page = 1
+ if page_size <= 0:
+ page_size = 10
+
+ try:
+ # 查询扩展表,获取用户的所有订单ID
+ pingtai_orders = DingdanPingtai.objects.filter(laoban_id=yonghuid)
+ pingtai_order_ids = list(pingtai_orders.values_list('dingdan__dingdan_id', flat=True))
+
+ if not pingtai_order_ids:
+ return Response({
+ 'code': 0,
+ 'msg': '成功',
+ 'data': {
+ 'list': [],
+ 'has_more': False,
+ 'total': 0
+ }
+ })
+
+ # 查询主表
+ status_query = Q()
+ if zhuangtai_list != [1, 2, 3, 4, 5, 6, 7, 8]:
+ status_query = Q(zhuangtai__in=zhuangtai_list)
+
+ query = Q(dingdan_id__in=pingtai_order_ids) & status_query
+ orders = Dingdan.objects.filter(query).order_by('-create_time')
+ #total_orders = orders.count()
+
+ # 手动分页逻辑
+ start_index = (page - 1) * page_size
+ end_index = start_index + page_size
+
+ # 获取当前页的数据
+ current_page_orders = orders[start_index:end_index]
+
+ # 构建返回数据
+ order_list = []
+ for order in current_page_orders:
+ order_data = {
+ 'dingdanId': order.dingdan_id,
+ 'zhuangtai': order.zhuangtai,
+ 'jine': float(order.jine) if order.jine else 0.0,
+ 'tupian': order.tupian or '',
+ 'jieshao': order.jieshao or '',
+ 'beizhu': order.beizhu or '',
+ 'nicheng': order.nicheng or '',
+ 'chuangjianshijian': order.create_time.strftime('%Y-%m-%d %H:%M:%S')
+ }
+ order_list.append(order_data)
+
+
+ # 判断是否还有更多数据
+ current_page_count = len(current_page_orders)
+ has_more = current_page_count == page_size
+
+ return Response({
+ 'code': 0,
+ 'msg': '成功',
+ 'data': {
+ 'list': order_list,
+ 'has_more': has_more,
+ #'total': total_orders
+ }
+ })
+
+ except Exception as e:
+ return Response({
+ 'code': 4,
+ 'msg': '服务器内部错误',
+ 'data': None
+ })
+
+
+# dingdan/views.py
+
+from rest_framework.views import APIView
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from django.db.models import Q
+import json
+
+from .models import Dingdan, DingdanPingtai, Dashoutupian
+
+
+class DingdanXiangqingView(APIView):
+ """订单详情接口"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 获取当前登录用户
+ try:
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+ except Exception as e:
+ return Response({
+ 'code': 1,
+ 'msg': '用户认证失败',
+ 'data': None
+ })
+
+ # 获取前端参数
+ try:
+ data = request.data
+ dingdan_id = data.get('dingdanId', '')
+ except Exception as e:
+ return Response({
+ 'code': 2,
+ 'msg': '参数格式错误',
+ 'data': None
+ })
+
+ # 参数验证
+ if not dingdan_id:
+ return Response({
+ 'code': 3,
+ 'msg': '订单ID不能为空',
+ 'data': None
+ })
+
+ try:
+ # 1. 验证订单是否属于当前用户
+ # 查询平台订单扩展表,检查laoban_id是否等于当前用户
+ pingtai_kuozhan = DingdanPingtai.objects.filter(
+ laoban_id=yonghuid,
+ dingdan__dingdan_id=dingdan_id
+ ).first()
+
+ if not pingtai_kuozhan:
+ # 如果不是平台订单,检查商家订单
+ from .models import DingdanShangjia
+ shangjia_kuozhan = DingdanShangjia.objects.filter(
+ shangjia_id=yonghuid,
+ dingdan__dingdan_id=dingdan_id
+ ).first()
+
+ if not shangjia_kuozhan:
+ return Response({
+ 'code': 4,
+ 'msg': '订单不存在或无权限查看',
+ 'data': None
+ })
+
+ # 2. 查询订单主表
+ dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
+
+ if not dingdan:
+ return Response({
+ 'code': 5,
+ 'msg': '订单不存在',
+ 'data': None
+ })
+
+ # 3. 获取订单信息
+ zhuangtai = dingdan.zhuangtai or 0
+ zhiding_id = dingdan.zhiding_id or ''
+ jiedan_dashou_id = dingdan.jiedan_dashou_id or ''
+ dashou_liuyan = dingdan.dashou_liuyan or ''
+
+ # 4. 查询打手交付图片
+ dashoutupian_records = Dashoutupian.objects.filter(
+ dingdan_id=dingdan_id
+ ).order_by('create_time')[:9] # 最多取9条
+
+ dashoujiaofu_list = []
+ for record in dashoutupian_records:
+ if record.tupian:
+ dashoujiaofu_list.append(record.tupian)
+
+ # 5. 构建返回数据(字段名与前端对应)
+ response_data = {
+ 'zhuangtai': zhuangtai, # 订单状态
+ 'zhiding': zhiding_id, # 指定打手ID
+ 'dashou': jiedan_dashou_id, # 服务打手ID
+ 'dashouliuyan': dashou_liuyan, # 打手留言
+ 'dashoujiaofu': dashoujiaofu_list # 打手交付图片列表
+ }
+
+ # 6. 返回成功响应
+ return Response({
+ 'code': 0,
+ 'msg': '成功',
+ 'data': response_data
+ })
+
+ except Exception as e:
+ print(f"订单详情查询失败: {str(e)}")
+ return Response({
+ 'code': 6,
+ 'msg': '服务器内部错误',
+ 'data': None
+ })
+
+
+class JiedanView(APIView):
+ """结单接口"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 获取当前登录用户
+ try:
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+ except Exception as e:
+ return Response({
+ 'code': 1,
+ 'msg': '用户认证失败',
+ 'data': None
+ })
+
+ # 获取前端参数
+ try:
+ data = request.data
+ dingdan_id = data.get('dingdanId', '')
+ pingfen = int(data.get('pingfen', 0)) # 评分,默认0
+ liuyan = data.get('liuyan', '') # 留言,默认空
+ except Exception as e:
+ return Response({
+ 'code': 2,
+ 'msg': '参数格式错误',
+ 'data': None
+ })
+
+ # 参数验证
+ if not dingdan_id:
+ return Response({
+ 'code': 3,
+ 'msg': '订单ID不能为空',
+ 'data': None
+ })
+
+ # 验证评分范围
+ if pingfen < 0 or pingfen > 5:
+ return Response({
+ 'code': 4,
+ 'msg': '评分必须在0-5分之间',
+ 'data': None
+ })
+
+ # 使用事务确保数据一致性
+ try:
+ with transaction.atomic():
+ # 1. 验证订单是否存在且属于当前用户
+ pingtai_kuozhan = DingdanPingtai.objects.filter(
+ laoban_id=yonghuid,
+ dingdan__dingdan_id=dingdan_id
+ ).select_related('dingdan').first()
+
+ if not pingtai_kuozhan:
+ return Response({
+ 'code': 5,
+ 'msg': '订单不存在或无权限操作',
+ 'data': None
+ })
+
+ dingdan = pingtai_kuozhan.dingdan
+
+ # 2. 验证订单状态(必须是结算中 - 状态8)
+ if dingdan.zhuangtai != 8:
+ return Response({
+ 'code': 6,
+ 'msg': '订单不在结算状态,无法结单',
+ 'data': None
+ })
+
+ # 3. 获取订单相关数据
+ dashou_fencheng = dingdan.dashou_fencheng or Decimal('0.00') # 打手分成
+ jiedan_dashou_id = dingdan.jiedan_dashou_id or '' # 接单打手ID
+ shangpin_id = dingdan.shangpin_id # 商品ID
+
+ # 4. 更新平台扩展表的老板评价(如果有留言)
+ if liuyan:
+ pingtai_kuozhan.laoban_pingjia = liuyan
+ pingtai_kuozhan.save()
+
+ # 5. 如果有评分,更新评分表
+ if pingfen > 0 and jiedan_dashou_id:
+ # 使用filter查询,更健壮
+ pingfen_records = Pingfen.objects.filter(dashou=jiedan_dashou_id)
+
+ if pingfen_records.exists():
+ # 已有记录,更新
+ pingfen_record = pingfen_records.first()
+ pingfen_record.zongfen += Decimal(str(pingfen))
+ pingfen_record.cishu += 1
+ pingfen_record.save()
+ else:
+ # 新记录,创建
+ Pingfen.objects.create(
+ dashou=jiedan_dashou_id,
+ zongfen=Decimal(str(pingfen)),
+ cishu=1
+ )
+
+ # 6. 更新商品真实销量(如果有商品ID)
+ if shangpin_id:
+ shangpin_records = Shangpin.objects.filter(id=shangpin_id)
+
+ if shangpin_records.exists():
+ shangpin = shangpin_records.first()
+ # 检查字段是否存在
+ if hasattr(shangpin, 'zhenshi_xiaoliang'):
+ shangpin.zhenshi_xiaoliang = (shangpin.zhenshi_xiaoliang or 0) + 1
+ shangpin.save()
+
+ # ========== 新增:跨平台我方派单且有对方信息,直接通知对方并跳过本地结算 ==========
+ if (dingdan.is_cross == 1
+ and dingdan.partner_order_id
+ and dingdan.partner_club_id):
+ # 更新订单状态为已完成
+ dingdan.zhuangtai = 3
+ dingdan.clkf = current_user.phone if hasattr(current_user, 'phone') else yonghuid
+ dingdan.save()
+
+ # 事务提交后异步通知对方平台
+ transaction.on_commit(
+ lambda: self._notify_partner_jiedan(
+ my_dingdan_id=dingdan.dingdan_id,
+ partner_club_id=dingdan.partner_club_id,
+ partner_order_id=dingdan.partner_order_id,
+ new_status=3,
+ current_user=current_user
+ )
+ )
+
+ # 直接返回,跳过后续所有本地打手结算、评价、统计等
+ return Response({
+ 'code': 0,
+ 'msg': '结单成功(已通知对方平台)',
+ 'data': None
+ })
+ # ====================================================================
+
+ # 7. 更新打手扩展表(如果有打手ID和分成)
+ if jiedan_dashou_id and dashou_fencheng > 0:
+ # 查询打手用户
+ dashou_user = UserMain.objects.filter(yonghuid=jiedan_dashou_id).first()
+
+ if dashou_user:
+ # 查询打手扩展表
+ dashou_kuozhan = UserDashou.objects.filter(user=dashou_user).first()
+
+ if dashou_kuozhan:
+ # 更新四个金额字段
+ dashou_kuozhan.zonge = (dashou_kuozhan.zonge or Decimal('0.00')) + dashou_fencheng
+ dashou_kuozhan.yue = (dashou_kuozhan.yue or Decimal('0.00')) + dashou_fencheng
+ dashou_kuozhan.jinrishouyi = (dashou_kuozhan.jinrishouyi or Decimal(
+ '0.00')) + dashou_fencheng
+ dashou_kuozhan.jinyueshouyi = (dashou_kuozhan.jinyueshouyi or Decimal(
+ '0.00')) + dashou_fencheng
+
+ # 成交总量加1
+ dashou_kuozhan.chengjiaozongliang = (dashou_kuozhan.chengjiaozongliang or 0) + 1
+
+ # 状态改为数字1
+ dashou_kuozhan.zhuangtai = 1
+
+ dashou_kuozhan.save()
+
+ # 8. 更新订单状态为已完成(状态3)
+ dingdan.zhuangtai = 3
+ dingdan.save()
+
+ # 🔥 新增:调用打手每日统计(成交)
+ try:
+ update_dashou_daily_by_action(
+ yonghuid=jiedan_dashou_id,
+ amount=dashou_fencheng,
+ action=2 # 2 表示成交(结算)
+ )
+ logger.info(f"打手 {jiedan_dashou_id} 每日统计更新成功")
+ except Exception as e:
+ logger.error(f"打手每日统计更新失败: {e}")
+ # 不影响主流程
+
+
+ # ========== 新增:商品和店铺每日统计 ==========
+ try:
+ # 商品每日统计(只要有商品ID就执行)
+ if dingdan.shangpin_id:
+ # 从扩展表获取已计算好的收益值(下单时已存储)
+ pingtai_shouyi = Decimal('0.00')
+ dianpu_shouyi = Decimal('0.00')
+ if hasattr(dingdan, 'pingtai_kuozhan'):
+ pingtai_shouyi = dingdan.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
+ dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
+
+ update_shangpin_daily_stat(
+ shangpin_id=dingdan.shangpin_id,
+ caozuo_leixing=2, # 2 = 结单
+ dingdan_jiage=dingdan.jine,
+ pingtai_shouyi=pingtai_shouyi,
+ dianpu_zongshouyi=dianpu_shouyi
+ )
+ logger.info(f"商品统计更新完成,商品ID: {dingdan.shangpin_id}")
+
+ # 店铺每日统计(仅当存在店铺ID时执行)
+
+
+ # ========== 修改为 ==========
+ # 店铺每日统计(包含上级分红)
+ dianpu_id = None
+ shangji_id = None
+ shangji_fen = Decimal('0.00')
+ if hasattr(dingdan, 'pingtai_kuozhan'):
+ dianpu_id = dingdan.pingtai_kuozhan.dianpu_id
+ shangji_id = dingdan.pingtai_kuozhan.shangji_dianpu_id
+ shangji_fen = dingdan.pingtai_kuozhan.shangji_fenhong or Decimal('0.00')
+ if dianpu_id:
+ dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
+ update_dianpu_daily_stat(
+ dianpu_id=dianpu_id,
+ caozuo_leixing=2, # 结算
+ dingdan_jiage=dingdan.jine,
+ dianpu_shouyi=dianpu_shouyi,
+ shangji_dianpu_id=shangji_id, # 新增:上级店铺ID
+ shangji_fenhong=shangji_fen # 新增:该单给上级的分红
+ )
+ logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}, 上级ID: {shangji_id}, 上级分红: {shangji_fen}")
+
+
+ '''dianpu_id = None
+ if hasattr(dingdan, 'pingtai_kuozhan'):
+ dianpu_id = dingdan.pingtai_kuozhan.dianpu_id
+ if dianpu_id:
+ dianpu_shouyi = dingdan.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
+ update_dianpu_daily_stat(
+ dianpu_id=dianpu_id,
+ caozuo_leixing=2, # 2 = 结单
+ dingdan_jiage=dingdan.jine,
+ dianpu_shouyi=dianpu_shouyi
+ )
+ logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}")'''
+ except Exception as e:
+ logger.error(f"商品/店铺统计更新失败: {e}")
+ logger.error(traceback.format_exc())
+ # 统计失败不影响主流程
+ # =============================================
+
+ # 12. 更新平台收益流水(仅在平台发单时)
+ if dingdan.fadan_pingtai == 1:
+ try:
+ from dingdan.utils import update_platform_profit # 导入函数
+ update_platform_profit(dingdan_id)
+ except Exception as e:
+ # 平台收益更新失败不影响主流程,但记录日志
+ logger.error(f"强制结单后更新平台收益失败: {str(e)}")
+
+ # 9. 返回成功响应
+ return Response({
+ 'code': 0,
+ 'msg': '结单成功',
+ 'data': None
+ })
+
+ except Exception as e:
+
+ # 事务会自动回滚
+ return Response({
+ 'code': 7,
+ 'msg': '结单失败,服务器内部错误',
+ 'data': None
+ })
+
+
+ def _notify_partner_jiedan(self, my_dingdan_id, partner_club_id, partner_order_id, new_status, current_user):
+ """
+ 通知对方俱乐部我方订单已完成
+ 参照 CrossPlatformForceCompleteView._notify_partner_order_status 实现
+ """
+ try:
+ # 1. 获取我方俱乐部ID
+ from .models import CrossPlatformOrderData
+ my_club_config = ClubConfig.objects.filter(is_self=1).first()
+ if not my_club_config:
+ logger.error("未配置我方俱乐部信息,无法通知对方")
+ return
+ self_club_id = my_club_config.club_id
+
+ # 2. 查询对方服务器域名
+ club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
+ partner_domain = club_rel.partner_domain
+ if not partner_domain:
+ logger.error(f"对方俱乐部 {partner_club_id} 未配置域名")
+ return
+
+ # 3. 构造通知请求
+ url = partner_domain.rstrip('/') + '/houtai/dfddtk'
+ payload = {
+ 'order_id': partner_order_id, # 在对方看来这是他们的订单ID
+ 'club_id': self_club_id,
+ 'status': new_status
+ }
+ resp = requests.post(url, json=payload, timeout=3)
+ if resp.status_code == 200:
+ resp_json = resp.json()
+ if resp_json.get('code') == 0:
+ # 4. 更新跨平台订单扩展表(记录对方状态)
+ CrossPlatformOrderData.objects.update_or_create(
+ dingdan_id=my_dingdan_id, # 我方订单ID
+ defaults={
+ 'partner_order_id': partner_order_id,
+ 'partner_club_id': partner_club_id,
+ 'partner_order_status': new_status,
+ 'partner_dashou_id': '',
+ 'partner_amount': Decimal('0')
+ }
+ )
+ logger.info(f"老板结单通知对方成功: {partner_order_id} -> {new_status}")
+ else:
+ logger.warning(f"对方接口返回错误: {resp_json.get('msg')}")
+ else:
+ logger.warning(f"对方接口HTTP错误: {resp.status_code}")
+ except requests.exceptions.Timeout:
+ logger.error(f"通知对方平台超时: {partner_club_id}")
+ except Exception as e:
+ logger.error(f"通知对方平台异常: {str(e)}")
+
+
+from dengji.models import DashouBiaoxian
+from dengji.services import record_dashou_biaoxian
+
+
+class DingdanXiangqingView2(APIView):
+ """订单详情接口(老板端)- 返回完整评价信息"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ try:
+ yonghuid = request.user.yonghuid
+ except Exception:
+ return Response({'code': 1, 'msg': '用户认证失败', 'data': None})
+
+ dingdan_id = request.data.get('dingdanId', '')
+ if not dingdan_id:
+ return Response({'code': 2, 'msg': '订单ID不能为空', 'data': None})
+
+ try:
+ # 权限验证
+ pingtai_ext = DingdanPingtai.objects.filter(
+ laoban_id=yonghuid,
+ dingdan__dingdan_id=dingdan_id
+ ).select_related('dingdan').first()
+
+ if not pingtai_ext:
+ return Response({'code': 3, 'msg': '订单不存在或无权限查看', 'data': None})
+
+ order = pingtai_ext.dingdan
+ zhuangtai = order.zhuangtai or 0
+ jiedan_dashou_id = order.jiedan_dashou_id or ''
+ dashou_liuyan = order.dashou_liuyan or ''
+ update_time = order.update_time.strftime('%Y-%m-%d %H:%M:%S') if order.update_time else ''
+
+ # 交付图片
+ dashoujiaofu_list = list(
+ Dashoutupian.objects.filter(dingdan_id=dingdan_id)
+ .order_by('create_time')[:9]
+ .values_list('tupian', flat=True)
+ )
+
+ # 打手信息
+ dashou_nicheng = ''
+ dashou_touxiang = ''
+ jiedan_time = ''
+ tijiao_time = ''
+ if jiedan_dashou_id:
+ dashou_user = UserMain.objects.filter(yonghuid=jiedan_dashou_id).first()
+ if dashou_user:
+ dashou_touxiang = dashou_user.avatar or ''
+ try:
+ dashou_nicheng = dashou_user.dashou_profile.nicheng or ''
+ except UserDashou.DoesNotExist:
+ pass
+ biaoxian = DashouBiaoxian.objects.filter(
+ dingdan_id=dingdan_id,
+ dashou_id=jiedan_dashou_id
+ ).first()
+ if biaoxian:
+ if biaoxian.jiedan_time:
+ jiedan_time = biaoxian.jiedan_time.strftime('%Y-%m-%d %H:%M:%S')
+ if biaoxian.tijiao_time:
+ tijiao_time = biaoxian.tijiao_time.strftime('%Y-%m-%d %H:%M:%S')
+
+ # 老板自己的评价(如果已经结算过,可能已有评价)
+ laoban_pingjia = pingtai_ext.laoban_pingjia or ''
+ shangxian_sudu = None
+ fuwu_taidu = None
+ fanche_cishu = None
+ if jiedan_dashou_id and zhuangtai in [3, 4, 5, 6]: # 已完成或退款相关状态才可能有评价
+ biaoxian = DashouBiaoxian.objects.filter(
+ dingdan_id=dingdan_id,
+ dashou_id=jiedan_dashou_id
+ ).first()
+ if biaoxian:
+ shangxian_sudu = biaoxian.shangxian_sudu
+ fuwu_taidu = biaoxian.fuwu_taidu
+ fanche_cishu = biaoxian.fanche_cishu
+
+ data = {
+ 'zhuangtai': zhuangtai,
+ 'dashou': jiedan_dashou_id,
+ 'dashou_nicheng': dashou_nicheng,
+ 'dashou_touxiang': dashou_touxiang,
+ 'dashouliuyan': dashou_liuyan,
+ 'dashoujiaofu': dashoujiaofu_list,
+ 'update_time': update_time,
+ 'jiedan_time': jiedan_time,
+ 'tijiao_time': tijiao_time,
+ 'laoban_pingjia': laoban_pingjia,
+ 'shangxian_sudu': shangxian_sudu, # 1-5 或 null
+ 'fuwu_taidu': fuwu_taidu, # 1-5 或 null
+ 'fanche_cishu': fanche_cishu # int 或 null
+ }
+
+ return Response({'code': 0, 'msg': '成功', 'data': data})
+
+ except Exception as e:
+ logger.error(f"订单详情查询异常: {e}", exc_info=True)
+ return Response({'code': 4, 'msg': '服务器内部错误', 'data': None})
+
+
+class JiedanView2(APIView):
+ """结单接口(老板端)- 接收全新评价字段并写入表现表"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 1. 用户认证
+ try:
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+ except Exception:
+ return Response({'code': 1, 'msg': '用户认证失败', 'data': None})
+
+ # 2. 获取参数(字段名与前端完全一致)
+ try:
+ data = request.data
+ dingdan_id = data.get('dingdanId', '')
+ fanche_cishu = data.get('fanche_cishu') # 前端传的字符串或数字,后端转为整数
+ shangxian_sudu = data.get('shangxian_sudu')
+ fuwu_taidu = data.get('fuwu_taidu')
+ liuyan = data.get('liuyan', '')
+ except Exception:
+ return Response({'code': 2, 'msg': '参数格式错误', 'data': None})
+
+ # 参数校验
+ if not dingdan_id:
+ return Response({'code': 3, 'msg': '订单ID不能为空', 'data': None})
+
+ # 转换并校验必填评价字段
+ try:
+ fanche_cishu = int(fanche_cishu) if fanche_cishu is not None else None
+ shangxian_sudu = int(shangxian_sudu) if shangxian_sudu is not None else None
+ fuwu_taidu = int(fuwu_taidu) if fuwu_taidu is not None else None
+ except (ValueError, TypeError):
+ return Response({'code': 4, 'msg': '评价参数必须为整数', 'data': None})
+
+ if fanche_cishu is None:
+ return Response({'code': 5, 'msg': '翻车次数不能为空', 'data': None})
+ if not (1 <= shangxian_sudu <= 5 and 1 <= fuwu_taidu <= 5):
+ return Response({'code': 6, 'msg': '评价分数必须在1-5之间', 'data': None})
+
+ # 使用事务保证数据一致性
+ try:
+ with transaction.atomic():
+ # 3. 权限验证:订单属于当前老板
+ pingtai_ext = DingdanPingtai.objects.filter(
+ laoban_id=yonghuid,
+ dingdan__dingdan_id=dingdan_id
+ ).select_related('dingdan').first()
+
+ if not pingtai_ext:
+ return Response({'code': 7, 'msg': '订单不存在或无权限操作', 'data': None})
+
+ order = pingtai_ext.dingdan
+
+ # 4. 状态校验:必须是结算中(状态8)
+ if order.zhuangtai != 8:
+ return Response({'code': 8, 'msg': '订单不在结算状态', 'data': None})
+
+ # 5. 提取订单关键数据
+ dashou_fencheng = order.dashou_fencheng or Decimal('0.00')
+ jiedan_dashou_id = order.jiedan_dashou_id or ''
+ shangpin_id = order.shangpin_id
+
+ # 6. 保存老板评价(兼容旧逻辑)
+ if liuyan:
+ pingtai_ext.laoban_pingjia = liuyan
+ pingtai_ext.save()
+
+ # 7. 更新评分表(用服务态度分作为综合评分,保留原有统计)
+ if jiedan_dashou_id and fuwu_taidu:
+ pingfen_records = Pingfen.objects.filter(dashou=jiedan_dashou_id)
+ if pingfen_records.exists():
+ pf = pingfen_records.first()
+ pf.zongfen += Decimal(str(fuwu_taidu))
+ pf.cishu += 1
+ pf.save()
+ else:
+ Pingfen.objects.create(
+ dashou=jiedan_dashou_id,
+ zongfen=Decimal(str(fuwu_taidu)),
+ cishu=1
+ )
+
+ # 8. 更新商品真实销量
+ if shangpin_id:
+ shangpin_qs = Shangpin.objects.filter(id=shangpin_id)
+ if shangpin_qs.exists():
+ sp = shangpin_qs.first()
+ if hasattr(sp, 'zhenshi_xiaoliang'):
+ sp.zhenshi_xiaoliang = (sp.zhenshi_xiaoliang or 0) + 1
+ sp.save()
+
+ # 9. 更新打手收益与状态
+ if jiedan_dashou_id and dashou_fencheng > 0:
+ dashou_user = UserMain.objects.filter(yonghuid=jiedan_dashou_id).first()
+ if dashou_user:
+ try:
+ dashou_ext = dashou_user.dashou_profile
+ dashou_ext.zonge = (dashou_ext.zonge or Decimal('0.00')) + dashou_fencheng
+ dashou_ext.yue = (dashou_ext.yue or Decimal('0.00')) + dashou_fencheng
+ dashou_ext.jinrishouyi = (dashou_ext.jinrishouyi or Decimal('0.00')) + dashou_fencheng
+ dashou_ext.jinyueshouyi = (dashou_ext.jinyueshouyi or Decimal('0.00')) + dashou_fencheng
+ dashou_ext.chengjiaozongliang = (dashou_ext.chengjiaozongliang or 0) + 1
+ dashou_ext.zhuangtai = 1
+ dashou_ext.save()
+ except UserDashou.DoesNotExist:
+ pass
+
+ # 10. 更新订单状态为已完成
+ order.zhuangtai = 3
+ order.save()
+
+ # 11. 打手每日统计(成交)
+ try:
+ update_dashou_daily_by_action(
+ yonghuid=jiedan_dashou_id,
+ amount=dashou_fencheng,
+ action=2 # 2=结算/成交
+ )
+ except Exception as e:
+ logger.error(f"打手每日统计失败: {e}")
+
+ # 12. 商品/店铺每日统计
+ try:
+ if shangpin_id:
+ # 从扩展表取已计算收益
+ pingtai_shouyi = Decimal('0.00')
+ dianpu_shouyi = Decimal('0.00')
+ if hasattr(order, 'pingtai_kuozhan'):
+ pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00')
+ dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00')
+
+ update_shangpin_daily_stat(
+ shangpin_id=shangpin_id,
+ caozuo_leixing=2,
+ dingdan_jiage=order.jine,
+ pingtai_shouyi=pingtai_shouyi,
+ dianpu_zongshouyi=dianpu_shouyi
+ )
+
+ # 店铺统计(含上级分红)
+ if hasattr(order, 'pingtai_kuozhan'):
+ dianpu_id = order.pingtai_kuozhan.dianpu_id
+ shangji_id = order.pingtai_kuozhan.shangji_dianpu_id
+ shangji_fen = order.pingtai_kuozhan.shangji_fenhong or Decimal('0.00')
+ if dianpu_id:
+ update_dianpu_daily_stat(
+ dianpu_id=dianpu_id,
+ caozuo_leixing=2,
+ dingdan_jiage=order.jine,
+ dianpu_shouyi=dianpu_shouyi,
+ shangji_dianpu_id=shangji_id,
+ shangji_fenhong=shangji_fen
+ )
+ except Exception as e:
+ logger.error(f"商品/店铺统计失败: {e}")
+
+ # 13. 平台收益流水(仅平台发单)
+ if order.fadan_pingtai == 1:
+ try:
+ from dingdan.utils import update_platform_profit # 导入函数
+ update_platform_profit(dingdan_id)
+ except Exception as e:
+ logger.error(f"平台收益更新失败: {e}")
+
+ # 14. 🆕 写入打手表现表(结算评价)
+ if jiedan_dashou_id:
+ record_dashou_biaoxian(
+ dingdan_id=dingdan_id,
+ dashou_id=jiedan_dashou_id,
+ xingwei=3, # 3=结算
+ fanche_cishu=fanche_cishu,
+ shangxian_sudu=shangxian_sudu,
+ fuwu_taidu=fuwu_taidu
+ # bankuai 暂不传入,后续可从订单商品关联
+ )
+
+ # 15. 返回成功
+ return Response({'code': 0, 'msg': '结单成功', 'data': None})
+
+ except Exception as e:
+ logger.error(f"结单事务异常: {e}", exc_info=True)
+ return Response({'code': 9, 'msg': '服务器内部错误,结单失败', 'data': None})
+
+
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+from django.db.models import Prefetch
+from dengji.models import Chenghao
+
+
+class ShangpinLeixingView(APIView):
+ """
+ 获取商家可用的商品类型列表(含关联板块的称号)
+ POST /dingdan/sjspleixing
+ """
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+
+ try:
+ # 1. 查询审核通过且抢单要求类型为1的商品类型,按排序权重升序
+ shangpin_queryset = ShangpinLeixing.objects.filter(
+ yaoqiuleixing=1,
+ shenhezhuangtai=1
+ ).select_related('bankuai').order_by('paixu')
+
+ # 2. 收集所有涉及到的板块ID
+ bankuai_ids = shangpin_queryset.values_list('bankuai_id', flat=True).distinct()
+
+ # 3. 查询这些板块下的所有称号(每个称号绑定到一个板块)
+ chenghao_list = Chenghao.objects.filter(
+ bankuai_id__in=bankuai_ids
+ ).values('id', 'mingcheng', 'texiao_miaoshu', 'bankuai_id')
+
+ # 4. 构建 板块ID -> 称号列表 的映射(确保准确关联)
+ bankuai_chenghao_map = {}
+ for ch in chenghao_list:
+ bid = ch['bankuai_id']
+ if bid not in bankuai_chenghao_map:
+ bankuai_chenghao_map[bid] = []
+ bankuai_chenghao_map[bid].append({
+ 'id': ch['id'],
+ 'mingcheng': ch['mingcheng'],
+ 'texiao_json': ch['texiao_miaoshu'] # 前端 chenghao-tag 组件需要
+ })
+
+ # 5. 组装最终数据
+ data_list = []
+ for sp in shangpin_queryset:
+ item = {
+ 'id': sp.id,
+ 'yaoqiuleixing': sp.yaoqiuleixing,
+ 'huiyuan_id': sp.huiyuan_id,
+ 'jieshao': sp.jieshao,
+ # 当前类型所属板块的称号列表(无板块则为空数组)
+ 'chenghaoList': bankuai_chenghao_map.get(sp.bankuai_id, [])
+ }
+ data_list.append(item)
+
+ return Response({
+ 'code': 200,
+ 'msg': '获取成功',
+ 'data': data_list
+ })
+
+ except Exception as e:
+ logger.error(f"获取商品类型失败: {str(e)}", exc_info=True)
+ return Response({'code': 500, 'msg': '服务器内部错误', 'data': {}}, status=500)
+
+import threading
+from utils.weixin_broadcast import WeixinBroadcastSender
+
+
+from dengji.models import DingdanBiaoqian,Chenghao
+
+
+import os
+import threading
+import logging
+from decimal import Decimal
+from django.db import transaction
+from django.db.models import F
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+from rest_framework import status
+
+from yonghu.models import UserMain, UserShangjia, UserDashou
+
+from houtai.utils import update_shangjia_daily
+
+
+# 导入 Celery 广播任务
+from dingdan.tongzhi_tasks import dingdan_guangbo
+
+logger = logging.getLogger(__name__)
+
+
+"""class ShangjiaPaifaView(APIView):
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ logger.info(f"商家派单请求: user_id={request.user.yonghuid}, data={request.data}")
+
+ try:
+ data = request.data
+
+ # ----- 1. 获取原始参数(保持原类型)-----
+ shangpin_type_id = data.get('shangpinTypeId')
+ huiyuan_id = data.get('huiyuanId')
+ yaoqiuleixing = data.get('yaoqiuleixing')
+ dingdan_jieshao_raw = data.get('dingdanJieshao', '')
+ dingdan_beizhu_raw = data.get('dingdanBeizhu', '')
+ laoban_name_raw = data.get('laobanName', '')
+ zhiding_uid_raw = data.get('zhidingUid', '')
+ jiage_str = data.get('jiage')
+
+ label_id = data.get('labelId') # 整数或 None
+ commission_enabled = data.get('commissionEnabled', False)
+ commission_value_raw = data.get('commissionValue')
+
+ # ----- 2. 字符串字段安全处理 -----
+ dingdan_jieshao = str(dingdan_jieshao_raw).strip()
+ dingdan_beizhu = str(dingdan_beizhu_raw).strip()
+ laoban_name = str(laoban_name_raw).strip()
+ zhiding_uid = str(zhiding_uid_raw).strip()
+ commission_value_str = str(commission_value_raw).strip() if commission_value_raw is not None else ''
+
+ # ----- 3. 必填校验 -----
+ if not all([shangpin_type_id, huiyuan_id, dingdan_jieshao, laoban_name, jiage_str]):
+ logger.warning(f"参数缺失: {request.data}")
+ return Response({'code': 400, 'msg': '缺少必填参数'})
+
+ # ----- 4. 价格验证 -----
+ try:
+ jiage = float(jiage_str)
+ if jiage < 1 or jiage > 10000:
+ return Response({'code': 400, 'msg': '价格需在1-10000元之间'})
+ if '.' in str(jiage_str) and len(str(jiage_str).split('.')[1]) > 2:
+ return Response({'code': 400, 'msg': '最多支持两位小数'})
+ except ValueError:
+ return Response({'code': 400, 'msg': '价格格式错误'})
+
+ # ----- 5. 商品类型 -----
+ try:
+ shangpin_type = ShangpinLeixing.objects.get(id=shangpin_type_id)
+ except ShangpinLeixing.DoesNotExist:
+ logger.error(f"商品类型不存在: {shangpin_type_id}")
+ return Response({'code': 400, 'msg': '商品类型不存在'})
+
+ # ----- 6. 押金 / 会员模式决定(修正版)-----
+ # 先根据商品类型中的配置填充默认值
+ final_yaoqiu_type = shangpin_type.yaoqiuleixing if shangpin_type.yaoqiuleixing else 1
+ final_yongjin = shangpin_type.yongjin if shangpin_type.yongjin else None
+ final_huiyuan_id = shangpin_type.huiyuan_id if shangpin_type.huiyuan_id else ''
+
+ # 如果前端明确开启了佣金且金额大于0,则覆盖为押金模式
+ if commission_enabled and commission_value_str:
+ try:
+ cv = float(commission_value_str)
+ if cv > 0:
+ final_yaoqiu_type = 2
+ final_yongjin = Decimal(str(cv))
+ final_huiyuan_id = ''
+ logger.info(f"前端开启押金模式: 佣金={cv}")
+ else:
+ # 佣金金额为0,视为无效,保持商品类型默认
+ logger.warning("前端传佣金为0,无效,使用商品类型默认")
+ except ValueError:
+ logger.warning("佣金格式错误,使用商品类型默认")
+ else:
+ logger.info(f"使用商品类型默认模式: 类型={final_yaoqiu_type}, huiyuan_id={final_huiyuan_id}, yongjin={final_yongjin}")
+
+ # ----- 7. 称号验证(label_id 是整数或 None)-----
+ chenghao_obj = None
+ if label_id is not None:
+ try:
+ chenghao_obj = Chenghao.objects.get(id=int(label_id))
+ logger.info(f"订单将关联称号: {chenghao_obj.mingcheng}")
+ except (Chenghao.DoesNotExist, ValueError, TypeError):
+ logger.warning(f"称号不存在或ID无效: {label_id}")
+ return Response({'code': 400, 'msg': '所选标签不存在'})
+
+ # ----- 8. 商家信息 -----
+ try:
+ shangjia = request.user.shop_profile
+ except UserShangjia.DoesNotExist:
+ logger.warning(f"用户不是商家: {request.user.yonghuid}")
+ return Response({'code': 403, 'msg': '您不是商家'})
+
+ if shangjia.zhuangtai != 1:
+ logger.warning(f"商家状态异常: {shangjia.zhuangtai}")
+ return Response({'code': 403, 'msg': '商家状态异常'})
+
+ if jiage > shangjia.yue:
+ logger.warning(f"余额不足: {shangjia.yue} < {jiage}")
+ return Response({'code': 400, 'msg': f'余额不足,当前余额{shangjia.yue}元'})
+
+ # ----- 9. 指定打手 -----
+ zhiding_dashou = None
+ if zhiding_uid:
+ try:
+ zhiding_user = UserMain.objects.get(yonghuid=zhiding_uid)
+ zhiding_dashou = zhiding_user.dashou_profile
+ if zhiding_dashou.zhanghaozhuangtai != 1:
+ return Response({'code': 400, 'msg': '指定打手状态异常'})
+ except UserMain.DoesNotExist:
+ return Response({'code': 400, 'msg': '指定用户不存在'})
+ except UserDashou.DoesNotExist:
+ return Response({'code': 400, 'msg': '指定用户不是打手'})
+
+ # ----- 10. 利率 -----
+ try:
+ lilu_obj = Lilubiao.objects.get(fadanpingtai='3')
+ lilu = float(lilu_obj.lilu) if lilu_obj.lilu > 0 else 1
+ except Lilubiao.DoesNotExist:
+ lilu = 1
+ dashou_fencheng = round(jiage * lilu, 2)
+
+ # ----- 11. 生成订单ID -----
+ def generate_dingdan_id():
+ import time, random
+ ts = int(time.time() * 1000)
+ pid = os.getpid() % 1000
+ rnd = random.randint(1000, 9999)
+ return f"SJ{ts}{pid:03d}{rnd}"
+
+ # ----- 12. 事务:创建订单、更新商家统计 -----
+ dingdan = None
+ try:
+ with transaction.atomic():
+ dingdan = Dingdan.objects.create(
+ dingdan_id=generate_dingdan_id(),
+ zhuangtai=7 if zhiding_dashou else 1,
+ fadan_pingtai=2,
+ jine=jiage,
+ dashou_fencheng=dashou_fencheng,
+ zhiding_id=zhiding_uid if zhiding_dashou else '',
+ shangpin_id=0,
+ leixing_id=int(shangpin_type_id),
+ yaoqiuleixing=final_yaoqiu_type,
+ yongjin=final_yongjin,
+ huiyuan_id=final_huiyuan_id,
+ jieshao=dingdan_jieshao[:2000],
+ beizhu=dingdan_beizhu[:1000],
+ nicheng=laoban_name[:50],
+ tupian=request.user.avatar,
+ user1_id=f"Sj{request.user.yonghuid}"
+ )
+ logger.info(f"订单主表创建: {dingdan.dingdan_id}")
+
+ DingdanShangjia.objects.create(
+ dingdan=dingdan,
+ shangjia_id=request.user.yonghuid,
+ sjnicheng=shangjia.nicheng,
+ )
+
+ if chenghao_obj:
+ DingdanBiaoqian.objects.create(dingdan=dingdan, chenghao=chenghao_obj)
+ logger.info(f"订单关联称号: {chenghao_obj.mingcheng}")
+
+ # 更新商家统计
+ UserShangjia.objects.filter(id=shangjia.id).update(
+ fabu=F('fabu') + 1,
+ yue=F('yue') - jiage,
+ jinridingdan=F('jinridingdan') + 1,
+ jinriliushui=F('jinriliushui') + jiage,
+ jinyuedingdan=F('jinyuedingdan') + 1,
+ jinyueliushui=F('jinyueliushui') + jiage,
+ )
+
+ update_shangjia_daily(
+ yonghuid=request.user.yonghuid,
+ amount=Decimal(str(jiage)),
+ action=1
+ )
+ # 事务提交,订单已持久化
+ except Exception as e:
+ logger.error(f"订单创建事务失败: {str(e)}", exc_info=True)
+ return Response({'code': 500, 'msg': '订单创建失败,请稍后重试'})
+
+ # ========== 以下操作均在事务外,避免异步任务查不到订单 ==========
+ try:
+ sync_order_to_partners(dingdan.dingdan_id)
+ except Exception as e:
+ logger.error(f"跨平台同步失败: {e}", exc_info=True)
+
+ try:
+ order_info = {
+ 'dingdan_id': dingdan.dingdan_id,
+ 'game_type': self._get_game_type_name(dingdan.leixing_id),
+ 'amount': str(dingdan.jine),
+ 'order_desc': (dingdan.jieshao or '')[:50],
+ }
+ dingdan_guangbo.delay(order_info)
+ logger.info(f"已提交广播任务: {dingdan.dingdan_id}")
+ except Exception as e:
+ logger.error(f"广播任务提交失败: {e}", exc_info=True)
+
+ logger.info(f"商家派单成功: {dingdan.dingdan_id}, 金额={jiage}")
+ return Response({'code': 200, 'msg': '派单成功', 'data': {'jine': str(dingdan.jine)}})
+
+ except Exception as e:
+ logger.error(f"商家派单接口未捕获异常: {str(e)}", exc_info=True)
+ return Response({'code': 500, 'msg': '系统内部错误'})
+
+ def _get_game_type_name(self, leixing_id):
+ try:
+ gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
+ return gt.jieshao if gt else '游戏订单'
+ except Exception:
+ return '游戏订单'"""
+
+
+logger = logging.getLogger(__name__)
+
+
+class ShangjiaPaifaView(APIView):
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ logger.info(f"商家派单请求: user_id={request.user.yonghuid}, data={request.data}")
+
+ try:
+ data = request.data
+
+ # ----- 1. 获取原始参数(保持原类型)-----
+ shangpin_type_id = data.get('shangpinTypeId')
+ huiyuan_id = data.get('huiyuanId')
+ yaoqiuleixing = data.get('yaoqiuleixing')
+ dingdan_jieshao_raw = data.get('dingdanJieshao', '')
+ dingdan_beizhu_raw = data.get('dingdanBeizhu', '')
+ laoban_name_raw = data.get('laobanName', '')
+ zhiding_uid_raw = data.get('zhidingUid', '')
+ jiage_str = data.get('jiage')
+
+ label_id = data.get('labelId') # 整数或 None
+ commission_enabled = data.get('commissionEnabled', False)
+ commission_value_raw = data.get('commissionValue')
+
+ # ----- 2. 字符串字段安全处理 -----
+ dingdan_jieshao = str(dingdan_jieshao_raw).strip()
+ dingdan_beizhu = str(dingdan_beizhu_raw).strip()
+ laoban_name = str(laoban_name_raw).strip()
+ zhiding_uid = str(zhiding_uid_raw).strip()
+ commission_value_str = str(commission_value_raw).strip() if commission_value_raw is not None else ''
+
+ # ----- 3. 必填校验 -----
+ if not all([shangpin_type_id, dingdan_jieshao, laoban_name, jiage_str]):
+ logger.warning(f"参数缺失: {request.data}")
+ return Response({'code': 400, 'msg': '缺少必填参数'})
+
+ # ----- 4. 价格验证 -----
+ try:
+ jiage = float(jiage_str)
+ if jiage < 1 or jiage > 10000:
+ return Response({'code': 400, 'msg': '价格需在1-10000元之间'})
+ if '.' in str(jiage_str) and len(str(jiage_str).split('.')[1]) > 2:
+ return Response({'code': 400, 'msg': '最多支持两位小数'})
+ except ValueError:
+ return Response({'code': 400, 'msg': '价格格式错误'})
+
+ # ----- 5. 商品类型 -----
+ try:
+ shangpin_type = ShangpinLeixing.objects.get(id=shangpin_type_id)
+ except ShangpinLeixing.DoesNotExist:
+ logger.error(f"商品类型不存在: {shangpin_type_id}")
+ return Response({'code': 400, 'msg': '商品类型不存在'})
+
+ # ----- 6. 押金 / 会员模式决定 -----
+ final_yongjin = None
+ if commission_enabled and commission_value_str:
+ try:
+ cv = float(commission_value_str)
+ if cv > 0:
+ final_yaoqiu_type = 2
+ final_yongjin = Decimal(str(cv))
+ final_huiyuan_id = ''
+ logger.info(f"押金模式: 佣金={cv}")
+ else:
+ final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
+ final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
+ logger.warning("佣金为0,降级为会员模式")
+ except ValueError:
+ final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
+ final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
+ logger.warning("佣金格式错误,降级为会员模式")
+ else:
+ final_yaoqiu_type = shangpin_type.yaoqiuleixing or 1
+ final_huiyuan_id = str(huiyuan_id) if huiyuan_id else ''
+ logger.info(f"会员模式: 类型={final_yaoqiu_type}")
+
+ # ----- 7. 称号(可选:不传/空值/无效/不存在均视为无标签)-----
+ chenghao_obj = None
+ label_id_str = str(label_id).strip() if label_id is not None else ''
+ if label_id_str and label_id_str not in ('0', 'null', 'undefined'):
+ try:
+ lid = int(label_id_str)
+ if lid > 0:
+ chenghao_obj = Chenghao.objects.filter(id=lid).first()
+ if chenghao_obj:
+ logger.info(f"订单将关联称号: {chenghao_obj.mingcheng}")
+ else:
+ logger.info(f"标签不存在,按无标签派单: {label_id}")
+ except (ValueError, TypeError):
+ logger.info(f"标签ID无效,按无标签派单: {label_id}")
+
+ # ----- 8. 商家信息 -----
+ try:
+ shangjia = request.user.shop_profile
+ except UserShangjia.DoesNotExist:
+ logger.warning(f"用户不是商家: {request.user.yonghuid}")
+ return Response({'code': 403, 'msg': '您不是商家'})
+
+ if shangjia.zhuangtai != 1:
+ logger.warning(f"商家状态异常: {shangjia.zhuangtai}")
+ return Response({'code': 403, 'msg': '商家状态异常'})
+
+ if jiage > shangjia.yue:
+ logger.warning(f"余额不足: {shangjia.yue} < {jiage}")
+ return Response({'code': 400, 'msg': f'余额不足,当前余额{shangjia.yue}元'})
+
+ # ----- 9. 指定打手 -----
+ zhiding_dashou = None
+ if zhiding_uid:
+ try:
+ zhiding_user = UserMain.objects.get(yonghuid=zhiding_uid)
+ zhiding_dashou = zhiding_user.dashou_profile
+ if zhiding_dashou.zhanghaozhuangtai != 1:
+ return Response({'code': 400, 'msg': '指定打手状态异常'})
+ except UserMain.DoesNotExist:
+ return Response({'code': 400, 'msg': '指定用户不存在'})
+ except UserDashou.DoesNotExist:
+ return Response({'code': 400, 'msg': '指定用户不是打手'})
+
+ # ----- 10. 利率(打手+管事分成,打手优先) -----
+ from dingdan.utils import calc_shangjia_order_fencheng
+ dashou_fencheng, guanshi_fencheng = calc_shangjia_order_fencheng(jiage)
+
+ # ----- 11. 生成订单ID -----
+ def generate_dingdan_id():
+ import time, random
+ ts = int(time.time() * 1000)
+ pid = os.getpid() % 1000
+ rnd = random.randint(1000, 9999)
+ return f"SJ{ts}{pid:03d}{rnd}"
+
+ # ----- 12. 事务:创建订单、更新商家统计 -----
+ dingdan = None
+ try:
+ with transaction.atomic():
+ dingdan = Dingdan.objects.create(
+ dingdan_id=generate_dingdan_id(),
+ zhuangtai=7 if zhiding_dashou else 1,
+ fadan_pingtai=2,
+ jine=jiage,
+ dashou_fencheng=dashou_fencheng,
+ guanshi_fencheng=guanshi_fencheng,
+ zhiding_id=zhiding_uid if zhiding_dashou else '',
+ shangpin_id=0,
+ leixing_id=int(shangpin_type_id),
+ yaoqiuleixing=final_yaoqiu_type,
+ yongjin=final_yongjin,
+ huiyuan_id=final_huiyuan_id,
+ jieshao=dingdan_jieshao[:2000],
+ beizhu=dingdan_beizhu[:1000],
+ nicheng=laoban_name[:50],
+ tupian=request.user.avatar,
+ user1_id=f"Sj{request.user.yonghuid}"
+ )
+ logger.info(f"订单主表创建: {dingdan.dingdan_id}")
+
+ DingdanShangjia.objects.create(
+ dingdan=dingdan,
+ shangjia_id=request.user.yonghuid,
+ sjnicheng=shangjia.nicheng,
+ )
+
+ if chenghao_obj:
+ DingdanBiaoqian.objects.create(dingdan=dingdan, chenghao=chenghao_obj)
+ logger.info(f"订单关联称号: {chenghao_obj.mingcheng}")
+
+ # 更新商家统计
+ UserShangjia.objects.filter(id=shangjia.id).update(
+ fabu=F('fabu') + 1,
+ yue=F('yue') - jiage,
+ jinridingdan=F('jinridingdan') + 1,
+ jinriliushui=F('jinriliushui') + jiage,
+ jinyuedingdan=F('jinyuedingdan') + 1,
+ jinyueliushui=F('jinyueliushui') + jiage,
+ )
+
+ update_shangjia_daily(
+ yonghuid=request.user.yonghuid,
+ amount=Decimal(str(jiage)),
+ action=1
+ )
+ # 事务提交,订单已持久化
+ except Exception as e:
+ logger.error(f"订单创建事务失败: {str(e)}", exc_info=True)
+ return Response({'code': 500, 'msg': '订单创建失败,请稍后重试'})
+
+ # ========== 以下操作均在事务外,避免异步任务查不到订单 ==========
+ # 跨平台同步(内部可能是异步任务,但函数本身是同步调用,现在订单已存在)
+ try:
+ sync_order_to_partners(dingdan.dingdan_id)
+ except Exception as e:
+ logger.error(f"跨平台同步失败: {e}", exc_info=True)
+
+ # Celery 广播任务(异步)
+ try:
+ order_info = {
+ 'dingdan_id': dingdan.dingdan_id,
+ 'game_type': self._get_game_type_name(dingdan.leixing_id),
+ 'amount': str(dingdan.jine),
+ 'order_desc': (dingdan.jieshao or '')[:50],
+ }
+ dingdan_guangbo.delay(order_info)
+ logger.info(f"已提交广播任务: {dingdan.dingdan_id}")
+ except Exception as e:
+ logger.error(f"广播任务提交失败: {e}", exc_info=True)
+
+ logger.info(f"商家派单成功: {dingdan.dingdan_id}, 金额={jiage}")
+ return Response({'code': 200, 'msg': '派单成功', 'data': {'jine': str(dingdan.jine)}})
+
+ except Exception as e:
+ logger.error(f"商家派单接口未捕获异常: {str(e)}", exc_info=True)
+ return Response({'code': 500, 'msg': '系统内部错误'})
+
+ def _get_game_type_name(self, leixing_id):
+ try:
+ gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
+ return gt.jieshao if gt else '游戏订单'
+ except Exception:
+ return '游戏订单'
+
+# apps/dingdan/views.py
+from django.db.models import Q, Count
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.permissions import IsAuthenticated
+from .models import Dingdan, DingdanShangjia
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+# views.py 中 ShangjiaDingdanHuoquView 完整改造
+
+class ShangjiaDingdanHuoquView(APIView):
+ """
+ 商家订单获取接口
+ POST /dingdan/sjdingdanhq
+ 需要JWT认证
+ 支持分页、状态筛选、商品类型筛选、模糊搜索(订单ID/订单介绍/游戏昵称/打手ID)
+ 返回待结算订单数量(pending_count)
+ """
+
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 1. 获取当前登录用户的yonghuid
+ try:
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+ except AttributeError:
+ return Response({
+ 'code': 1,
+ 'msg': '用户认证失败',
+ 'data': None
+ }, status=status.HTTP_401_UNAUTHORIZED)
+
+ # 2. 解析参数
+ try:
+ data = request.data
+ zhuangtai_list = data.get('zhuangtai_list', [])
+ page = max(1, int(data.get('page', 1)))
+ page_size = min(100, max(1, int(data.get('page_size', 10))))
+
+ # 🆕 商品类型筛选
+ leixing_id = data.get('leixing_id', None)
+
+ # 🆕 模糊搜索关键字(支持订单ID、订单介绍、游戏昵称、打手ID)
+ keyword = data.get('keyword', '').strip()
+ except (ValueError, TypeError):
+ return Response({
+ 'code': 2,
+ 'msg': '参数格式错误',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 3. 验证状态列表
+ if not isinstance(zhuangtai_list, list) or len(zhuangtai_list) == 0:
+ return Response({
+ 'code': 3,
+ 'msg': '状态列表不能为空',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ # 4. 构建基础查询(该商家的所有订单)
+ base_qs = DingdanShangjia.objects.filter(
+ shangjia_id=yonghuid
+ ).select_related('dingdan')
+
+ # 5. 状态过滤
+ base_qs = base_qs.filter(dingdan__zhuangtai__in=zhuangtai_list)
+
+ # 🆕 6. 商品类型筛选
+ if leixing_id is not None:
+ try:
+ leixing_id_int = int(leixing_id)
+ base_qs = base_qs.filter(dingdan__leixing_id=leixing_id_int)
+ except (ValueError, TypeError):
+ # 如果leixing_id不是有效整数,忽略该筛选
+ pass
+
+ # 🆕 7. 模糊搜索(支持订单ID、订单介绍、游戏昵称、打手ID)
+ if keyword:
+ from django.db.models import Q
+ # 订单ID精确匹配(dingdan_id有唯一索引,先用精确匹配)
+ dingdan_id_match = Q(dingdan__dingdan_id__exact=keyword)
+ # 订单介绍模糊匹配
+ jieshao_match = Q(dingdan__jieshao__icontains=keyword)
+ # 游戏昵称模糊匹配
+ nicheng_match = Q(dingdan__nicheng__icontains=keyword)
+ # 打手ID精确匹配
+ dashou_match = Q(dingdan__jiedan_dashou_id__exact=keyword)
+
+ base_qs = base_qs.filter(
+ dingdan_id_match | jieshao_match | nicheng_match | dashou_match
+ )
+
+ # 8. 计算待结算订单数量(状态为8)
+ pending_count = DingdanShangjia.objects.filter(
+ shangjia_id=yonghuid,
+ dingdan__zhuangtai=8
+ ).count()
+
+ # 9. 排序、分页
+ total_count = base_qs.count()
+ start_index = (page - 1) * page_size
+ end_index = start_index + page_size
+ current_page_orders = base_qs.order_by('-dingdan__create_time')[start_index:end_index]
+
+ # 10. 构建返回数据
+ order_list = []
+ for shangjia_order in current_page_orders:
+ order = shangjia_order.dingdan
+ order_data = {
+ 'dingdan_id': order.dingdan_id,
+ 'zhuangtai': order.zhuangtai,
+ 'jine': float(order.jine) if order.jine else 0.0,
+ 'jieshao': order.jieshao or '',
+ 'beizhu': order.beizhu or '',
+ 'nicheng': order.nicheng or '',
+ 'leixing_id': order.leixing_id, # 🆕 返回商品类型ID
+ 'tupian': order.tupian or '', # 🆕 返回商品图片
+ 'jiedan_dashou_id': order.jiedan_dashou_id or '', # 🆕 返回打手ID
+ 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else ''
+ }
+ order_list.append(order_data)
+
+ # 11. 判断是否还有更多
+ current_page_count = len(current_page_orders)
+ has_more = current_page_count == page_size and end_index < total_count
+
+ return Response({
+ 'code': 0,
+ 'msg': '成功',
+ 'data': {
+ 'list': order_list,
+ 'has_more': has_more,
+ 'total': total_count,
+ 'pending_count': pending_count
+ }
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ logger.error(f"商家订单获取失败: {str(e)}", exc_info=True)
+ return Response({
+ 'code': 4,
+ 'msg': '服务器内部错误',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+from dingdan.models import Dingdan, DingdanShangjia, Dashoutupian, Fadan
+
+from .models import FadanFenhong # 注意:fadan_fenhong 可能在其他 app 中,根据实际路径调整
+
+logger = logging.getLogger(__name__)
+
+
+class ShangjiaDingdanXiangqingView(APIView):
+ """
+ 商家订单详情接口
+ 返回订单基础信息、打手信息、交付图片、罚款数据、分红利率、更换打手产生的新订单ID等
+ """
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 1. 获取当前登录用户的 yonghuid
+ try:
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+ except AttributeError:
+ return Response({
+ 'code': 1,
+ 'msg': '用户认证失败',
+ 'data': None
+ }, status=status.HTTP_401_UNAUTHORIZED)
+
+ # 2. 获取前端参数
+ try:
+ data = request.data
+ dingdan_id = data.get('dingdan_id', '').strip()
+ except Exception:
+ return Response({
+ 'code': 2,
+ 'msg': '参数格式错误',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ if not dingdan_id:
+ return Response({
+ 'code': 3,
+ 'msg': '订单ID不能为空',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ # 3. 查询商家订单扩展表,验证权限并获取基础订单数据
+ shangjia_kuozhan = DingdanShangjia.objects.filter(
+ shangjia_id=yonghuid,
+ dingdan__dingdan_id=dingdan_id
+ ).select_related('dingdan').first()
+
+ if not shangjia_kuozhan:
+ return Response({
+ 'code': 4,
+ 'msg': '订单不存在或您无权限查看',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ order = shangjia_kuozhan.dingdan
+
+ # 4. 交付图片列表
+ dashou_tupian_list = Dashoutupian.objects.filter(
+ dingdan_id=dingdan_id
+ ).order_by('create_time').values_list('tupian', flat=True)
+ dashou_jiaofu = list(dashou_tupian_list)
+
+ # 5. 打手详细信息(头像、昵称、用户ID)
+ dashou_yonghuid = order.jiedan_dashou_id or ''
+ dashou_nicheng = ''
+ dashou_avatar = ''
+ if dashou_yonghuid:
+ dashou_user = UserMain.objects.filter(
+ yonghuid=dashou_yonghuid
+ ).select_related('dashou_profile').first()
+ if dashou_user:
+ dashou_avatar = dashou_user.avatar or ''
+ if hasattr(dashou_user, 'dashou_profile') and dashou_user.dashou_profile:
+ dashou_nicheng = dashou_user.dashou_profile.nicheng or ''
+
+ # 6. 罚款数据(如果存在)
+ fadan_obj = Fadan.objects.filter(
+ guanliandingdan_id=dingdan_id,
+ beichufa_id=dashou_yonghuid
+ ).first()
+ fadan_dict = None
+ if fadan_obj:
+ fadan_dict = {
+ 'id': fadan_obj.id,
+ 'chufaliyou': fadan_obj.chufaliyou or '',
+ 'fakuanjine': float(fadan_obj.fakuanjine) if fadan_obj.fakuanjine else 0.0,
+ 'yingxiang_qiangdan': fadan_obj.yingxiang_qiangdan,
+ 'zhuangtai': fadan_obj.zhuangtai,
+ 'shensuliyou': fadan_obj.shensuliyou or '',
+ 'tongyi_chufaliyou': fadan_obj.tongyi_chufaliyou or '',
+ 'bohuiliyou': fadan_obj.bohuiliyou or '',
+ 'create_time': fadan_obj.create_time.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.create_time else '',
+ 'update_time': fadan_obj.update_time.strftime('%Y-%m-%d %H:%M:%S') if fadan_obj.update_time else '',
+ }
+
+ # 7. 商家自己的分红利率(用于罚款分红计算)
+ fenhong_lilv = 0.0
+ try:
+ # 查找商家作为分红者的最新记录,取应得利率
+ last_fenhong = FadanFenhong.objects.filter(
+ fenhongzhe_id=yonghuid,
+ shenfen=1 # 1=商家
+ ).order_by('-create_time').first()
+ if last_fenhong:
+ fenhong_lilv = float(last_fenhong.yingde_lilv or 0)
+ except Exception as e:
+ logger.warning(f'获取分红利率失败: {e}')
+
+ # 8. 更换打手后产生的新订单ID(如果当前订单被更换过)
+ xin_dingdan_id = ''
+ try:
+ # 查找以当前订单作为“上一个订单ID”的新订单扩展记录
+ new_shangjia_order = DingdanShangjia.objects.filter(
+ shangyige_dingdan_id=dingdan_id,
+ shangjia_id=yonghuid
+ ).select_related('dingdan').first()
+ if new_shangjia_order:
+ xin_dingdan_id = new_shangjia_order.dingdan.dingdan_id
+ except Exception as e:
+ logger.warning(f'查找新订单ID失败: {e}')
+
+ # 9. 构建返回数据
+ response_data = {
+ # 基础订单字段
+ 'dingdan_id': order.dingdan_id,
+ 'zhuangtai': order.zhuangtai or 0,
+ 'jine': float(order.jine) if order.jine else 0.0,
+ 'jieshao': order.jieshao or '',
+ 'beizhu': order.beizhu or '',
+ 'nicheng': order.nicheng or '',
+ 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else '',
+
+ # 打手信息
+ 'dashou_yonghuid': dashou_yonghuid,
+ 'dashou_nicheng': dashou_nicheng,
+ 'dashou_avatar': dashou_avatar,
+
+ # 服务详情
+ 'fuwudashou_id': dashou_yonghuid, # 保留旧字段兼容
+ 'dashou_liuyan': order.dashou_liuyan or '',
+ 'dashou_jiaofu': dashou_jiaofu,
+
+ # 商家扩展字段(旧)
+ 'shangjia_liuyan': shangjia_kuozhan.shangjia_pingjia or '',
+ 'tkliyou': order.tkly or '',
+ 'cfliyou': shangjia_kuozhan.cfliyou or '', # 保留,但不影响新罚款
+ 'sqzhuangtai': shangjia_kuozhan.sqzhuangtai or 0,
+ 'bhliyou': shangjia_kuozhan.bhliyou or '',
+ 'chufa_jieguo': '', # 已弃用
+
+ # 新增:罚款数据
+ 'fadan': fadan_dict,
+
+ # 新增:分红利率
+ 'fenhong_lilv': fenhong_lilv,
+
+ # 新增:更换打手后的新订单ID
+ 'xin_dingdan_id': xin_dingdan_id,
+ }
+
+ return Response({
+ 'code': 0,
+ 'msg': '成功',
+ 'data': response_data
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ logger.error(f"商家订单详情查询失败: {str(e)}", exc_info=True)
+ return Response({
+ 'code': 5,
+ 'msg': '服务器内部错误',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+from dingdan.models import Dingdan, DingdanShangjia, PreSettlement
+
+from dingdan.utils import update_daily_dispatch_stat
+
+
+logger = logging.getLogger(__name__)
+
+from houtai.utils import update_shangjia_daily
+class ShangjiaJiesuanView(APIView):
+ """商家结单接口(支持跨平台订单)"""
+ permission_classes = [permissions.IsAuthenticated]
+
+ def post(self, request):
+ # 1. 获取当前登录用户
+ try:
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+
+ try:
+ shangjia = current_user.shop_profile
+ except UserShangjia.DoesNotExist:
+ return Response({
+ 'code': 1,
+ 'msg': '您不是商家或商家信息未完善',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ except AttributeError:
+ return Response({
+ 'code': 1,
+ 'msg': '用户认证失败',
+ 'data': None
+ }, status=status.HTTP_401_UNAUTHORIZED)
+
+ # 2. 获取前端参数
+ try:
+ data = request.data
+ dingdan_id = data.get('dingdan_id', '').strip()
+ pingfen = int(data.get('pingfen', 0))
+ liuyan = data.get('liuyan', '').strip()
+ except (ValueError, TypeError):
+ return Response({
+ 'code': 2,
+ 'msg': '参数格式错误',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 3. 参数验证
+ if not dingdan_id:
+ return Response({
+ 'code': 3,
+ 'msg': '订单ID不能为空',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ if pingfen < 0 or pingfen > 5:
+ return Response({
+ 'code': 4,
+ 'msg': '评分必须在0-5分之间',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ with transaction.atomic():
+ # 4. 查询订单并加锁(修复并发问题)
+ order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)
+
+ # 验证订单是否属于当前商家
+ shangjia_kuozhan = DingdanShangjia.objects.filter(
+ dingdan=order,
+ shangjia_id=yonghuid
+ ).first()
+
+ if not shangjia_kuozhan:
+ return Response({
+ 'code': 5,
+ 'msg': '订单不存在或您无权限操作',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 5. 验证订单状态
+ if order.zhuangtai != 8:
+ return Response({
+ 'code': 6,
+ 'msg': '订单不在结算状态,无法结单',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 获取订单字段
+ jine = order.jine or Decimal('0')
+ dashou_fencheng = order.dashou_fencheng or Decimal('0')
+ jiedan_dashou_id = order.jiedan_dashou_id
+ dispatch_type = order.dispatch_type # 1=我方派单,2=对方派单
+ is_cross = order.is_cross
+ partner_club_id = order.partner_club_id
+ partner_order_id = order.partner_order_id
+
+ if not jiedan_dashou_id:
+ return Response({
+ 'code': 7,
+ 'msg': '订单没有接单打手,无法结单',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # ========== 跨平台判断 ==========
+ is_cross_claimed_by_partner = (is_cross == 1 and (partner_club_id or partner_order_id))
+
+ # ========== 根据订单类型处理 ==========
+ # 情况1:对方派单(我方打手接单)-> 预结算,不直接给打手加钱
+ if dispatch_type == 2:
+ # 创建预结算记录
+ PreSettlement.objects.update_or_create(
+ dingdan_id=dingdan_id,
+ defaults={
+ 'partner_order_id': partner_order_id,
+ 'partner_club_id': partner_club_id or '000001',
+ 'dashou_id': jiedan_dashou_id,
+ 'amount': dashou_fencheng,
+ 'order_amount': jine,
+ 'status': 0,
+ 'operator': request.user.phone,
+ }
+ )
+ # 更新订单状态
+ order.zhuangtai = 3
+ order.save()
+
+ # 更新统计表
+ update_daily_dispatch_stat(
+ direction=2,
+ partner_club_id=partner_club_id or '000001',
+ success_amount=jine,
+ success_count=1
+ )
+
+ # 情况2:我方派单且被对方打手接单 -> 只改状态,不给本地打手加钱
+ elif dispatch_type == 1 and is_cross_claimed_by_partner:
+ order.zhuangtai = 3
+ order.save()
+
+ update_daily_dispatch_stat(
+ direction=1,
+ partner_club_id=partner_club_id or '000001',
+ success_amount=dashou_fencheng,
+ success_count=1
+ )
+
+ # 情况3:本地订单 或 跨平台但自家打手接单 -> 正常结算给打手
+ else:
+ # 查询打手信息
+ try:
+ dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
+ dashou = dashou_user.dashou_profile
+ except (UserMain.DoesNotExist, UserDashou.DoesNotExist):
+ return Response({
+ 'code': 8,
+ 'msg': '接单打手不存在或信息不完整',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 原子更新打手数据(使用 F 表达式)
+ UserDashou.objects.filter(id=dashou.id).update(
+ chengjiaozongliang=F('chengjiaozongliang') + 1,
+ yue=F('yue') + dashou_fencheng,
+ zonge=F('zonge') + dashou_fencheng,
+ jinrishouyi=F('jinrishouyi') + dashou_fencheng,
+ jinyueshouyi=F('jinyueshouyi') + dashou_fencheng,
+ )
+
+ # 更新商家成交单量
+ UserShangjia.objects.filter(id=shangjia.id).update(
+ chengjiao=F('chengjiao') + 1,
+ )
+
+ # 更新订单状态
+ order.zhuangtai = 3
+ order.save()
+
+ # 处理评分(所有情况都处理)
+ if pingfen > 0:
+ pingfen_obj, created = Pingfen.objects.get_or_create(
+ dashou=jiedan_dashou_id,
+ defaults={'zongfen': pingfen, 'cishu': 1}
+ )
+ if not created:
+ Pingfen.objects.filter(id=pingfen_obj.id).update(
+ zongfen=F('zongfen') + pingfen,
+ cishu=F('cishu') + 1,
+ )
+
+ # 更新商家订单扩展表的评价
+ DingdanShangjia.objects.filter(id=shangjia_kuozhan.id).update(
+ shangjia_pingjia=liuyan,
+ )
+
+ # 🔥 新增:调用打手每日统计(成交)
+ try:
+ update_dashou_daily_by_action(
+ yonghuid=jiedan_dashou_id,
+ amount=dashou_fencheng,
+ action=2 # 2 表示成交(结算)
+ )
+ logger.info(f"打手 {jiedan_dashou_id} 每日统计更新成功")
+ except Exception as e:
+ logger.error(f"打手每日统计更新失败: {e}")
+ # 不影响主流程
+
+ # ✅ 新方法(行为驱动,更安全)
+ update_shangjia_daily(
+ yonghuid=request.user.yonghuid,
+ amount=Decimal(str(jine)),
+ action=2 # 2 = 结算
+ )
+
+ # 商家订单管事分红(仅 fadan_pingtai=2)
+ if order.fadan_pingtai == 2:
+ from dingdan.utils import settle_shangjia_order_guanshi_fenhong
+ settle_shangjia_order_guanshi_fenhong(order, jiedan_dashou_id)
+
+ # 返回成功
+ return Response({
+ 'code': 0,
+ 'msg': '结单成功',
+ 'data': {
+ 'dingdan_id': dingdan_id,
+ 'new_zhuangtai': 3,
+ 'jine': float(jine),
+ 'dashou_fencheng': float(dashou_fencheng),
+ 'jiedan_dashou_id': jiedan_dashou_id,
+ 'pingfen': pingfen,
+ 'liuyan': liuyan
+ }
+ }, status=status.HTTP_200_OK)
+
+ except Dingdan.DoesNotExist:
+ return Response({
+ 'code': 5,
+ 'msg': '订单不存在',
+ 'data': None
+ }, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ logger.error(f"商家结单失败: {str(e)}", exc_info=True)
+ return Response({
+ 'code': 9,
+ 'msg': '服务器内部错误,结单失败',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
+
+
+class ShangjiaChexiaoView(APIView):
+ """商家撤销订单申请接口"""
+ permission_classes = [IsAuthenticated] # JWT认证
+
+ def post(self, request):
+ # 1. 获取当前登录用户并验证商家身份
+ try:
+ current_user = request.user
+ # 使用反向查询获取商家扩展信息(性能最佳)
+ shangjia = current_user.shop_profile
+ except UserShangjia.DoesNotExist:
+ return Response({
+ 'code': 1,
+ 'msg': '您不是商家或商家信息未完善',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 2. 检查商家账号状态
+ if shangjia.zhuangtai != 1:
+ return Response({
+ 'code': 2,
+ 'msg': '商家账号状态异常,无法操作',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 3. 获取前端参数
+ try:
+ data = request.data
+ dingdan_id = data.get('dingdan_id', '').strip()
+ chexiao_liyou = data.get('chexiao_liyou', '').strip()
+ except Exception:
+ return Response({
+ 'code': 3,
+ 'msg': '参数格式错误',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 4. 参数验证
+ if not dingdan_id or not chexiao_liyou:
+ return Response({
+ 'code': 4,
+ 'msg': '订单ID和撤销原因不能为空',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ # 5. 使用事务确保数据一致性
+ with transaction.atomic():
+ # 查询订单主表
+ dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
+ if not dingdan:
+ return Response({
+ 'code': 5,
+ 'msg': '订单不存在',
+ 'data': None
+ }, status=status.HTTP_404_NOT_FOUND)
+
+ # 6. 验证订单状态
+ if dingdan.zhuangtai not in [1, 7]:
+ return Response({
+ 'code': 6,
+ 'msg': '当前订单状态不可撤销',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 7. 验证订单所属商家(使用反向查询)
+ shangjia_kuozhan = DingdanShangjia.objects.filter(
+ dingdan__dingdan_id=dingdan_id,
+ shangjia_id=current_user.yonghuid
+ ).first()
+
+ if not shangjia_kuozhan:
+ return Response({
+ 'code': 7,
+ 'msg': '您无权撤销此订单',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 8. 创建退款记录(注意:申请状态直接设为1-成功)
+ Tuikuanjilu.objects.create(
+ dingdan_id=dingdan_id,
+ qingqiuid=current_user.yonghuid,
+ liyou=chexiao_liyou,
+ sqzhuangtai=1, # 直接成功
+ jine=dingdan.jine,
+ dashou_fencheng=dingdan.dashou_fencheng,
+ jieshao=dingdan.jieshao,
+ beizhu=dingdan.beizhu,
+ nicheng=dingdan.nicheng,
+
+ )
+
+ # 9. 更新商家扩展表数据(使用F表达式原子操作)
+ UserShangjia.objects.filter(id=shangjia.id).update(
+ yue=F('yue') + dingdan.jine,
+ tuikuan=F('tuikuan') + 1,
+ jinridingdan=F('jinridingdan') - 1,
+ jinyuedingdan=F('jinyuedingdan') - 1,
+ jinriliushui=F('jinriliushui') - dingdan.jine,
+ jinyueliushui=F('jinyueliushui') - dingdan.jine,
+
+ )
+
+ # 10. 更新订单状态为5(已退款)
+ dingdan.zhuangtai = 5
+
+ dingdan.save()
+
+ # ✅ 新方法(行为驱动,更安全)
+ from houtai.utils import update_shangjia_daily
+ update_shangjia_daily(
+ yonghuid=request.user.yonghuid,
+ amount=Decimal(str(dingdan.jine)),
+ action=3 # 3 = 退款
+ )
+
+ return Response({
+ 'code': 0,
+ 'msg': '撤销成功',
+ 'data': {
+ 'dingdan_id': dingdan_id,
+ 'new_zhuangtai': 5,
+ 'tuikuan_liyou': chexiao_liyou,
+ 'jine': float(dingdan.jine) if dingdan.jine else 0.0
+ }
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ # 记录异常日志
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.error(f"商家撤销订单失败: {str(e)}", exc_info=True)
+
+ return Response({
+ 'code': 8,
+ 'msg': '服务器内部错误,撤销失败',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+class ShangjiaTuikuanShenqingView(APIView):
+ """商家退款申请接口"""
+ permission_classes = [IsAuthenticated] # JWT认证
+
+ def post(self, request):
+ # 1. 获取当前登录用户并验证商家身份
+ try:
+ current_user = request.user
+ shangjia = current_user.shop_profile
+ except UserShangjia.DoesNotExist:
+ return Response({
+ 'code': 1,
+ 'msg': '您不是商家或商家信息未完善',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 2. 检查商家账号状态
+ if shangjia.zhuangtai != 1:
+ return Response({
+ 'code': 2,
+ 'msg': '商家账号状态异常,无法操作',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 3. 获取前端参数
+ try:
+ data = request.data
+ dingdan_id = data.get('dingdan_id', '').strip()
+ tuikuan_liyou = data.get('tuikuan_liyou', '').strip()
+ except Exception:
+ return Response({
+ 'code': 3,
+ 'msg': '参数格式错误',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 4. 参数验证
+ if not dingdan_id or not tuikuan_liyou:
+ return Response({
+ 'code': 4,
+ 'msg': '订单ID和退款原因不能为空',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ # 5. 使用事务确保数据一致性
+ with transaction.atomic():
+ # 查询订单主表
+ dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
+ if not dingdan:
+ return Response({
+ 'code': 5,
+ 'msg': '订单不存在',
+ 'data': None
+ }, status=status.HTTP_404_NOT_FOUND)
+
+ # 6. 验证订单状态
+ if dingdan.zhuangtai not in [2, 8]:
+ return Response({
+ 'code': 6,
+ 'msg': '当前订单状态不可申请退款',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 7. 验证订单所属商家
+ shangjia_kuozhan = DingdanShangjia.objects.filter(
+ dingdan__dingdan_id=dingdan_id,
+ shangjia_id=current_user.yonghuid
+ ).first()
+
+ if not shangjia_kuozhan:
+ return Response({
+ 'code': 7,
+ 'msg': '您无权为此订单申请退款',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # ========== 8. 处理退款记录(修改点:先查询是否存在) ==========
+ refund_record, created = Tuikuanjilu.objects.get_or_create(
+ dingdan_id=dingdan_id,
+ defaults={
+ 'dashouid': dingdan.jiedan_dashou_id,
+ 'qingqiuid': current_user.yonghuid,
+ 'liyou': tuikuan_liyou,
+ 'sqzhuangtai': 0, # 待处理
+ 'jine': dingdan.jine,
+ 'dashou_fencheng': dingdan.dashou_fencheng,
+ 'jieshao': dingdan.jieshao,
+ 'beizhu': dingdan.beizhu,
+ 'nicheng': dingdan.nicheng,
+ }
+ )
+ if not created:
+ # 记录已存在,更新退款理由和申请时间
+ refund_record.liyou = tuikuan_liyou
+ refund_record.save(update_fields=['liyou', 'update_time'])
+ logger.info(f"订单 {dingdan_id} 已有退款记录,已更新退款理由")
+
+ # 9. 更新订单状态为4(退款中)
+ dingdan.zhuangtai = 4
+ dingdan.tkly = tuikuan_liyou
+ dingdan.save()
+
+ # ====== 原有功能:更新打手状态 ======
+ if dingdan.jiedan_dashou_id:
+ try:
+ dashou_main = UserMain.objects.filter(
+ yonghuid=dingdan.jiedan_dashou_id
+ ).first()
+ if dashou_main and hasattr(dashou_main, 'dashou_profile'):
+ dashou_profile = dashou_main.dashou_profile
+ dashou_profile.zhuangtai = 1
+ dashou_profile.save()
+ logger.info(f"退款申请成功,打手状态已更新:订单{dingdan_id},打手{dingdan.jiedan_dashou_id}状态变更为1")
+ except Exception as e:
+ logger.error(f"更新打手状态失败:{str(e)},订单{dingdan_id},打手ID{dingdan.jiedan_dashou_id}")
+
+ # ========== 新增:如果是跨平台订单,异步通知对方平台 ==========
+ if dingdan.is_cross == 1:
+ import threading
+ import requests
+ from django.conf import settings
+
+ def send_refund_notify():
+ url = f"{settings.PARTNER_BASE_URL}{settings.PARTNER_REFUND_NOTIFY_URL}"
+ payload = {
+ 'token': settings.CROSS_PLATFORM_TOKEN,
+ 'order_id': dingdan.dingdan_id
+ }
+ try:
+ resp = requests.post(url, json=payload, timeout=5)
+ if resp.status_code == 200:
+ data = resp.json()
+ if data.get('code') == 0:
+ logger.info(f"跨平台退款通知发送成功: {dingdan.dingdan_id}")
+ else:
+ logger.error(f"跨平台退款通知业务失败: {data}")
+ else:
+ logger.error(f"跨平台退款通知HTTP失败: {resp.status_code}")
+ except Exception as e:
+ logger.error(f"跨平台退款通知异常: {str(e)}", exc_info=True)
+
+ thread = threading.Thread(target=send_refund_notify)
+ thread.daemon = True
+ thread.start()
+ logger.info(f"跨平台退款通知线程已启动: {dingdan.dingdan_id}")
+ # ========== 新增结束 ==========
+
+ return Response({
+ 'code': 0,
+ 'msg': '退款申请提交成功',
+ 'data': {
+ 'dingdan_id': dingdan_id,
+ 'new_zhuangtai': 4,
+ 'tuikuan_liyou': tuikuan_liyou,
+ 'jine': float(dingdan.jine) if dingdan.jine else 0.0,
+ 'dashou_id': dingdan.jiedan_dashou_id
+ }
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ logger.error(f"商家退款申请失败: {str(e)}", exc_info=True)
+ return Response({
+ 'code': 8,
+ 'msg': '服务器内部错误,申请失败',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+class ShangjiaChufaShenqingView(APIView):
+ """商家处罚申请接口(已添加图片上传功能)"""
+ permission_classes = [IsAuthenticated] # JWT认证
+
+ def post(self, request):
+ # 1. 获取当前登录用户并验证商家身份
+ try:
+ current_user = request.user
+ shangjia = current_user.shop_profile
+ except UserShangjia.DoesNotExist:
+ return Response({
+ 'code': 1,
+ 'msg': '您不是商家或商家信息未完善',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 2. 检查商家账号状态
+ if shangjia.zhuangtai != 1:
+ return Response({
+ 'code': 2,
+ 'msg': '商家账号状态异常,无法操作',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 3. 获取前端参数
+ try:
+ data = request.data
+ dingdan_id = data.get('dingdan_id', '').strip()
+ dashou_id = data.get('dashou_id', '').strip()
+ chufa_liyou = data.get('chufa_liyou', '').strip()
+ chufa_tupian_urls = data.get('chufa_tupian_urls', []) # 🔴 新增:图片URL数组
+ except Exception:
+ return Response({
+ 'code': 3,
+ 'msg': '参数格式错误',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 4. 参数验证
+ if not dingdan_id or not dashou_id or not chufa_liyou:
+ return Response({
+ 'code': 4,
+ 'msg': '订单ID、打手ID和处罚原因不能为空',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ # 5. 使用事务确保数据一致性
+ with transaction.atomic():
+ # 验证打手身份
+ try:
+ dashou_user = UserMain.objects.get(yonghuid=dashou_id)
+ dashou = dashou_user.dashou_profile
+ except UserMain.DoesNotExist:
+ return Response({
+ 'code': 5,
+ 'msg': '打手用户不存在',
+ 'data': None
+ }, status=status.HTTP_404_NOT_FOUND)
+ except UserDashou.DoesNotExist:
+ return Response({
+ 'code': 5,
+ 'msg': '该用户不是打手',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 查询订单主表
+ dingdan = Dingdan.objects.filter(dingdan_id=dingdan_id).first()
+ if not dingdan:
+ return Response({
+ 'code': 6,
+ 'msg': '订单不存在',
+ 'data': None
+ }, status=status.HTTP_404_NOT_FOUND)
+
+ # 验证订单状态
+ if dingdan.zhuangtai not in [2, 8, 4, 5, 6, 3]:
+ return Response({
+ 'code': 7,
+ 'msg': '当前订单状态不可申请处罚',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 验证订单所属商家
+ shangjia_kuozhan = DingdanShangjia.objects.filter(
+ dingdan__dingdan_id=dingdan_id,
+ shangjia_id=current_user.yonghuid
+ ).first()
+
+ if not shangjia_kuozhan:
+ return Response({
+ 'code': 8,
+ 'msg': '您无权为此订单申请处罚',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 验证订单的打手是否与提供的打手ID一致
+ if dingdan.jiedan_dashou_id != dashou_id:
+ return Response({
+ 'code': 9,
+ 'msg': '该打手并非此订单的服务打手',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 创建处罚记录
+ chufajilu = Chufajilu.objects.create(
+ dashouid=dashou_id,
+ qingqiuid=current_user.yonghuid,
+ cfliyou=chufa_liyou,
+ sqzhuangtai=0, # 待处理
+ jifen=5, # 扣除5积分
+ dingdan_id=dingdan_id,
+ )
+ # 🔴【新增】在创建处罚记录后立即扣除打手积分
+ # 注意:这里使用前面已经获取的dashou对象(打手扩展表)
+ jifen_kouchu = chufajilu.jifen # 扣除的积分,这里是5
+ if jifen_kouchu > 0:
+ # 检查打手积分是否足够
+ if dashou.jifen < jifen_kouchu:
+ dashou.jifen = 0
+ else:
+ dashou.jifen -= jifen_kouchu
+ dashou.save()
+
+ # 🔴【新增】保存处罚图片到数据库
+ if chufa_tupian_urls and isinstance(chufa_tupian_urls, list):
+ for tupian_url in chufa_tupian_urls:
+ if tupian_url and tupian_url.strip():
+ Chufatupian.objects.create(
+ dingdan_id=dingdan_id,
+ yonghuid=current_user.yonghuid,
+ tupian=tupian_url
+ )
+
+ # 更新商家订单扩展表的处罚相关字段
+ shangjia_kuozhan.cfliyou = chufa_liyou
+ shangjia_kuozhan.sqzhuangtai = 0
+ shangjia_kuozhan.save()
+
+ return Response({
+ 'code': 0,
+ 'msg': '处罚申请提交成功',
+ 'data': {
+ 'dingdan_id': dingdan_id,
+ 'dashou_id': dashou_id,
+ 'chufa_liyou': chufa_liyou,
+ 'sqzhuangtai': 0,
+ 'jifen': 5,
+ 'tupian_count': len(chufa_tupian_urls) if chufa_tupian_urls else 0 # 🔴 返回图片数量
+ }
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.error(f"商家处罚申请失败: {str(e)}", exc_info=True)
+
+ return Response({
+ 'code': 10,
+ 'msg': '服务器内部错误,申请失败',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+# apps/dingdan/views.py
+from django.db.models import Q, OuterRef, Subquery
+from django.core.paginator import Paginator
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+from rest_framework_simplejwt.authentication import JWTAuthentication
+from .models import Dingdan, DingdanShangjia
+
+
+# dingdan/views.py
+from django.db.models import Q, OuterRef, Subquery, F, Prefetch
+
+from dengji.models import DingdanBiaoqian
+from dengji.models import YonghuChenghao, Chenghao
+
+
+class DashouDingdanHuoquView(APIView):
+ """
+ 获取可抢订单列表(支持标签筛选)
+ POST /dingdan/ddhq
+ 需要JWT Token认证
+ """
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ try:
+ # 1. 获取请求参数
+ data = request.data
+ leixing_id = data.get('leixing_id')
+ page = int(data.get('page', 1))
+ page_size = int(data.get('page_size', 10))
+ biaoqian_id = data.get('biaoqian_id')
+
+ # 2. 构建基础查询条件
+ base_query = Q(zhuangtai__in=[1, 7])
+ if leixing_id:
+ base_query &= Q(leixing_id=leixing_id)
+
+ # 3. 标签筛选:先查出符合条件的订单ID,避免跨表 JOIN 排序冲突
+ if biaoqian_id and int(biaoqian_id) > 0:
+ biaoqian_order_ids = DingdanBiaoqian.objects.filter(
+ chenghao_id=int(biaoqian_id)
+ ).values_list('dingdan_id', flat=True)
+ base_query &= Q(dingdan_id__in=list(biaoqian_order_ids))
+
+ # 4. 构建基础查询集
+ base_qs = Dingdan.objects.filter(base_query).order_by('-create_time').distinct()
+
+ # 5. 获取总记录数
+ total = base_qs.count()
+
+ # 6. 计算分页偏移量
+ offset = (page - 1) * page_size
+ limit = page_size
+
+ # 7. 获取当前页数据
+ page_qs = base_qs[offset:offset + limit]
+
+ # 8. 使用子查询获取商家昵称
+ shangjia_sub = DingdanShangjia.objects.filter(
+ dingdan__dingdan_id=OuterRef('dingdan_id')
+ ).values('sjnicheng')[:1]
+
+ # 9. 获取当前页所有需要的字段
+ dingdan_values = page_qs.annotate(
+ sjnicheng=Subquery(shangjia_sub)
+ ).values(
+ 'dingdan_id', 'zhuangtai', 'fadan_pingtai', 'dashou_fencheng',
+ 'zhiding_id', 'leixing_id', 'tupian', 'yaoqiuleixing',
+ 'huiyuan_id', 'yongjin', 'jieshao', 'beizhu', 'create_time',
+ 'sjnicheng'
+ )
+
+ # 10. 转换为列表
+ dingdan_list = list(dingdan_values)
+
+ # 11. 批量获取指定打手的头像和昵称
+ zhiding_ids = {item['zhiding_id'] for item in dingdan_list if item.get('zhiding_id')}
+ zhiding_info = {}
+ if zhiding_ids:
+ dashou_query = UserMain.objects.filter(
+ yonghuid__in=zhiding_ids
+ ).select_related('dashou_profile').annotate(
+ nicheng=F('dashou_profile__nicheng')
+ ).values('yonghuid', 'avatar', 'nicheng')
+ zhiding_info = {
+ item['yonghuid']: {
+ 'avatar': item['avatar'] or '',
+ 'nicheng': item['nicheng'] or ''
+ }
+ for item in dashou_query
+ }
+
+ # ============ 批量获取三层标签 ============
+ order_ids = [item['dingdan_id'] for item in dingdan_list]
+
+ # --- 订单需求标签 ---
+ xuqiu_tags_map = {}
+ if order_ids:
+ try:
+ dbq_qs = DingdanBiaoqian.objects.filter(
+ dingdan_id__in=order_ids
+ ).select_related('chenghao')
+ for dbq in dbq_qs:
+ oid = dbq.dingdan_id
+ if oid not in xuqiu_tags_map:
+ xuqiu_tags_map[oid] = []
+ ch = dbq.chenghao
+ xuqiu_tags_map[oid].append({
+ 'id': ch.id,
+ 'mingcheng': ch.mingcheng,
+ 'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
+ })
+ except Exception as e:
+ logger.error(f"查询订单需求标签失败: {e}", exc_info=True)
+
+ # --- 所有需要查询标签的用户ID集合(指定打手 + 商家) ---
+ yonghuid_set = set(zhiding_ids)
+ shangjia_order_ids = [item['dingdan_id'] for item in dingdan_list if item['fadan_pingtai'] == 2]
+ shangjia_id_map = {}
+ if shangjia_order_ids:
+ # 使用跨表查询 dingdan__dingdan_id__in 匹配字符串订单ID
+ sj_exts = DingdanShangjia.objects.filter(
+ dingdan__dingdan_id__in=shangjia_order_ids
+ ).values('dingdan_id', 'shangjia_id')
+ for sj in sj_exts:
+ shangjia_id_map[sj['dingdan_id']] = sj['shangjia_id']
+ if sj['shangjia_id']:
+ yonghuid_set.add(sj['shangjia_id'])
+
+ # 将 yonghuid 字符串转换为 UserMain 的主键 id
+ user_id_map = {}
+ if yonghuid_set:
+ users = UserMain.objects.filter(yonghuid__in=yonghuid_set).values('id', 'yonghuid')
+ for u in users:
+ user_id_map[u['yonghuid']] = u['id']
+
+ user_main_ids = list(user_id_map.values())
+ user_tags_map = {}
+ if user_main_ids:
+ uc_qs = YonghuChenghao.objects.filter(
+ yonghu_id__in=user_main_ids
+ ).select_related('chenghao')
+ for uc in uc_qs:
+ uid = uc.yonghu_id
+ if uid not in user_tags_map:
+ user_tags_map[uid] = []
+ user_tags_map[uid].append(uc.chenghao)
+
+ dashou_tags_map = {}
+ shangjia_tags_map = {}
+ for item in dingdan_list:
+ oid = item['dingdan_id']
+ # 指定打手标签
+ zhid = item['zhiding_id']
+ dashou_tags = []
+ if zhid and zhid in user_id_map:
+ uid = user_id_map[zhid]
+ if uid in user_tags_map:
+ for ch in user_tags_map[uid]:
+ if ch.leixing == 'dashou':
+ dashou_tags.append({
+ 'id': ch.id,
+ 'mingcheng': ch.mingcheng,
+ 'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
+ })
+ dashou_tags_map[oid] = dashou_tags
+
+ # 商家标签
+ sj_tags = []
+ if item['fadan_pingtai'] == 2 and oid in shangjia_id_map:
+ sj_yonghuid = shangjia_id_map[oid]
+ if sj_yonghuid and sj_yonghuid in user_id_map:
+ uid = user_id_map[sj_yonghuid]
+ if uid in user_tags_map:
+ for ch in user_tags_map[uid]:
+ if ch.leixing == 'shangjia':
+ sj_tags.append({
+ 'id': ch.id,
+ 'mingcheng': ch.mingcheng,
+ 'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
+ })
+ shangjia_tags_map[oid] = sj_tags
+
+ # 12. 格式化返回数据
+ formatted_list = []
+ for order in dingdan_list:
+ oid = order['dingdan_id']
+ zid = order.get('zhiding_id')
+ info = zhiding_info.get(zid, {})
+ formatted_list.append({
+ 'dingdan_id': order['dingdan_id'],
+ 'zhuangtai': order['zhuangtai'],
+ 'pingtai': order['fadan_pingtai'],
+ 'fadan_pingtai': order['fadan_pingtai'],
+ 'dashou_fencheng': float(order['dashou_fencheng']) if order['dashou_fencheng'] else 0.0,
+ 'zhiding_id': order['zhiding_id'] or '',
+ 'leixing_id': order['leixing_id'] or 0,
+ 'tupian': order['tupian'] or '',
+ 'yaoqiuleixing': order['yaoqiuleixing'] or 0,
+ 'huiyuan_id': order['huiyuan_id'] or '',
+ 'yajin': float(order['yongjin']) if order['yongjin'] else 0.0,
+ 'yongjin': float(order['yongjin']) if order['yongjin'] else 0.0,
+ 'jieshao': order['jieshao'] or '',
+ 'beizhu': order['beizhu'] or '',
+ 'creat_time': order['create_time'].strftime('%Y-%m-%d %H:%M:%S') if order['create_time'] else '',
+ 'sjnicheng': order['sjnicheng'] or '',
+ 'zhiding_uid': order['zhiding_id'] or '',
+ 'zhiding_avatar': info.get('avatar', ''),
+ 'zhiding_nicheng': info.get('nicheng', ''),
+ # 三层标签
+ 'xuqiu_biaoqian': xuqiu_tags_map.get(oid, []),
+ 'dashou_biaoqian': dashou_tags_map.get(oid, []),
+ 'shangjia_biaoqian': shangjia_tags_map.get(oid, []),
+ })
+
+ has_more = (page * page_size) < total
+
+ return Response({
+ 'code': 200,
+ 'msg': '获取成功',
+ 'data': {
+ 'list': formatted_list,
+ 'has_more': has_more,
+ 'total': total,
+ 'current_page': page,
+ 'page_size': page_size,
+ 'total_pages': (total + page_size - 1) // page_size,
+ }
+ })
+
+ except Exception as e:
+ logger.error(f"订单列表查询失败: {e}", exc_info=True)
+ return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
+
+ def _parse_texiao_json(self, texiao_miaoshu):
+ if not texiao_miaoshu:
+ return {}
+ try:
+ return json.loads(texiao_miaoshu)
+ except:
+ return {}
+
+
+# apps/dingdan/views.py
+from shangpin.models import ShangpinLeixing # 假设这是你的商品类型模型
+
+
+class ShangpinLeixingHuoquView(APIView):
+ """
+ 获取商品类型列表
+ POST /dingdan/ddlxhq
+ 需要JWT Token认证
+ """
+ authentication_classes = [JWTAuthentication]
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 1. 查询商品类型表
+ # 假设你的商品类型模型字段为:id, jieshao, tupian_url
+ leixing_list = ShangpinLeixing.objects.all().values(
+ 'id', # 商品类型ID
+ 'jieshao', # 类型介绍
+ 'tupian_url' # 类型图片URL(相对URL)
+ ).order_by('id') # 按ID排序,可根据需要调整
+
+ # 2. 格式化返回数据
+ formatted_list = []
+ for leixing in leixing_list:
+ formatted_leixing = {
+ 'id': leixing['id'],
+ 'jieshao': leixing['jieshao'] or '', # 处理空值
+ 'tupian_url': leixing['tupian_url'] or '' # 处理空值
+ }
+ formatted_list.append(formatted_leixing)
+
+ # 3. 返回响应
+ return Response({
+ 'code': 200,
+ 'msg': '获取成功',
+ 'data': {
+ 'list': formatted_list,
+ 'total': len(formatted_list)
+ }
+ })
+
+
+# apps/dingdan/views.py
+from django.db import transaction
+from django.utils import timezone
+from django.db.models import F
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+from rest_framework_simplejwt.authentication import JWTAuthentication
+import logging
+
+from .models import Dingdan, Liaotian
+
+from utils.goeasy_service import GoEasyService
+
+logger = logging.getLogger(__name__)
+
+
+from peizhi.models import Club
+
+logger = logging.getLogger(__name__)
+
+
+# apps/dingdan/views.py
+from django.db import transaction
+from django.utils import timezone
+from django.db.models import F
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+from rest_framework_simplejwt.authentication import JWTAuthentication
+import logging
+import requests
+import json
+
+from .models import Dingdan, DingdanPingtai, DingdanShangjia
+
+
+logger = logging.getLogger(__name__)
+
+from django.db import transaction
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+from django.utils import timezone
+from django.db.models import F
+from django.conf import settings
+import json
+import requests
+from datetime import datetime
+
+
+from peizhi.models import Club
+
+logger = logging.getLogger(__name__)
+
+
+# dingdan/views.py
+import logging
+import requests
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status, permissions
+from .models import Dingdan, DingdanPingtai, DingdanShangjia
+from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
+from peizhi.models import ClubConfig
+from utils.chat_utils import _send_group_message, _subscribe_users_to_group
+
+logger = logging.getLogger('dingdan')
+
+# dingdan/views.py
+# dingdan/views.py
+import logging
+import requests
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status, permissions
+from .models import Dingdan, DingdanPingtai, DingdanShangjia
+from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
+from peizhi.models import ClubConfig
+
+logger = logging.getLogger('dingdan')
+
+
+# dingdan/views.py
+
+import logging
+import requests
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import permissions
+from .models import Dingdan, DingdanPingtai, DingdanShangjia
+from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
+from peizhi.models import ClubConfig
+
+logger = logging.getLogger('dingdan')
+
+import logging
+import requests
+import time
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import permissions
+
+from .models import Dingdan, DingdanPingtai, DingdanShangjia
+from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
+from peizhi.models import ClubConfig
+
+logger = logging.getLogger('dingdan')
+
+
+# ----------------- 基础配置获取 -----------------
+def get_self_goeasy_appkey():
+ try:
+ cfg = ClubConfig.objects.filter(is_self=1).first()
+ return cfg.chat_api_key if cfg else None
+ except Exception:
+ return None
+
+
+def get_self_goeasy_secret():
+ try:
+ cfg = ClubConfig.objects.filter(is_self=1).first()
+ return cfg.chat_api_id if cfg else None
+ except Exception:
+ return None
+
+
+def get_self_storage_domain():
+ try:
+ cfg = ClubConfig.objects.filter(is_self=1).first()
+ return cfg.storage_bucket_domain if cfg else ''
+ except Exception:
+ return ''
+
+
+def full_avatar(relative_url, bucket_domain=None):
+ if not relative_url:
+ return ''
+ if relative_url.startswith('http'):
+ return relative_url
+ domain = bucket_domain or get_self_storage_domain()
+ if domain:
+ domain = domain.rstrip('/') + '/'
+ return domain + relative_url.lstrip('/')
+ return relative_url
+
+
+# ----------------- GoEasy API 封装 -----------------
+def subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None):
+ """订阅用户到群组,确保能收发该群消息"""
+ if not appkey:
+ appkey = get_self_goeasy_appkey()
+ if not secret:
+ secret = get_self_goeasy_secret()
+ if not appkey:
+ logger.error("订阅失败:GoEasy AppKey 未配置")
+ return False
+ url = 'https://rest-hangzhou.goeasy.io/v2/im/subscribe-groups'
+ body = {"appkey": appkey, "userIds": user_ids, "groupIds": group_ids}
+ headers = {"Content-Type": "application/json"}
+ if secret:
+ headers["Authorization"] = f"Bearer {secret}"
+ try:
+ r = requests.post(url, headers=headers, json=body, timeout=10)
+ if r.status_code == 200:
+ logger.info(f"订阅成功:用户 {user_ids} -> 群 {group_ids}")
+ return True
+ logger.error(f"订阅失败:{r.status_code} {r.text}")
+ return False
+ except Exception as e:
+ logger.exception("订阅异常")
+ return False
+
+
+def send_group_message(
+ appkey, secret, group_id, sender_id, sender_name, sender_avatar,
+ message_text='', custom_payload=None, group_name='', order_id='', is_cross=0
+):
+ """
+ 通过 GoEasy REST API 发送群消息。
+ message_text: 普通文本内容
+ custom_payload: 自定义消息体(如订单卡片),此时 type 设为 'order'
+ """
+ url = 'https://rest-hangzhou.goeasy.io/v2/im/message'
+ to_data = {
+ "name": group_name or group_id,
+ "avatar": sender_avatar or '',
+ "orderId": order_id,
+ "isCross": is_cross
+ }
+ msg_type = 'order' if custom_payload else 'text'
+ payload = custom_payload if custom_payload else message_text
+
+ body = {
+ "appkey": appkey,
+ "senderId": sender_id,
+ "senderData": {"avatar": sender_avatar, "name": sender_name},
+ "to": {"type": "group", "id": group_id, "data": to_data},
+ "type": msg_type,
+ "payload": payload
+ }
+ headers = {"Content-Type": "application/json"}
+ if secret:
+ headers["Authorization"] = f"Bearer {secret}"
+
+ for attempt in range(3): # 最多重试3次
+ try:
+ r = requests.post(url, headers=headers, json=body, timeout=10)
+ if r.status_code == 200:
+ logger.info(f"消息发送成功:sender={sender_id}, group={group_id}, type={msg_type}")
+ return True
+ logger.warning(f"消息发送失败 (尝试 {attempt+1}/3):状态码 {r.status_code}, 响应 {r.text}")
+ except Exception as e:
+ logger.warning(f"消息发送异常 (尝试 {attempt+1}/3):{e}")
+ if attempt < 2:
+ time.sleep(1)
+ logger.error(f"消息最终发送失败:sender={sender_id}, group={group_id}")
+ return False
+
+
+# ----------------- 权限校验 -----------------
+def check_user_permission(identity_type, uid, order):
+ """验证用户身份、账号状态、以及是否属于该订单"""
+ try:
+ user = UserMain.objects.get(yonghuid=uid)
+ except UserMain.DoesNotExist:
+ return False, '用户不存在'
+
+ if identity_type == 'dashou':
+ try:
+ dashou = UserDashou.objects.get(user=user)
+ if dashou.zhanghaozhuangtai != 1 :
+ return False, '打手账号已被封禁或状态异常'
+ except UserDashou.DoesNotExist:
+ return False, '该用户不是打手'
+ if order.jiedan_dashou_id != uid:
+ return False, '你不是该订单的接单打手'
+
+ elif identity_type == 'shangjia':
+ try:
+ shop = UserShangjia.objects.get(user=user)
+ if shop.zhuangtai != 1:
+ return False, '商家账号已被禁用'
+ except UserShangjia.DoesNotExist:
+ return False, '该用户不是商家'
+ try:
+ if order.shangjia_kuozhan.shangjia_id != uid:
+ return False, '你不是该订单的发布商家'
+ except DingdanShangjia.DoesNotExist:
+ return False, '订单缺少商家信息'
+
+ elif identity_type == 'boss':
+ try:
+ UserBoss.objects.get(user=user) # 老板暂无封禁字段,仅需存在
+ except UserBoss.DoesNotExist:
+ return False, '该用户不是老板'
+ try:
+ if order.pingtai_kuozhan.laoban_id != uid:
+ return False, '你不是该订单的老板'
+ except DingdanPingtai.DoesNotExist:
+ return False, '订单缺少平台信息'
+
+ else:
+ return False, '不支持的身份类型'
+ return True, ''
+
+
+# ----------------- 跨平台对方配置获取 -----------------
+def get_partner_club_config(partner_club_id):
+ try:
+ return ClubConfig.objects.get(club_id=partner_club_id, is_self=0)
+ except ClubConfig.DoesNotExist:
+ return None
+
+
+# ----------------- 主视图 -----------------
+
+
+class DaiLiQunLiaoXiaoXiView(APIView):
+ """
+ 代理发送群聊消息
+ 支持:
+ - 非跨平台订单:直接用我方 GoEasy Key 以真实身份发送
+ - 跨平台订单(双方任意一方派单):除了我方群外,还需用对方 GoEasy Key 以代理身份向对方群发送
+ 跨平台判断条件:订单同时存在 partner_order_id 和 partner_club_id 即视为跨平台
+ """
+ permission_classes = [permissions.IsAuthenticated]
+
+ def post(self, request):
+ # ===== 1. 参数解析 =====
+ order_id = request.data.get('orderId')
+ identity_type = request.data.get('identityType') # dashou / shangjia / boss
+ sender_name = request.data.get('senderName', '')
+ sender_avatar_raw = request.data.get('senderAvatar', '')
+ message_type = request.data.get('messageType', 'text')
+ message_text = request.data.get('text', '')
+ order_payload = request.data.get('orderPayload', None)
+
+ logger.info(f"[群聊代理] 收到请求: orderId={order_id}, identityType={identity_type}, "
+ f"messageType={message_type}, text={message_text[:20]}...")
+
+ if not order_id or not identity_type:
+ return Response({'code': 400, 'msg': '缺少 orderId 或 identityType'})
+ if identity_type not in ('dashou', 'shangjia', 'boss'):
+ return Response({'code': 400, 'msg': 'identityType 无效'})
+
+ # ===== 2. 构造 senderId =====
+ uid = request.user.yonghuid
+ prefix_map = {'dashou': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'}
+ sender_id = prefix_map[identity_type] + uid
+ logger.info(f"[群聊代理] 登录用户 yonghuid={uid}, 构造 senderId={sender_id}")
+
+ # ===== 3. 查询订单 =====
+ try:
+ order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=order_id)
+ except Dingdan.DoesNotExist:
+ logger.warning(f"[群聊代理] 订单 {order_id} 不存在")
+ return Response({'code': 404, 'msg': '订单不存在'})
+
+ # ===== 4. 权限校验 =====
+ valid, err_msg = check_user_permission(identity_type, uid, order)
+ if not valid:
+ logger.warning(f"[群聊代理] 权限校验失败: {err_msg}, uid={uid}, identity={identity_type}")
+ return Response({'code': 403, 'msg': err_msg})
+
+ # ===== 5. 我方 GoEasy 配置 =====
+ self_appkey = get_self_goeasy_appkey()
+ self_secret = get_self_goeasy_secret()
+ if not self_appkey:
+ logger.error("[群聊代理] 我方 GoEasy AppKey 未配置")
+ return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
+
+ # ===== 6. 公共群信息 =====
+ real_group_id = f"group_{order.dingdan_id}"
+ group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}"
+ sender_avatar = full_avatar(sender_avatar_raw) if sender_avatar_raw else full_avatar('')
+
+ # ===== 7. 订阅发送者到群 =====
+ if not subscribe_users_to_group([sender_id], [real_group_id], self_appkey, self_secret):
+ logger.error("[群聊代理] 订阅群组失败")
+ return Response({'code': 500, 'msg': '订阅群组失败'})
+
+ # ===== 8. 判断跨平台(只看对方订单ID和俱乐部ID) =====
+ partner_order_id = order.partner_order_id
+ partner_club_id = order.partner_club_id
+ is_cross = 1 if (partner_order_id and partner_club_id) else 0
+ logger.info(f"[群聊代理] 订单 {order_id} partner_order_id={partner_order_id}, partner_club_id={partner_club_id}, 判定跨平台={is_cross}")
+
+ custom_payload = order_payload if message_type == 'order' else None
+ text_content = message_text
+
+ # ---------- 先向我方群发送消息(永远执行) ----------
+ local_ok = send_group_message(
+ appkey=self_appkey,
+ secret=self_secret,
+ group_id=real_group_id,
+ sender_id=sender_id,
+ sender_name=sender_name,
+ sender_avatar=sender_avatar,
+ message_text=text_content,
+ custom_payload=custom_payload,
+ group_name=group_name,
+ order_id=order.dingdan_id,
+ is_cross=is_cross
+ )
+ if not local_ok:
+ return Response({'code': 500, 'msg': '消息发送失败'})
+
+ # ---------- 跨平台转发(如果有对方订单ID和俱乐部ID) ----------
+ if is_cross:
+ logger.info("[群聊代理] 满足跨平台条件,准备双平台发送")
+ partner_cfg = get_partner_club_config(partner_club_id)
+ if not partner_cfg or not partner_cfg.chat_api_key:
+ logger.warning("[群聊代理] 对方俱乐部配置不可用,仅发送我方群")
+ return Response({'code': 200, 'msg': '发送成功(对方GoEasy未配置)'})
+
+ partner_appkey = partner_cfg.chat_api_key
+ partner_secret = partner_cfg.chat_api_id or ''
+ partner_group_id = f"group_{partner_order_id}"
+
+ # 构造代理身份
+ agent_sender_id = f"{order.dingdan_id}_{sender_id}"
+ if identity_type == 'dashou':
+ agent_sender_name = f"[我方]{sender_name}"
+ elif identity_type == 'shangjia':
+ agent_sender_name = f"[我方商家]{sender_name}"
+ else:
+ agent_sender_name = f"[我方老板]{sender_name}"
+ agent_avatar = full_avatar('')
+
+ logger.info(f"[群聊代理] 对方群组={partner_group_id}, 代理身份={agent_sender_id}/{agent_sender_name}")
+
+ # 向对方群发送代理消息
+ partner_ok = send_group_message(
+ appkey=partner_appkey,
+ secret=partner_secret,
+ group_id=partner_group_id,
+ sender_id=agent_sender_id,
+ sender_name=agent_sender_name,
+ sender_avatar=agent_avatar,
+ message_text=text_content,
+ custom_payload=custom_payload,
+ group_name='跨平台订单交流',
+ order_id=partner_order_id,
+ is_cross=1
+ )
+ logger.info(f"[群聊代理] 对方群发送结果: {partner_ok}")
+
+ if partner_ok:
+ return Response({'code': 200, 'msg': '发送成功(双平台)'})
+ else:
+ return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'})
+ else:
+ logger.info("[群聊代理] 非跨平台,仅发送我方群")
+ return Response({'code': 200, 'msg': '发送成功'})
+
+
+#通过跨平台字段判断是否是跨平台
+
+
+from peizhi.models import ClubConfig, ShangjiaLianjie
+logger = logging.getLogger('dingdan')
+
+# ---------- 基础配置 ----------
+def get_self_goeasy_appkey():
+ try:
+ cfg = ClubConfig.objects.filter(is_self=1).first()
+ return cfg.chat_api_key if cfg else None
+ except Exception:
+ return None
+
+def get_self_goeasy_secret():
+ try:
+ cfg = ClubConfig.objects.filter(is_self=1).first()
+ return cfg.chat_api_id if cfg else None
+ except Exception:
+ return None
+
+def get_partner_club_config(partner_club_id):
+ try:
+ return ClubConfig.objects.get(club_id=partner_club_id, is_self=0)
+ except ClubConfig.DoesNotExist:
+ return None
+
+
+class ShangjiaLianjieLiuYanZhuanFaView(APIView):
+ """
+ 商家链接留言转发接口
+ 路径:POST /dingdan/sjljlbxxzf
+ 固定以 B{商家ID} 身份发送群消息。通过 partner_order_id/partner_club_id 判断是否跨平台。
+ """
+ permission_classes = []
+
+ def post(self, request):
+ token = request.data.get('token')
+ text = request.data.get('text', '')
+
+ if not token or not text:
+ return Response({'code': 400, 'msg': '缺少参数'})
+
+ try:
+ lianjie = ShangjiaLianjie.objects.get(lianjie_token=token)
+ except ShangjiaLianjie.DoesNotExist:
+ return Response({'code': 404, 'msg': '链接不存在或已失效'})
+
+ if not lianjie.dingdan_id:
+ return Response({'code': 400, 'msg': '链接缺少订单ID'})
+
+ try:
+ order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=lianjie.dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response({'code': 500, 'msg': '订单数据异常'})
+
+ shangjia_uid = lianjie.yonghu_id
+ sender_id = f"B{shangjia_uid}"
+ sender_name = "老板"
+ sender_avatar = ''
+
+ appkey = get_self_goeasy_appkey()
+ secret = get_self_goeasy_secret()
+ if not appkey:
+ return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
+
+ group_id = f"group_{order.dingdan_id}"
+ group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}"
+
+ local_cross_flag = 1 if (order.partner_order_id and order.partner_club_id) else 0
+
+ if not _subscribe_users_to_group([sender_id], [group_id], appkey, secret):
+ return Response({'code': 500, 'msg': '订阅群组失败'})
+
+ local_ok = _send_group_message(
+ appkey=appkey,
+ secret=secret,
+ group_id=group_id,
+ sender_id=sender_id,
+ sender_name=sender_name,
+ sender_avatar=sender_avatar,
+ message_text=text,
+ custom_payload=None,
+ group_name=group_name,
+ order_id=order.dingdan_id,
+ is_cross=local_cross_flag
+ )
+ if not local_ok:
+ return Response({'code': 500, 'msg': '消息发送失败'})
+
+ # 跨平台转发
+ partner_order_id = order.partner_order_id
+ partner_club_id = order.partner_club_id
+
+ if not partner_order_id or not partner_club_id:
+ return Response({'code': 200, 'msg': '发送成功'})
+
+ partner_cfg = get_partner_club_config(partner_club_id)
+ if not partner_cfg or not partner_cfg.chat_api_key:
+ return Response({'code': 200, 'msg': '发送成功(对方未配置)'})
+
+ agent_sender_id = f"{order.dingdan_id}_{sender_id}"
+ agent_sender_name = f"[我方老板]{shangjia_uid}"
+
+ partner_group_id = f"group_{partner_order_id}"
+
+ partner_ok = _send_group_message(
+ appkey=partner_cfg.chat_api_key,
+ secret=partner_cfg.chat_api_id or '',
+ group_id=partner_group_id,
+ sender_id=agent_sender_id,
+ sender_name=agent_sender_name,
+ sender_avatar='',
+ message_text=text,
+ custom_payload=None,
+ group_name='跨平台订单交流',
+ order_id=partner_order_id,
+ is_cross=1
+ )
+
+ if partner_ok:
+ return Response({'code': 200, 'msg': '发送成功(双平台)'})
+ else:
+ return Response({'code': 200, 'msg': '我方发送成功,对方发送失败'})
+
+
+from peizhi.models import ClubConfig
+from utils.chat_utils import establish_order_chat # 【新增】导入聊天工具
+class QiangdanView(APIView):
+ authentication_classes = [JWTAuthentication]
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ data = request.data
+ dingdan_id = data.get('dingdan_id')
+ if not dingdan_id:
+ return Response({'code': 400, 'msg': '订单ID不能为空'}, status=400)
+
+ try:
+ with transaction.atomic():
+ # 1. 验证打手身份和状态
+ dashou_profile = self._validate_dashou_status(request.user)
+ if not dashou_profile:
+ return Response({'code': 403, 'msg': '您不是打手或账号状态异常'}, status=403)
+
+
+ # ==================== 【新增】罚单检查 ====================
+ from utils.fadan_utils import check_fadan_qiangdan_eligible
+ fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(request.user.yonghuid,1)
+ if not fadan_ok:
+ return Response({'code': 400, 'msg': fadan_msg})
+ # =========================================================
+ # ========== 标签快速校验(提前拒绝,避免锁资源) ==========
+ from dengji.utils import check_dashou_biaoqian_required
+ biaoqian_ok, biaoqian_msg = check_dashou_biaoqian_required(dingdan_id, request.user.yonghuid)
+ if not biaoqian_ok:
+ return Response({'code': 400, 'msg': biaoqian_msg})
+ # ===========================================================
+
+ # 2. 锁定并验证订单(行锁)
+ order_result = self._validate_and_lock_order(dingdan_id, request.user.yonghuid)
+ if isinstance(order_result, str):
+ return Response({'code': 400, 'msg': order_result})
+ order = order_result
+
+ is_cross = order.is_cross == 1
+ dispatch_type = order.dispatch_type
+
+ if is_cross and dispatch_type == 2:
+ validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
+ if not validation_result['success']:
+ return Response({'code': 400, 'msg': validation_result['message']})
+
+ claim_allowed = self._request_partner_check_claim(order, request.user.yonghuid)
+ if claim_allowed is None:
+ return Response({'code': 500, 'msg': '跨平台通信失败,请重试'}, status=500)
+ elif claim_allowed is False:
+ if order.zhuangtai == 1:
+ order.zhuangtai = 21
+ order.save()
+ logger.info(f"订单 {dingdan_id} 被对方拒绝抢单,状态改为21")
+ else:
+ order.zhuangtai = 20
+ order.save()
+ logger.info(f"订单 {dingdan_id} 已被其他平台抢走,状态改为20")
+ return Response({'code': 400, 'msg': '订单已被其他平台打手抢走或不可抢'})
+
+ self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
+
+ try:
+ update_daily_dispatch_stat(
+ direction=2,
+ partner_club_id=order.partner_club_id or '000001',
+ claimed_amount=order.jine,
+ claimed_count=1
+ )
+ except Exception as e:
+ logger.error(f"更新抢单统计失败: {e}", exc_info=True)
+
+ elif is_cross and dispatch_type == 1:
+ validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
+ if not validation_result['success']:
+ return Response({'code': 400, 'msg': validation_result['message']})
+
+ self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
+ self._async_notify_partners_order_claimed(order)
+
+ else:
+ validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid)
+ if not validation_result['success']:
+ return Response({'code': 400, 'msg': validation_result['message']})
+ self._execute_qiangdan(order, dashou_profile, request.user.yonghuid)
+
+ # ========== 【唯一改动】聊天建立方式 ==========
+ chat_success = establish_order_chat(dingdan_id)
+
+ try:
+ update_dashou_daily_by_action(
+ yonghuid=order.jiedan_dashou_id,
+ amount=order.dashou_fencheng,
+ action=1
+ )
+ logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功")
+ except Exception as e:
+ logger.error(f"打手每日统计更新失败: {e}")
+
+
+ # 在 QiangdanView.post 里,_execute_qiangdan 之后添加:
+ from dengji.services import record_dashou_biaoxian
+ # 记录抢单表现(不影响主流程)
+ # 如果订单表里有 bankuai 信息,可以用 order.bankuai 代替 None
+ record_dashou_biaoxian(
+ dingdan_id=dingdan_id,
+ dashou_id=order.jiedan_dashou_id,
+ xingwei=1,
+ jiedan_time=timezone.now(), # 直接取当前时间
+ bankuai=None # 暂不填,后续提交时补上
+ )
+
+ return Response({
+ 'code': 200,
+ 'msg': '抢单成功',
+ 'data': {
+ 'dingdan_id': dingdan_id,
+ 'chat_established': chat_success
+ }
+ })
+
+ except Exception as e:
+ logger.error(f"抢单异常: {str(e)}", exc_info=True)
+ return Response({'code': 500, 'msg': '系统异常,请稍后重试'}, status=500)
+
+ # ---------- 以下所有方法原封不动照抄旧版 ----------
+ def _validate_dashou_status(self, user):
+ try:
+ dashou_profile = user.dashou_profile
+ if (dashou_profile.zhuangtai != 1 or
+ dashou_profile.zhanghaozhuangtai != 1 or
+ dashou_profile.jifen < 5):
+ return None
+ return dashou_profile
+ except UserDashou.DoesNotExist:
+ return None
+
+ def _validate_and_lock_order(self, dingdan_id, dashou_yonghuid):
+ try:
+ order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id)
+ if order.zhuangtai not in [1, 7]:
+ return "订单状态不可抢"
+ if order.zhuangtai == 7:
+ if not order.zhiding_id or order.zhiding_id != dashou_yonghuid:
+ return "此订单为指定单,您不是指定打手"
+ if order.jiedan_dashou_id:
+ return "订单已被其他打手抢走"
+ return order
+ except Dingdan.DoesNotExist:
+ return "订单不存在"
+
+ def _validate_order_requirements(self, order, dashou_profile, dashou_yonghuid):
+ if order.yaoqiuleixing == 1:
+ return self._validate_huiyuan_requirement(order, dashou_yonghuid)
+ elif order.yaoqiuleixing == 2:
+ return self._validate_yajin_requirement(order, dashou_profile)
+ return {'success': True, 'message': '验证通过'}
+
+ def _validate_huiyuan_requirement(self, order, dashou_yonghuid):
+ try:
+ huiyuan_record = Huiyuangoumai.objects.get(
+ huiyuan_id=order.huiyuan_id,
+ yonghu_id=dashou_yonghuid
+ )
+ if huiyuan_record.jiance_shifou_daoqi() or huiyuan_record.huiyuan_zhuangtai != 1:
+ return {'success': False, 'message': '会员已过期或未开通对应会员'}
+ return {'success': True, 'message': '会员验证通过'}
+ except Huiyuangoumai.DoesNotExist:
+ return {'success': False, 'message': '未开通对应会员,无法抢此订单'}
+
+ def _validate_yajin_requirement(self, order, dashou_profile):
+ yajin_required = order.yongjin or 0
+ dashou_yajin = dashou_profile.yajin or 0
+ if dashou_yajin < yajin_required:
+ return {'success': False, 'message': f'押金不足,需要{yajin_required}元,当前有{dashou_yajin}元'}
+ return {'success': True, 'message': '押金验证通过'}
+
+ def _execute_qiangdan(self, order, dashou_profile, dashou_yonghuid):
+ order.zhuangtai = 2
+ order.jiedan_dashou_id = dashou_yonghuid
+ order.user2_id = f"Ds{dashou_yonghuid}"
+ order.save()
+
+ dashou_profile.zhuangtai = 0
+ dashou_profile.jiedanzongliang = F('jiedanzongliang') + 1
+ dashou_profile.jinrijiedan = F('jinrijiedan') + 1
+ dashou_profile.jinyuejiedan = F('jinyuejiedan') + 1
+ dashou_profile.save()
+
+ # ---------- 跨平台询问对方(照抄) ----------
+ def _request_partner_check_claim(self, order, dashou_id):
+ partner_club_id = order.partner_club_id or '000001'
+ try:
+ club = Club.objects.filter(partner_club_id=partner_club_id).first()
+ if not club or not club.partner_domain:
+ logger.error(f"未找到对方俱乐部配置: partner_club_id={partner_club_id}")
+ return None
+
+ my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first()
+ if not my_club:
+ logger.error(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}")
+ return None
+
+ payload = {
+ 'token': settings.CROSS_PLATFORM_TOKEN,
+ 'wofangdingdan_id': order.partner_order_id,
+ 'partner_order_id': order.dingdan_id,
+ 'our_club_id': my_club.club_id,
+ 'partner_club_id': club.partner_club_id,
+ 'dashou_id': dashou_id,
+ 'sj_id': f"Sj{settings.AGENT_SHANGJIA_ID}"
+ }
+
+ url = club.partner_domain.rstrip('/') + '/dingdan/partner_check_claim'
+ resp = requests.post(url, json=payload, timeout=5)
+
+ if resp.status_code != 200:
+ logger.error(f"跨平台抢单询问HTTP失败: {resp.status_code}")
+ return None
+
+ data = resp.json()
+ if data.get('code') == 0:
+ return data.get('data', {}).get('can_claim') is True
+ else:
+ logger.error(f"跨平台抢单询问业务失败: {data}")
+ return False
+ except Exception as e:
+ logger.error(f"跨平台抢单询问异常: {e}", exc_info=True)
+ return None
+
+ # ---------- 异步通知其他俱乐部 ----------
+ def _async_notify_partners_order_claimed(self, order):
+ def notify_task():
+ clubs = Club.objects.filter(is_interop_enabled=True).select_related()
+ for club in clubs:
+ for attempt in range(1, 4):
+ success = self._send_claim_notification_to_partner(order, club)
+ if success:
+ break
+ if attempt < 3:
+ time.sleep(2 ** attempt)
+ else:
+ logger.error(f"通知俱乐部 {club.club_id} 订单已抢失败,重试3次后仍失败")
+
+ thread = threading.Thread(target=notify_task)
+ thread.daemon = True
+ thread.start()
+
+ def _send_claim_notification_to_partner(self, order, club):
+ try:
+ my_club = Club.objects.filter(partner_club_id=club.partner_club_id).first()
+ if not my_club:
+ logger.warning(f"未找到我方俱乐部配置: partner_club_id={club.partner_club_id}")
+ return False
+
+ payload = {
+ 'token': settings.CROSS_PLATFORM_TOKEN,
+ 'our_club_id': my_club.club_id,
+ 'partner_club_id': club.partner_club_id,
+ 'partner_order_id': order.dingdan_id,
+ 'claimed': True
+ }
+ url = club.partner_domain.rstrip('/') + '/dingdan/partner_notify_claim'
+ resp = requests.post(url, json=payload, timeout=3)
+ return resp.status_code == 200
+ except Exception as e:
+ logger.error(f"通知俱乐部 {club.club_id} 异常: {e}")
+ return False
+
+
+class DashouDingdanHuoquView1(APIView):
+ """
+ 打手订单获取接口
+ 支持筛选条件:
+ - zhuangtai_list: 订单状态列表(可选,空数组或不传表示全部状态)
+ - leixing_id: 商品类型ID(可选)
+ - keyword: 搜索关键词,模糊匹配订单ID和订单介绍(可选)
+ """
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 1. 获取当前登录用户的 yonghuid
+ try:
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+ except AttributeError:
+ return Response({
+ 'code': 1,
+ 'msg': '用户认证失败',
+ 'data': None
+ }, status=status.HTTP_401_UNAUTHORIZED)
+
+ # 2. 获取并校验前端参数
+ try:
+ data = request.data
+ zhuangtai_list = data.get('zhuangtai_list', []) # 可选,默认空列表(全部)
+ page = max(1, int(data.get('page', 1)))
+ page_size = min(100, max(1, int(data.get('page_size', 10))))
+ leixing_id = data.get('leixing_id', None) # 可选,商品类型ID
+ keyword = data.get('keyword', '').strip() # 可选,搜索关键词
+ except (ValueError, TypeError):
+ return Response({
+ 'code': 2,
+ 'msg': '参数格式错误',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 3. 参数默认值与类型修正
+ if not isinstance(zhuangtai_list, list):
+ zhuangtai_list = [] # 非列表则视为全部
+ if leixing_id is not None:
+ try:
+ leixing_id = int(leixing_id)
+ except (ValueError, TypeError):
+ leixing_id = None
+
+ # 4. 构建查询条件
+ try:
+ # 基础条件:当前打手接单
+ query = Q(jiedan_dashou_id=yonghuid)
+
+ # 状态筛选:如果 zhuangtai_list 不为空列表,则加入筛选;否则查询全部状态
+ if zhuangtai_list:
+ query &= Q(zhuangtai__in=zhuangtai_list)
+
+ # 商品类型筛选
+ if leixing_id is not None:
+ query &= Q(leixing_id=leixing_id)
+
+ # 关键词搜索:模糊匹配订单ID和订单介绍
+ if keyword:
+ keyword_query = Q(dingdan_id__icontains=keyword) | Q(jieshao__icontains=keyword)
+ query &= keyword_query
+
+ # 5. 执行查询,按创建时间倒序
+ dingdan_query = Dingdan.objects.filter(query).order_by('-create_time')
+
+ # 只选择需要的字段,提高性能
+ dingdan_query = dingdan_query.only(
+ 'dingdan_id',
+ 'zhuangtai',
+ 'dashou_fencheng', # 打手分成金额
+ 'jieshao',
+ 'tupian',
+ 'nicheng',
+ 'create_time'
+ )
+
+ total_count = dingdan_query.count()
+
+ # 6. 分页处理
+ start_index = (page - 1) * page_size
+ end_index = start_index + page_size
+ current_page_orders = dingdan_query[start_index:end_index]
+ current_page_count = len(current_page_orders)
+
+ # 7. 构建返回数据列表
+ order_list = []
+ for order in current_page_orders:
+ order_data = {
+ 'dingdan_id': order.dingdan_id,
+ 'zhuangtai': order.zhuangtai,
+ 'jine': float(order.dashou_fencheng) if order.dashou_fencheng else 0.0,
+ 'jieshao': order.jieshao or '',
+ 'tupian': order.tupian or '',
+ 'nicheng': order.nicheng or '',
+ 'create_time': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else ''
+ }
+ order_list.append(order_data)
+
+ # 8. 判断是否还有更多数据
+ has_more = (current_page_count == page_size) and (end_index < total_count)
+ if total_count == 0:
+ has_more = False
+
+ return Response({
+ 'code': 0,
+ 'msg': '成功',
+ 'data': {
+ 'list': order_list,
+ 'has_more': has_more,
+ 'total': total_count
+ }
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.error(f"打手订单获取失败: {str(e)}", exc_info=True)
+
+ return Response({
+ 'code': 4,
+ 'msg': '服务器内部错误',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+#
+# views.py - 打手COS凭证接口(完整替换)
+import json
+import time
+from tencentcloud.common import credential
+from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
+from tencentcloud.sts.v20180813 import sts_client, models
+from django.conf import settings
+from rest_framework.views import APIView
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.response import Response
+from rest_framework import status
+import logging
+
+logger = logging.getLogger(__name__)
+
+class DashouCOSZhengshuView(APIView):
+ """打手获取COS上传凭证接口(调试版本)"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ try:
+ # 1. 接收参数(修复 int 无 strip 问题)
+ dingdan_id = str(request.data.get('dingdan_id', '')).strip()
+ yongtu = request.data.get('yongtu', 'dingdan')
+
+ # 2. 条件判断修正:除了 fakuan 之外,其他用途均需提供 dingdan_id
+ if yongtu != 'fakuan' and not dingdan_id:
+ return Response(
+ {'code': 400, 'msg': '订单ID不能为空', 'error': 'dingdan_id is required when yongtu is not fakuan'},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 3. 业务验证逻辑(根据用途分别验证)
+ if yongtu == 'chufa':
+ try:
+ from .models import Chufajilu
+ chufa_record = Chufajilu.objects.filter(
+ dingdan_id=dingdan_id,
+ dashouid=request.user.yonghuid,
+ sqzhuangtai__in=[0, 3]
+ ).first()
+ if not chufa_record:
+ return Response(
+ {'code': 403, 'msg': '处罚记录无权限或状态不对',
+ 'error': f'No punishment record found for user {request.user.yonghuid} on order {dingdan_id}'},
+ status=status.HTTP_403_FORBIDDEN
+ )
+ except Exception as e:
+ logger.error(f"查处罚记录异常: {str(e)}")
+ return Response(
+ {'code': 500, 'msg': '服务器内部错误', 'error': str(e)},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+ elif yongtu == 'fakuan':
+ # 罚款申诉不需要订单验证
+ pass
+ else:
+ # 默认为订单相关,需要验证订单且打手必须接单
+ try:
+ from .models import Dingdan
+ dingdan = Dingdan.objects.get(
+ dingdan_id=dingdan_id,
+ jiedan_dashou_id=request.user.yonghuid,
+ zhuangtai__in=[2, 8]
+ )
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found or not assigned to user {request.user.yonghuid}'},
+ status=status.HTTP_403_FORBIDDEN
+ )
+
+ # 4. 获取腾讯云STS临时密钥
+ try:
+ # 初始化固定凭证
+ cred = credential.Credential(
+ settings.COS_SECRET_ID,
+ settings.COS_SECRET_KEY
+ )
+
+ # 创建STS客户端
+ http_profile = HttpProfile()
+ http_profile.endpoint = "sts.tencentcloudapi.com"
+ client_profile = ClientProfile()
+ client_profile.httpProfile = http_profile
+ client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
+
+ # 构建策略资源
+ resources = []
+ if yongtu == 'chufa':
+ resources.append(
+ f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/dssstp/*"
+ )
+ elif yongtu == 'fakuan':
+ resources.append(
+ f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/dashoufakuan/dashoufakuanshensu/*"
+ )
+ else:
+ resources.append(
+ f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
+ )
+
+ policy = {
+ "version": "2.0",
+ "statement": [{
+ "effect": "allow",
+ "action": [
+ "cos:PutObject",
+ "cos:PostObject",
+ "cos:InitiateMultipartUpload",
+ "cos:UploadPart",
+ "cos:CompleteMultipartUpload"
+ ],
+ "resource": resources
+ }]
+ }
+
+ req = models.GetFederationTokenRequest()
+ req.Name = f"dashou_{dingdan_id}_{int(time.time())}"
+ req.Policy = json.dumps(policy)
+ req.DurationSeconds = 7200
+
+ resp = client.GetFederationToken(req)
+ credentials = resp.Credentials
+
+ except TencentCloudSDKException as e:
+ logger.error(f"获取STS临时密钥失败: {str(e)}")
+ return Response({
+ 'code': 500,
+ 'msg': f'获取上传凭证失败: {str(e)}',
+ 'error_detail': str(e)
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ # 5. 构建响应
+ current_time = int(time.time())
+ upload_dir = 'fakuan/dashoufakuan/dashoufakuanshensu/' if yongtu == 'fakuan' else (
+ 'a_long/chfajltp/dssstp/' if yongtu == 'chufa' else f'order/{dingdan_id}/'
+ )
+ response_data = {
+ 'credentials': {
+ 'tmpSecretId': credentials.TmpSecretId,
+ 'tmpSecretKey': credentials.TmpSecretKey,
+ 'sessionToken': credentials.Token,
+ },
+ 'startTime': current_time,
+ 'expiredTime': current_time + 7200,
+ 'bucket': settings.COS_BUCKET,
+ 'region': settings.COS_REGION,
+ 'upload_dir': upload_dir,
+ }
+ return Response({'code': 0, 'msg': '成功', 'data': response_data})
+
+ except Exception as e:
+ # 捕捉所有未预期的异常,返回完整 traceback
+ logger.error(f'COS凭证接口未知错误: {traceback.format_exc()}')
+ return Response({
+ 'code': 500,
+ 'msg': f'服务器内部错误: {str(e)}',
+ 'error_detail': traceback.format_exc()
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+class ShangjiaCOSZhengshuView(APIView):
+ """商家获取COS上传凭证接口(用于处罚申诉图片上传)"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ try:
+ # 1. 接收参数
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+ yongtu = request.data.get('yongtu', 'chufa') # 用途:chufa=处罚申诉
+
+ if not dingdan_id:
+ return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ # 2. 🔴【修改】验证商家身份 - 通过反向查询shop_profile判断是否为商家
+ try:
+ # 检查是否有商家扩展表
+ shangjia_profile = request.user.shop_profile
+
+ # 验证商家状态是否为1(正常)
+ if shangjia_profile.zhuangtai != 1:
+ return Response({'code': 3, 'msg': '商家账号状态异常,无法操作', 'data': None},
+ status=status.HTTP_403_FORBIDDEN)
+
+ # 🔴【重要】获取商家ID(从商家扩展表中获取)
+ shangjia_id = request.user.yonghuid # 假设商家扩展表中有yonghuid字段
+
+ except AttributeError:
+ # 没有shop_profile属性,说明不是商家
+ return Response({'code': 3, 'msg': '非商家用户无权限', 'data': None},
+ status=status.HTTP_403_FORBIDDEN)
+
+ # 3. 🔴【修改】验证订单权限 - 通过商家订单扩展表验证
+ try:
+ from .models import Dingdan, DingdanShangjia
+
+ # 获取订单
+ dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
+
+ # 🔴【重要】通过反向查询商家扩展表验证订单是否属于当前商家
+ try:
+ shangjia_kuozhan = dingdan.shangjia_kuozhan # 使用反向关系名
+
+ # 验证商家ID是否匹配
+ if str(shangjia_kuozhan.shangjia_id) != str(shangjia_id):
+ return Response({'code': 2, 'msg': '订单不属于当前商家', 'data': None},
+ status=status.HTTP_403_FORBIDDEN)
+
+ # 验证订单状态是否允许申请处罚(根据前端逻辑)
+ allowed_zhuangtai = [2, 4, 5, 6, 8]
+ if dingdan.zhuangtai not in allowed_zhuangtai:
+ return Response({'code': 4, 'msg': '当前订单状态不允许申请处罚', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ # 🔴【新增】检查是否已存在处罚申请(通过Chufajilu模型)
+ # 注意:这里假设Chufajilu模型存在,并且有shangjia_id字段
+ try:
+ from .models import Chufajilu
+ existing_chufa = Chufajilu.objects.filter(
+ dingdan_id=dingdan_id,
+ qingqiuid=shangjia_id,
+ sqzhuangtai__in=[0, 1] # 审核中或已处罚状态
+ ).exists()
+
+ if existing_chufa:
+ return Response({'code': 5, 'msg': '该订单已有处罚申请,请勿重复上传', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST)
+ except ImportError:
+ # 如果没有Chufajilu模型,跳过这个检查
+ pass
+
+ except DingdanShangjia.DoesNotExist:
+ # 没有商家扩展记录,说明这不是商家订单
+ return Response({'code': 2, 'msg': '非商家订单无法操作', 'data': None},
+ status=status.HTTP_403_FORBIDDEN)
+
+ except Dingdan.DoesNotExist:
+ return Response({'code': 2, 'msg': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ logger.error(f"订单验证异常: {str(e)}")
+ return Response({'code': 500, 'msg': '服务器验证异常', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ # 4. 获取腾讯云STS临时密钥
+ try:
+ # 初始化固定凭证
+ cred = credential.Credential(
+ settings.COS_SECRET_ID,
+ settings.COS_SECRET_KEY
+ )
+
+ # 创建STS客户端
+ http_profile = HttpProfile()
+ http_profile.endpoint = "sts.tencentcloudapi.com"
+ client_profile = ClientProfile()
+ client_profile.httpProfile = http_profile
+
+ client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
+
+ # 🔴【修改】构建策略 - 只允许上传到商家处罚证据图片目录
+ resources = [
+ f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/sjzjtp/*"
+ ]
+
+ policy = {
+ "version": "2.0",
+ "statement": [
+ {
+ "effect": "allow",
+ "action": [
+ "cos:PutObject",
+ "cos:PostObject",
+ "cos:InitiateMultipartUpload",
+ "cos:UploadPart",
+ "cos:CompleteMultipartUpload"
+ ],
+ "resource": resources
+ }
+ ]
+ }
+
+ # 请求STS临时凭证
+ req = models.GetFederationTokenRequest()
+ # 🔴【修改】命名包含商家ID
+ req.Name = f"shangjia_{shangjia_id}_{dingdan_id}_{int(time.time())}"
+ req.Policy = json.dumps(policy)
+ req.DurationSeconds = 7200 # 2小时有效期
+
+ resp = client.GetFederationToken(req)
+
+ # 提取临时密钥
+ credentials = resp.Credentials
+
+ except TencentCloudSDKException as e:
+ logger.error(f"商家获取STS临时密钥失败: {str(e)}")
+ return Response({
+ 'code': 500,
+ 'msg': f'获取上传凭证失败: {str(e)}',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ # 5. 构建响应数据
+ current_time = int(time.time())
+ response_data = {
+ 'credentials': {
+ 'tmpSecretId': credentials.TmpSecretId,
+ 'tmpSecretKey': credentials.TmpSecretKey,
+ 'sessionToken': credentials.Token,
+ },
+ 'startTime': current_time,
+ 'expiredTime': current_time + 7200,
+ 'bucket': settings.COS_BUCKET,
+ 'region': settings.COS_REGION,
+ # 🔴【修改】商家专用上传目录
+ 'upload_dir': 'a_long/chfajltp/sjzjtp/',
+ # 🔴【新增】返回商家ID和订单ID,便于前端生成文件名
+ 'dingdan_id': dingdan_id,
+ 'shangjia_id': shangjia_id,
+ # 🔴【新增】可选:返回商家昵称,便于前端显示
+ 'shangjia_nicheng': shangjia_profile.nicheng if hasattr(shangjia_profile, 'nicheng') else ''
+ }
+
+ # 6. 返回成功
+ return Response({
+ 'code': 0,
+ 'msg': '成功',
+ 'data': response_data
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ logger.error(f'商家COS凭证接口未知错误: {str(e)}', exc_info=True)
+ return Response({
+ 'code': 500,
+ 'msg': f'服务器内部错误: {str(e)}',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+class DashouTijiaoView(APIView):
+ """打手提交订单完成接口"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ try:
+ # 1. 获取参数
+ data = request.data
+ dingdan_id = data.get('dingdan_id', '')
+ liuyan = data.get('liuyan', '')
+ jiaofu_tupian_urls = data.get('jiaofu_tupian_urls', [])
+
+ # 2. 参数验证
+ if not dingdan_id:
+ return Response({
+ 'code': 1,
+ 'msg': '订单ID不能为空',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ if not isinstance(jiaofu_tupian_urls, list):
+ return Response({
+ 'code': 2,
+ 'msg': '图片URL列表格式错误',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 限制图片数量(最多9张,最少1张)
+ if len(jiaofu_tupian_urls) == 0:
+ return Response({
+ 'code': 3,
+ 'msg': '至少需要上传一张图片',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ if len(jiaofu_tupian_urls) > 9:
+ return Response({
+ 'code': 4,
+ 'msg': '最多只能上传9张图片',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 留言长度限制(50字)
+ if liuyan and len(liuyan) > 50:
+ return Response({
+ 'code': 5,
+ 'msg': '留言最多50个字',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 3. 获取当前用户并验证打手身份
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+
+ try:
+ dashou_profile = current_user.dashou_profile
+ except UserDashou.DoesNotExist:
+ return Response({
+ 'code': 6,
+ 'msg': '用户不是打手身份',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 4. 验证订单状态和权限
+ try:
+ dingdan = Dingdan.objects.get(
+ dingdan_id=dingdan_id,
+ jiedan_dashou_id=yonghuid,
+ zhuangtai=2 # 必须是进行中的订单
+ )
+ except Dingdan.DoesNotExist:
+ return Response({
+ 'code': 7,
+ 'msg': '订单不存在或没有提交权限',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 5. 存储图片到Dashoutupian表
+ for tupian_url in jiaofu_tupian_urls:
+ # 创建打手提交图片记录
+ Dashoutupian.objects.create(
+ dingdan_id=dingdan_id,
+ dashou=yonghuid,
+ tupian=tupian_url,
+ create_time=timezone.now()
+ )
+
+ # 6. 更新订单主表
+ dingdan.dashou_liuyan = liuyan
+ dingdan.zhuangtai = 8 # 状态改为结算中
+ dingdan.update_time = timezone.now()
+ dingdan.save()
+
+ # 7. 更新打手扩展表状态
+ dashou_profile.zhuangtai = 1 # 改为数字1(根据你之前说的)
+ dashou_profile.save()
+
+ from dengji.services import record_dashou_biaoxian
+ # ========== 新增:记录打手提交时间 ==========
+ record_dashou_biaoxian(
+ dingdan_id=dingdan_id,
+ dashou_id=yonghuid,
+ xingwei=2, # 提交
+ tijiao_time=timezone.now()
+ )
+ # ===========================================
+
+ # 8. 返回成功响应
+ return Response({
+ 'code': 0,
+ 'msg': '订单提交成功',
+ 'data': {
+ 'dingdan_id': dingdan_id,
+ 'new_zhuangtai': 8, # 新的订单状态
+ 'dashou_zhuangtai': 1 # 打手新的状态
+ }
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.error(f"打手提交订单失败: {str(e)}", exc_info=True)
+
+ return Response({
+ 'code': 8,
+ 'msg': '服务器内部错误',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+class DashouDingdanXiangqingView(APIView):
+ """打手订单详情获取接口"""
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ try:
+ # 1. 获取订单ID
+ dingdan_id = request.data.get('dingdan_id', '')
+ if not dingdan_id:
+ return Response({
+ 'code': 1,
+ 'msg': '订单ID不能为空',
+ 'data': None
+ }, status=status.HTTP_400_BAD_REQUEST)
+
+ # 2. 获取当前用户并验证权限
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+
+ try:
+ dashou_profile = current_user.dashou_profile
+ except UserDashou.DoesNotExist:
+ return Response({
+ 'code': 2,
+ 'msg': '用户不是打手身份',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 3. 查询订单主表
+ try:
+ dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response({
+ 'code': 3,
+ 'msg': '订单不存在',
+ 'data': None
+ }, status=status.HTTP_404_NOT_FOUND)
+
+ # 4. 验证订单是否属于当前打手
+ if dingdan.jiedan_dashou_id != yonghuid:
+ return Response({
+ 'code': 4,
+ 'msg': '没有权限查看此订单',
+ 'data': None
+ }, status=status.HTTP_403_FORBIDDEN)
+
+ # 5. 构建基础订单数据
+ dingdan_data = {
+ 'dingdan_id': dingdan.dingdan_id,
+ 'zhuangtai': dingdan.zhuangtai,
+ 'jine': float(dingdan.dashou_fencheng) if dingdan.dashou_fencheng else 0.0,
+ 'jieshao': dingdan.jieshao or '',
+ 'tupian': dingdan.tupian or '',
+ 'nicheng': dingdan.nicheng or '',
+ 'beizhu': dingdan.beizhu or '',
+ 'create_time': dingdan.create_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan.create_time else '',
+ 'tuikuan_liyou':dingdan.tkly or '',
+ 'dashou_liuyan': dingdan.dashou_liuyan or '',
+ 'fuwudashou_id': dingdan.jiedan_dashou_id or '',
+ 'fadanpingtai': dingdan.fadan_pingtai or ''
+ }
+
+ # 6. 查询打手提交的图片
+ dashou_tupian_list = Dashoutupian.objects.filter(
+ dingdan_id=dingdan_id,
+ dashou=yonghuid
+ ).order_by('create_time')
+
+ jiaofu_tupian_urls = []
+ for tupian_record in dashou_tupian_list:
+ if tupian_record.tupian:
+ jiaofu_tupian_urls.append(tupian_record.tupian)
+
+ dingdan_data['dashou_jiaofu'] = jiaofu_tupian_urls
+
+ # 7. 查询商家扩展表信息
+ shangjia_info = {}
+ try:
+ shangjia_kuozhan = dingdan.shangjia_kuozhan
+ shangjia_info = {
+ 'shangjia_id': shangjia_kuozhan.shangjia_id or '',
+ 'shangjia_nicheng': shangjia_kuozhan.sjnicheng or '',
+ 'shangjia_liuyan': shangjia_kuozhan.shangjia_pingjia or '',
+ #'tuikuan_liyou': '',
+ 'chufa_liyou': shangjia_kuozhan.cfliyou or '',
+ 'chufa_zhuangtai': shangjia_kuozhan.sqzhuangtai or 0,
+ #'chufa_jieguo': shangjia_kuozhan.chufa_jieguo or '',
+ 'bohui_liyou': shangjia_kuozhan.bhliyou or ''
+ }
+ except DingdanShangjia.DoesNotExist:
+ # 没有商家扩展信息,返回空值
+ shangjia_info = {
+ 'shangjia_id': '',
+ 'shangjia_nicheng': '',
+ 'shangjia_liuyan': '',
+ 'tuikuan_liyou': '',
+ 'chufa_liyou': '',
+ 'chufa_zhuangtai': 0,
+ 'chufa_jieguo': '',
+ 'bohui_liyou': ''
+ }
+
+ # 8. 查询平台扩展表信息(如果有的话)
+ pingtai_info = {}
+ try:
+ pingtai_kuozhan = dingdan.pingtai_kuozhan
+ pingtai_info = {
+ 'laoban_id': pingtai_kuozhan.laoban_id or '',
+ 'laoban_pingjia': pingtai_kuozhan.laoban_pingjia or ''
+ }
+ except DingdanPingtai.DoesNotExist:
+ pingtai_info = {
+ 'laoban_id': '',
+ 'laoban_pingjia': ''
+ }
+
+ # 9. 合并所有数据
+ response_data = {
+ **dingdan_data,
+ **shangjia_info,
+ **pingtai_info
+ }
+
+ return Response({
+ 'code': 0,
+ 'msg': '成功',
+ 'data': response_data
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ import logging
+ logger = logging.getLogger(__name__)
+ logger.error(f"获取打手订单详情失败: {str(e)}", exc_info=True)
+
+ return Response({
+ 'code': 5,
+ 'msg': '服务器内部错误',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+# dingdan/views.py
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.parsers import JSONParser
+import logging
+
+
+logger = logging.getLogger(__name__)
+
+
+class AdGetDingdanXiangQing(APIView):
+ """
+ 后台订单详情获取接口
+ 权限: JWT Token认证 + 管理员身份验证
+ 前端调用: POST /dingdan/adddxq
+ 参数: {"zhanghao": "管理员手机号", "dingdan_id": "订单ID"}
+ """
+
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ """
+ 获取订单详情数据
+ 返回: 订单主表数据 + 扩展表数据 + 打手图片列表
+ """
+ try:
+ # 1. 获取前端参数
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+
+ if not qianzhanghao or not dingdan_id:
+ return Response(
+ {'code': 400, 'message': '参数不完整', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 2. JWT Token身份验证 (IsAuthenticated已处理)
+ dangqianyonghu = request.user
+
+ # 3. 验证手机号是否匹配
+ if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 4. 验证用户类型是否为管理员
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 5. 验证管理员扩展表是否存在
+ try:
+ guanliyuan = dangqianyonghu.admin_profile
+ except AttributeError:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 6. 查询订单主表
+ try:
+ from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia, Dashoutupian
+
+ # 查询订单主表 - 使用select_related预加载关联表
+ dingdan_obj = Dingdan.objects.select_related(
+ 'pingtai_kuozhan', 'shangjia_kuozhan'
+ ).get(dingdan_id=dingdan_id)
+
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 7. 构建返回数据 - 主表字段
+ response_data = {
+ # 基础信息字段
+ 'dingdan_id': dingdan_obj.dingdan_id or '',
+ 'jieshao': dingdan_obj.jieshao or '',
+ 'zhuangtai': dingdan_obj.zhuangtai or 0,
+ 'leixing': dingdan_obj.leixing_id or 0, # 前端需要 leixing,对应 leixing_id
+ 'fadanpingtai': dingdan_obj.fadan_pingtai or 0, # 前端需要 fadanpingtai,对应 fadan_pingtai
+ 'jiage': float(dingdan_obj.jine) if dingdan_obj.jine else 0.00, # 前端需要 jiage,对应 jine
+ 'tupian_url': dingdan_obj.tupian or '', # 前端需要 tupian_url,对应 tupian
+ 'beizhu': dingdan_obj.beizhu or '',
+
+ # 服务信息字段
+ 'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '',
+ 'dashou_fencheng': float(dingdan_obj.dashou_fencheng) if dingdan_obj.dashou_fencheng else 0.00,
+ 'zhiding_id': dingdan_obj.zhiding_id or '',
+ 'dashou_liuyan': dingdan_obj.dashou_liuyan or '',
+
+ # 时间字段
+ 'chuangjianshijian': dingdan_obj.create_time.strftime(
+ '%Y-%m-%d %H:%M:%S') if dingdan_obj.create_time else '',
+ 'genggaishijian': dingdan_obj.update_time.strftime(
+ '%Y-%m-%d %H:%M:%S') if dingdan_obj.update_time else '',
+ }
+
+ # 8. 根据发单平台查询扩展表数据
+ fadan_pingtai = dingdan_obj.fadan_pingtai
+
+ if fadan_pingtai == 1:
+ # 平台发单 - 查询平台订单扩展表
+ try:
+ # 使用反向关联 pingtai_kuozhan
+ pingtai_kuozhan = dingdan_obj.pingtai_kuozhan
+
+ response_data.update({
+ 'laoban_id': pingtai_kuozhan.laoban_id or '',
+ 'nicheng': dingdan_obj.nicheng or '', # 这个字段在订单主表
+ 'laoban_pingjia': pingtai_kuozhan.laoban_pingjia or '',
+ })
+ except DingdanPingtai.DoesNotExist:
+ # 扩展表不存在,返回空值
+ response_data.update({
+ 'laoban_id': '',
+ 'nicheng': dingdan_obj.nicheng or '',
+ 'laoban_pingjia': '',
+ })
+
+ elif fadan_pingtai == 2:
+ # 商家发单 - 查询商家订单扩展表
+ try:
+ # 使用反向关联 shangjia_kuozhan
+ shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
+
+ response_data.update({
+ 'shangjia_id': shangjia_kuozhan.shangjia_id or '',
+ 'sjnicheng': shangjia_kuozhan.sjnicheng or '',
+ 'shangjia_pingjia': shangjia_kuozhan.shangjia_pingjia or '',
+ 'cfliyou': shangjia_kuozhan.cfliyou or '', # 商家处罚理由
+ 'bhliyou': shangjia_kuozhan.bhliyou or '', # 驳回理由
+ })
+ except DingdanShangjia.DoesNotExist:
+ # 扩展表不存在,返回空值
+ response_data.update({
+ 'shangjia_id': '',
+ 'sjnicheng': '',
+ 'shangjia_pingjia': '',
+ 'cfliyou': '',
+ 'bhliyou': '',
+ })
+
+ # 9. 查询打手提交的图片
+ try:
+ # 查询该订单的所有打手提交图片
+ dashou_tupian_list = Dashoutupian.objects.filter(
+ dingdan_id=dingdan_id
+ ).values_list('tupian', flat=True)
+
+ # 转换为列表,过滤空值
+ dashou_images = [img for img in dashou_tupian_list if img]
+ response_data['dashou_images'] = dashou_images
+ except Exception as e:
+ logger.warning(f"查询打手图片失败: {str(e)}")
+ response_data['dashou_images'] = []
+
+ # 🔥 新增:查询退款记录表中的商家退款理由
+ try:
+ # 导入退款记录模型
+ from dingdan.models import Tuikuanjilu
+
+ # 查询该订单的退款记录(一个订单可能有多次退款申请,我们取最新的)
+ tuikuan_record = Tuikuanjilu.objects.filter(
+ dingdan_id=dingdan_id
+ ).order_by('-create_time').first() # 按创建时间倒序,取最新的
+
+ if tuikuan_record and tuikuan_record.liyou:
+ # 如果有退款记录且理由不为空,添加到返回数据
+ response_data['tuikuan_liyou'] = tuikuan_record.liyou
+ else:
+ # 没有找到退款记录或理由为空
+ response_data['tuikuan_liyou'] = ''
+ except Tuikuanjilu.DoesNotExist:
+ # 退款记录表不存在该订单的记录
+ response_data['tuikuan_liyou'] = ''
+ except Exception as e:
+ # 查询过程中出现其他异常
+ logger.warning(f"查询退款记录失败: {str(e)}")
+ response_data['tuikuan_liyou'] = ''
+
+ # 10. 返回成功响应
+ return Response({
+ 'code': 0,
+ 'message': '获取成功',
+ 'data': response_data
+ })
+
+ except Exception as e:
+ logger.error(f"获取订单详情异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+
+# 获取日志器
+logger = logging.getLogger(__name__)
+
+
+class AdGengHuanDaShou(APIView):
+ """
+ 后台更换打手接口
+ 权限: JWT Token认证 + 管理员身份验证
+ 前端调用: POST /dingdan/adddgh
+ 参数: {
+ "zhanghao": "管理员账号",
+ "dingdan_id": "订单ID",
+ "new_dashou_id": "新打手ID"
+ }
+ """
+
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ """
+ 更换订单的打手
+ 核心逻辑:
+ 1. 验证管理员身份
+ 2. 验证订单存在且状态为进行中(2)或结算中(8)
+ 3. 验证新打手ID存在且是打手身份
+ 4. 验证新打手状态正常(zhuangtai=1且zhanghaozhuangtai=1)
+ 5. 根据订单的抢单要求类型进行验证:
+ - 会员抢单(1):验证打手有对应会员且未过期
+ - 押金抢单(2):验证打手押金≥订单押金
+ 6. 更新新打手状态和统计数据
+ 7. 更新原打手状态和统计数据
+ 8. 更新订单信息
+ """
+ try:
+ # 1. 获取前端参数
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+ new_dashou_id = request.data.get('new_dashou_id', '').strip()
+
+ if not qianzhanghao or not dingdan_id or not new_dashou_id:
+ return Response(
+ {'code': 400, 'message': '参数不完整', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 2. JWT Token身份验证
+ dangqianyonghu = request.user
+
+ # 3. 验证手机号是否匹配
+ if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 4. 验证用户类型是否为管理员
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 5. 验证管理员扩展表是否存在
+ try:
+ guanliyuan = dangqianyonghu.admin_profile
+ except AttributeError:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 6. 查询订单主表
+ try:
+ from dingdan.models import Dingdan
+ dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 7. 验证订单状态是否为进行中(2)或结算中(8)
+ if dingdan_obj.zhuangtai not in [2, 8]:
+ return Response(
+ {'code': 400, 'message': '订单状态不允许更换打手', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 8. 验证新打手ID是否存在,并且是打手身份
+ try:
+ new_dashou_user = UserMain.objects.get(
+ yonghuid=new_dashou_id,
+ #user_type='dashou'
+ )
+ except UserMain.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '打手不存在或不是打手身份', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 9. 获取新打手扩展表
+ try:
+ new_dashou_profile = new_dashou_user.dashou_profile
+ except AttributeError:
+ return Response(
+ {'code': 404, 'message': '打手信息不完整', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 10. 验证新打手状态:zhuangtai=1且zhanghaozhuangtai=1
+ if new_dashou_profile.zhuangtai != 1 or new_dashou_profile.zhanghaozhuangtai != 1:
+ return Response(
+ {'code': 400, 'message': '打手状态不允许接单', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 11. 根据订单的抢单要求类型进行验证
+ yaoqiuleixing = dingdan_obj.yaoqiuleixing
+
+ if yaoqiuleixing == 1: # 会员抢单
+ # 获取订单的会员ID
+ huiyuan_id = dingdan_obj.huiyuan_id
+ if not huiyuan_id:
+ return Response(
+ {'code': 400, 'message': '订单会员ID为空', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 查询会员购买记录
+ try:
+ from shangpin.models import Huiyuangoumai
+ huiyuan_goumai = Huiyuangoumai.objects.get(
+ huiyuan_id=huiyuan_id,
+ yonghu_id=new_dashou_id # 注意:yonghu_id存储的是用户ID(yonghuid)
+ )
+ except Huiyuangoumai.DoesNotExist:
+ return Response(
+ {'code': 400, 'message': '打手未购买该订单要求的会员', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 检查会员是否过期
+ # ✅ 可以调用!因为jiance_shifou_daoqi()是Huiyuangoumai模型的实例方法
+ is_expired = huiyuan_goumai.jiance_shifou_daoqi()
+ if is_expired or huiyuan_goumai.huiyuan_zhuangtai != 1:
+ return Response(
+ {'code': 400, 'message': '打手的会员已过期', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ elif yaoqiuleixing == 2: # 押金抢单
+ # 获取订单的押金(yongjin字段)
+ order_yajin = dingdan_obj.yongjin
+ if order_yajin is None:
+ return Response(
+ {'code': 400, 'message': '订单押金为空', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 验证打手押金是否大于等于订单押金
+ if new_dashou_profile.yajin < order_yajin:
+ return Response(
+ {'code': 400, 'message': '打手押金不足', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+ else:
+ # 未知的抢单要求类型
+ return Response(
+ {'code': 400, 'message': '订单抢单要求类型错误', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 12. 所有验证通过,开始更换打手(使用事务保证一致性)
+ with transaction.atomic():
+ # 12.1 更新新打手扩展表
+ new_dashou_profile.zhuangtai = 0 # 接单中
+ new_dashou_profile.jiedanzongliang += 1
+ new_dashou_profile.jinrijiedan += 1
+ new_dashou_profile.jinyuejiedan += 1
+ new_dashou_profile.save()
+
+ # 12.2 如果原订单有接单打手,则更新原打手状态
+ old_dashou_id = dingdan_obj.jiedan_dashou_id
+ if old_dashou_id:
+ try:
+ # 查询原打手主表
+ old_dashou_user = UserMain.objects.get(
+ yonghuid=old_dashou_id,
+ #user_type='dashou'
+ )
+
+ # 获取原打手扩展表
+ old_dashou_profile = old_dashou_user.dashou_profile
+
+ # 更新原打手状态
+ old_dashou_profile.zhuangtai = 1 # 空闲
+ old_dashou_profile.tuikuanliang += 1 # 退款订单总量+1
+ old_dashou_profile.save()
+
+ logger.info(f"成功更新原打手{old_dashou_id}状态")
+ except UserMain.DoesNotExist:
+ # 原打手不存在,继续执行不影响更换
+ logger.warning(f"原打手{old_dashou_id}不存在,跳过更新")
+ except AttributeError:
+ # 原打手扩展表不存在,继续执行不影响更换
+ logger.warning(f"原打手{old_dashou_id}扩展信息不存在,跳过更新")
+ except Exception as e:
+ # 其他异常,记录日志但继续执行
+ logger.error(f"更新原打手状态异常: {str(e)}", exc_info=True)
+
+ # 12.3 更新订单表
+ dingdan_obj.jiedan_dashou_id = new_dashou_id
+ dingdan_obj.zhuangtai = 2 # 进行中状态
+ dingdan_obj.save()
+
+ # 13. 返回成功响应
+ return Response({
+ 'code': 0,
+ 'message': '更换打手成功',
+ 'data': None
+ })
+
+ except Exception as e:
+ logger.error(f"更换打手接口异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+
+import logging
+
+# 获取日志器
+logger = logging.getLogger(__name__)
+
+
+class AdQiangZhiJieDan(APIView):
+ """
+ 后台强制结单接口
+ 权限: JWT Token认证 + 管理员身份验证
+ 前端调用: POST /dingdan/adddqzjd
+ 参数: {
+ "zhanghao": "管理员账号",
+ "dingdan_id": "订单ID"
+ }
+ """
+
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ """
+ 强制结单接口
+ 核心逻辑:
+ 1. 验证管理员身份
+ 2. 验证订单状态为2(进行中)、8(结算中)或4(退款审核中)才能强制结单
+ 3. 根据发单平台(1=平台发单, 2=商家发单)分别处理
+ 4. 更新订单状态为3(已完成)
+ 5. 给打手结算分成金额
+ 6. 如果是商家发单,还需要更新商家成交订单数量
+ """
+ try:
+ # 1. 获取前端参数
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+
+ if not qianzhanghao or not dingdan_id:
+ return Response(
+ {'code': 400, 'message': '参数不完整', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 2. JWT Token身份验证
+ dangqianyonghu = request.user
+
+ # 3. 验证手机号是否匹配
+ if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 4. 验证用户类型是否为管理员
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 5. 验证管理员扩展表是否存在
+ try:
+ guanliyuan = dangqianyonghu.admin_profile
+ except AttributeError:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 6. 查询订单主表
+ try:
+ from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia
+ dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 7. 验证订单状态是否为2、8或4
+ if dingdan_obj.zhuangtai not in [2, 8, 4]:
+ return Response(
+ {'code': 400, 'message': '订单状态不允许强制结单', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 8. 获取订单的重要字段
+ jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
+ dashou_fencheng = dingdan_obj.dashou_fencheng # 打手分成金额
+ fadan_pingtai = dingdan_obj.fadan_pingtai # 发单平台
+
+ # 9. 验证打手分成金额是否存在
+ if dashou_fencheng is None:
+ return Response(
+ {'code': 400, 'message': '订单缺少打手分成金额', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 10. 使用事务保证数据一致性
+ with transaction.atomic():
+ # 10.1 更新订单状态为3(已完成)
+ dingdan_obj.zhuangtai = 3
+ dingdan_obj.save()
+
+ # 10.2 处理打手结算
+ if jiedan_dashou_id:
+ try:
+ # 查询打手用户主表
+ dashou_user = UserMain.objects.get(
+ yonghuid=jiedan_dashou_id,
+ #user_type='dashou'
+ )
+
+ # 获取打手扩展表
+ dashou_profile = dashou_user.dashou_profile
+
+ # 更新打手扩展表数据
+ # ✅ 确认字段:chengjiaozongliang 成交单总量
+ dashou_profile.chengjiaozongliang += 1
+
+ # ✅ 确认字段:yue 可提现余额
+ dashou_profile.yue += dashou_fencheng
+
+ # ✅ 确认字段:zonge 打单赚取总额
+ dashou_profile.zonge += dashou_fencheng
+
+ # ✅ 确认字段:jinrishouyi 今日收益金额
+ dashou_profile.jinrishouyi += dashou_fencheng
+
+ # ✅ 确认字段:jinyueshouyi 今月收益金额
+ dashou_profile.jinyueshouyi += dashou_fencheng
+
+ dashou_profile.save()
+ logger.info(f"强制结单成功,打手{jiedan_dashou_id}结算分成:{dashou_fencheng}")
+
+ except UserMain.DoesNotExist:
+ # 打手用户不存在,记录日志但不影响强制结单
+ logger.warning(f"强制结单:打手{jiedan_dashou_id}不存在,跳过打手结算")
+ except AttributeError:
+ # 打手扩展表不存在,记录日志但不影响强制结单
+ logger.warning(f"强制结单:打手{jiedan_dashou_id}扩展信息不存在,跳过打手结算")
+
+ # 10.3 如果是商家发单,还需要更新商家扩展表
+ if fadan_pingtai == 2:
+ try:
+ # 获取商家订单扩展表
+ shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
+ shangjia_id = shangjia_kuozhan.shangjia_id
+
+ if shangjia_id:
+ # 查询商家用户主表
+ shangjia_user = UserMain.objects.get(
+ yonghuid=shangjia_id,
+ #user_type='shop'
+ )
+
+ # 获取商家扩展表
+ shangjia_profile = shangjia_user.shop_profile
+
+ # ✅ 确认字段:chengjiao 商家成交订单总量
+ shangjia_profile.chengjiao += 1
+ shangjia_profile.save()
+
+ logger.info(f"强制结单成功,商家{shangjia_id}成交订单+1")
+
+ except DingdanShangjia.DoesNotExist:
+ # 商家订单扩展表不存在,跳过商家更新
+ logger.warning(f"强制结单:订单{dingdan_id}商家扩展表不存在,跳过商家更新")
+ except UserMain.DoesNotExist:
+ # 商家用户不存在,跳过商家更新
+ logger.warning(f"强制结单:商家{shangjia_id}不存在,跳过商家更新")
+ except AttributeError:
+ # 商家扩展表不存在,跳过商家更新
+ logger.warning(f"强制结单:商家{shangjia_id}扩展信息不存在,跳过商家更新")
+
+ # 12. 更新平台收益流水(仅在平台发单时)
+ if dingdan_obj.fadan_pingtai == 1:
+ try:
+ from dingdan.utils import update_platform_profit # 导入函数
+ update_platform_profit(dingdan_id)
+ except Exception as e:
+ # 平台收益更新失败不影响主流程,但记录日志
+ logger.error(f"强制结单后更新平台收益失败: {str(e)}")
+
+ # 11. 返回成功响应
+ return Response({
+ 'code': 0,
+ 'message': '强制结单成功',
+ 'data': None
+ })
+
+ except Exception as e:
+ logger.error(f"强制结单接口异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+
+class AdJuJueTuiKuan(APIView):
+ """
+ 后台拒绝退款接口
+ 权限: JWT Token认证 + 管理员身份验证
+ 前端调用: POST /dingdan/adddjj
+ 参数: {
+ "zhanghao": "管理员账号",
+ "dingdan_id": "订单ID",
+ "jujue_liyou": "拒绝退款理由(可选)"
+ }
+ """
+
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ """
+ 拒绝退款接口
+ 核心逻辑:
+ 1. 验证管理员身份
+ 2. 拒绝退款后订单状态改为6(退款失败)
+ 3. 给打手结算分成金额
+ 4. 如果是商家发单,更新商家成交订单数量
+ 5. 更新退款记录表,状态改为2(驳回)
+ """
+ try:
+ # 1. 获取前端参数
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+ jujue_liyou = request.data.get('jujue_liyou', '').strip()
+
+ if not qianzhanghao or not dingdan_id:
+ return Response(
+ {'code': 400, 'message': '参数不完整', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 2. JWT Token身份验证
+ dangqianyonghu = request.user
+
+ # 3. 验证手机号是否匹配
+ if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 4. 验证用户类型是否为管理员
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 5. 验证管理员扩展表是否存在
+ try:
+ guanliyuan = dangqianyonghu.admin_profile
+ except AttributeError:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 6. 查询订单主表
+ try:
+ from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
+ dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 7. 获取订单的重要字段
+ jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
+ dashou_fencheng = dingdan_obj.dashou_fencheng # 打手分成金额
+ fadan_pingtai = dingdan_obj.fadan_pingtai # 发单平台
+
+ # 8. 验证打手分成金额是否存在
+ if dashou_fencheng is None:
+ return Response(
+ {'code': 400, 'message': '订单缺少打手分成金额', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 9. 使用事务保证数据一致性
+ with transaction.atomic():
+ # 9.1 更新订单状态为6(退款失败)
+ dingdan_obj.zhuangtai = 6
+ dingdan_obj.save()
+
+ # 9.2 处理打手结算
+ if jiedan_dashou_id:
+ try:
+ # 查询打手用户主表
+ dashou_user = UserMain.objects.get(
+ yonghuid=jiedan_dashou_id,
+ #user_type='dashou'
+ )
+
+ # 获取打手扩展表
+ dashou_profile = dashou_user.dashou_profile
+
+ # 更新打手扩展表数据
+ # ✅ 确认字段:chengjiaozongliang 成交单总量
+ dashou_profile.chengjiaozongliang += 1
+
+ # ✅ 确认字段:yue 可提现余额
+ dashou_profile.yue += dashou_fencheng
+
+ # ✅ 确认字段:zonge 打单赚取总额
+ dashou_profile.zonge += dashou_fencheng
+
+ # ✅ 确认字段:jinrishouyi 今日收益金额
+ dashou_profile.jinrishouyi += dashou_fencheng
+
+ # ✅ 确认字段:jinyueshouyi 今月收益金额
+ dashou_profile.jinyueshouyi += dashou_fencheng
+
+ dashou_profile.save()
+ logger.info(f"拒绝退款成功,打手{jiedan_dashou_id}结算分成:{dashou_fencheng}")
+
+ except UserMain.DoesNotExist:
+ # 打手用户不存在,记录日志但不影响操作
+ logger.warning(f"拒绝退款:打手{jiedan_dashou_id}不存在,跳过打手结算")
+ except AttributeError:
+ # 打手扩展表不存在,记录日志但不影响操作
+ logger.warning(f"拒绝退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手结算")
+
+ # 9.3 如果是商家发单,还需要更新商家扩展表
+ if fadan_pingtai == 2:
+ try:
+ # 获取商家订单扩展表
+ shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
+ shangjia_id = shangjia_kuozhan.shangjia_id
+
+ if shangjia_id:
+ # 查询商家用户主表
+ shangjia_user = UserMain.objects.get(
+ yonghuid=shangjia_id,
+ #user_type='shop'
+ )
+
+ # 获取商家扩展表
+ shangjia_profile = shangjia_user.shop_profile
+
+ # ✅ 确认字段:chengjiao 商家成交订单总量
+ shangjia_profile.chengjiao += 1
+ shangjia_profile.save()
+
+ logger.info(f"拒绝退款成功,商家{shangjia_id}成交订单+1")
+
+ except DingdanShangjia.DoesNotExist:
+ # 商家订单扩展表不存在,跳过商家更新
+ logger.warning(f"拒绝退款:订单{dingdan_id}商家扩展表不存在,跳过商家更新")
+ except UserMain.DoesNotExist:
+ # 商家用户不存在,跳过商家更新
+ logger.warning(f"拒绝退款:商家{shangjia_id}不存在,跳过商家更新")
+ except AttributeError:
+ # 商家扩展表不存在,跳过商家更新
+ logger.warning(f"拒绝退款:商家{shangjia_id}扩展信息不存在,跳过商家更新")
+
+ # 9.4 更新退款记录表
+ try:
+ # 查询退款记录表
+ tuikuan_jilu = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
+
+ # ✅ 确认字段:sqzhuangtai 申请状态,改为2(驳回)
+ tuikuan_jilu.sqzhuangtai = 2
+
+ # ✅ 确认字段:bhliyou 驳回理由
+ tuikuan_jilu.bhliyou = jujue_liyou
+
+ # ✅ 确认字段:chuliid 处理人ID(使用管理员账号,即手机号)
+ tuikuan_jilu.chuliid = qianzhanghao
+
+ tuikuan_jilu.save()
+ logger.info(f"拒绝退款成功,更新退款记录表,订单:{dingdan_id}")
+
+ except Tuikuanjilu.DoesNotExist:
+ # 退款记录表不存在,记录日志但不影响主流程
+ logger.warning(f"拒绝退款:订单{dingdan_id}退款记录不存在,跳过更新")
+
+ # 10. 返回成功响应
+ return Response({
+ 'code': 0,
+ 'message': '拒绝退款成功',
+ 'data': None
+ })
+
+ except Exception as e:
+ logger.error(f"拒绝退款接口异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+
+class AdTongYiTuiKuanShangJia(APIView):
+ """
+ 后台同意退款接口(商家发单)
+ 权限: JWT Token认证 + 管理员身份验证 + 密码验证
+ 前端调用: POST /dingdan/adddsjtk
+ 参数: {
+ "zhanghao": "管理员账号",
+ "dingdan_id": "订单ID",
+ "tuikuan_liyou": "退款理由(可选)",
+ "pay_password": "支付密码(openid)"
+ }
+ """
+
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ """
+ 同意退款接口(商家发单)
+ 核心逻辑:
+ 1. 验证管理员身份
+ 2. 验证支付密码(openid)
+ 3. 检查订单状态为4(退款审核中)
+ 4. 更新订单状态为5(已退款)
+ 5. 更新打手退款订单数量
+ 6. 更新商家退款订单数量和余额(加上订单金额)
+ 7. 更新退款记录表,状态改为1(成功)
+ """
+ try:
+ # 1. 获取前端参数
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+ tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
+ pay_password = request.data.get('pay_password', '').strip()
+
+ if not qianzhanghao or not dingdan_id or not pay_password:
+ return Response(
+ {'code': 400, 'message': '参数不完整', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 2. JWT Token身份验证
+ dangqianyonghu = request.user
+
+ # 3. 验证手机号是否匹配
+ if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 4. 验证用户类型是否为管理员
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 5. 验证管理员扩展表是否存在
+ try:
+ guanliyuan = dangqianyonghu.admin_profile
+ except AttributeError:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 6. 验证支付密码(对比openid)
+ if pay_password != dangqianyonghu.openid:
+ return Response(
+ {'code': 401, 'message': '支付密码错误', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 7. 查询订单主表
+ try:
+ from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu
+ dingdan_obj = Dingdan.objects.get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 8. 验证订单状态为4(退款审核中)
+ if dingdan_obj.zhuangtai != 4:
+ return Response(
+ {'code': 400, 'message': '订单状态不是退款审核中', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 9. 验证订单为商家发单
+ if dingdan_obj.fadan_pingtai != 2:
+ return Response(
+ {'code': 400, 'message': '此接口只处理商家发单的退款', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 10. 获取订单的重要字段
+ jiedan_dashou_id = dingdan_obj.jiedan_dashou_id # 接单打手ID
+ jine = dingdan_obj.jine # 订单金额(不是打手分成)
+
+ # 11. 验证订单金额是否存在
+ if jine is None:
+ return Response(
+ {'code': 400, 'message': '订单缺少订单金额', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 12. 获取商家ID
+ try:
+ shangjia_kuozhan = dingdan_obj.shangjia_kuozhan
+ shangjia_id = shangjia_kuozhan.shangjia_id
+ except DingdanShangjia.DoesNotExist:
+ return Response(
+ {'code': 400, 'message': '订单商家信息不存在', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 13. 使用事务保证数据一致性
+ with transaction.atomic():
+ # 13.1 更新订单状态为5(已退款)
+ dingdan_obj.zhuangtai = 5
+ dingdan_obj.save()
+
+ # 13.2 更新打手扩展表(退款订单总量+1)
+ if jiedan_dashou_id:
+ try:
+ # 查询打手用户主表
+ dashou_user = UserMain.objects.get(
+ yonghuid=jiedan_dashou_id,
+ #user_type='dashou'
+ )
+
+ # 获取打手扩展表
+ dashou_profile = dashou_user.dashou_profile
+
+ # ✅ 确认字段:tuikuanliang 退款订单总量
+ dashou_profile.tuikuanliang += 1
+
+ dashou_profile.save()
+ logger.info(f"同意退款成功,打手{jiedan_dashou_id}退款订单+1")
+
+ except UserMain.DoesNotExist:
+ # 打手用户不存在,记录日志但不影响操作
+ logger.warning(f"同意退款:打手{jiedan_dashou_id}不存在,跳过打手更新")
+ except AttributeError:
+ # 打手扩展表不存在,记录日志但不影响操作
+ logger.warning(f"同意退款:打手{jiedan_dashou_id}扩展信息不存在,跳过打手更新")
+
+ # 13.3 更新商家扩展表
+ if shangjia_id:
+ try:
+ # 查询商家用户主表
+ shangjia_user = UserMain.objects.get(
+ yonghuid=shangjia_id,
+ #user_type='shop'
+ )
+
+ # 获取商家扩展表
+ shangjia_profile = shangjia_user.shop_profile
+
+ # ✅ 确认字段:tuikuan 退款订单总量
+ shangjia_profile.tuikuan += 1
+
+ # ✅ 确认字段:yue 商家余额(加上订单金额)
+ shangjia_profile.yue += jine
+
+ shangjia_profile.save()
+ logger.info(f"同意退款成功,商家{shangjia_id}退款订单+1,余额增加:{jine}")
+
+ except UserMain.DoesNotExist:
+ # 商家用户不存在,记录日志但不影响操作
+ logger.warning(f"同意退款:商家{shangjia_id}不存在,跳过商家更新")
+ except AttributeError:
+ # 商家扩展表不存在,记录日志但不影响操作
+ logger.warning(f"同意退款:商家{shangjia_id}扩展信息不存在,跳过商家更新")
+
+ # 13.4 更新退款记录表
+ try:
+ # 查询退款记录表
+ tuikuan_jilu = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
+
+ # ✅ 确认字段:sqzhuangtai 申请状态,改为1(成功)
+ tuikuan_jilu.sqzhuangtai = 1
+
+ # 可以添加同意理由字段,如:tongyi_liyou
+ # tuikuan_jilu.tongyi_liyou = tuikuan_liyou
+
+ # ✅ 确认字段:chuliid 处理人ID(使用管理员账号,即手机号)
+ tuikuan_jilu.chuliid = qianzhanghao
+
+ tuikuan_jilu.save()
+ logger.info(f"同意退款成功,更新退款记录表,订单:{dingdan_id}")
+
+ except Tuikuanjilu.DoesNotExist:
+ # 退款记录表不存在,记录日志但不影响主流程
+ logger.warning(f"同意退款:订单{dingdan_id}退款记录不存在,跳过更新")
+
+ # 14. 返回成功响应
+ return Response({
+ 'code': 0,
+ 'message': '同意退款成功',
+ 'data': None
+ })
+
+ except Exception as e:
+ logger.error(f"同意退款接口异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+
+# dingdan/views.py
+import logging
+import time
+import random
+import hashlib
+import requests
+import xml.etree.ElementTree as ET
+from decimal import Decimal
+from django.db import transaction
+from django.conf import settings
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from rest_framework.permissions import IsAuthenticated
+from rest_framework.parsers import JSONParser
+
+from yonghu.models import UserMain, UserDashou, UserBoss
+from dingdan.models import Dingdan, DingdanPingtai, Tuikuanjilu
+from peizhi.models import Szjilu
+
+logger = logging.getLogger(__name__)
+
+
+class AdTongYiTuiKuanPingTai(APIView):
+ """
+ 后台同意退款接口(平台发单)
+ 权限: JWT Token认证 + 管理员身份验证 + 密码验证
+ 前端调用: POST /dingdan/adddpttk
+ 参数: {
+ "zhanghao": "管理员账号",
+ "dingdan_id": "订单ID",
+ "tuikuan_liyou": "退款理由(可选)",
+ "pay_password": "支付密码(openid)"
+ }
+ """
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ try:
+ # 1. 获取前端参数
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+ tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip()
+ pay_password = request.data.get('pay_password', '').strip()
+
+ if not qianzhanghao or not dingdan_id or not pay_password:
+ return Response(
+ {'code': 400, 'message': '参数不完整', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 2. JWT Token身份验证
+ dangqianyonghu = request.user
+
+ # 3. 验证手机号是否匹配
+ if str(getattr(dangqianyonghu, 'phone', '')) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 4. 验证用户类型是否为管理员
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 5. 验证管理员扩展表是否存在
+ try:
+ guanliyuan = dangqianyonghu.admin_profile
+ except AttributeError:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 6. 验证支付密码(对比openid)
+ if pay_password != dangqianyonghu.openid:
+ return Response(
+ {'code': 401, 'message': '支付密码错误', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 7. 查询订单主表(加锁)
+ with transaction.atomic():
+ try:
+ dingdan_obj = Dingdan.objects.select_related('pingtai_kuozhan').select_for_update().get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 8. 验证订单状态为1、7、2、8、4(允许退款的状态)
+ if dingdan_obj.zhuangtai not in [1, 7, 2, 8, 4]:
+ return Response(
+ {'code': 400, 'message': '订单状态不允许退款', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 9. 验证订单为平台发单
+ if dingdan_obj.fadan_pingtai != 1:
+ return Response(
+ {'code': 400, 'message': '此接口只处理平台发单的退款', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 10. 获取订单的重要字段
+ jiedan_dashou_id = dingdan_obj.jiedan_dashou_id
+ jine = dingdan_obj.jine
+ if jine is None:
+ return Response(
+ {'code': 400, 'message': '订单缺少订单金额', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 11. 获取老板ID(从平台订单扩展表)
+ if not hasattr(dingdan_obj, 'pingtai_kuozhan') or not dingdan_obj.pingtai_kuozhan:
+ return Response(
+ {'code': 400, 'message': '订单老板信息不存在', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+ laoban_id = dingdan_obj.pingtai_kuozhan.laoban_id
+
+ # 12. 调用微信退款接口(优先使用 wechat_transaction_id)
+ transaction_id = getattr(dingdan_obj, 'wechat_transaction_id', None)
+ weixin_refund_result = self.call_weixin_refund(
+ out_trade_no=dingdan_id,
+ jine=jine,
+ transaction_id=transaction_id
+ )
+
+ if not weixin_refund_result.get('success'):
+ logger.error(f"微信退款失败,订单:{dingdan_id},错误:{weixin_refund_result.get('msg')}")
+ return Response(
+ {'code': 400, 'message': weixin_refund_result.get('msg', '退款失败')},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 13. 更新订单状态为5(已退款)
+ dingdan_obj.zhuangtai = 5
+ dingdan_obj.save()
+
+ # 14. 更新打手扩展表
+ if jiedan_dashou_id:
+ try:
+ dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
+ dashou_profile = dashou_user.dashou_profile
+ dashou_profile.zhuangtai = 1 # 打手状态设为空闲
+ dashou_profile.tuikuanliang += 1
+ dashou_profile.save()
+ except (UserMain.DoesNotExist, AttributeError):
+ logger.warning(f"平台退款:打手{jiedan_dashou_id}不存在或扩展信息缺失,跳过")
+
+ # 15. 更新老板扩展表
+ if laoban_id:
+ try:
+ laoban_user = UserMain.objects.get(yonghuid=laoban_id)
+ laoban_profile = laoban_user.boss_profile
+ laoban_profile.alltui += 1
+ laoban_profile.zonge -= jine
+ if laoban_profile.zonge < 0:
+ laoban_profile.zonge = 0
+ laoban_profile.save()
+ except (UserMain.DoesNotExist, AttributeError):
+ logger.warning(f"平台退款:老板{laoban_id}不存在或扩展信息缺失,跳过")
+
+ # 16. 更新收支记录表(id=1)
+ try:
+
+ #更新支出记录
+ from dingdan.utils import update_daily_payout
+ update_daily_payout(jine)
+
+
+ szjilu_obj = Szjilu.objects.get(id=1)
+ szjilu_obj.zongsy -= jine
+ szjilu_obj.zongzc += jine
+ szjilu_obj.jrzc += jine
+ szjilu_obj.save()
+ except Szjilu.DoesNotExist:
+ logger.error(f"平台退款:收支记录表(id=1)不存在,跳过")
+ except Exception as e:
+ logger.error(f"更新收支记录表异常: {str(e)}")
+
+ # 17. 更新退款记录表
+ try:
+ tuikuan_jilu, created = Tuikuanjilu.objects.get_or_create(
+ dingdan_id=dingdan_id,
+ defaults={
+ 'dashouid': jiedan_dashou_id if jiedan_dashou_id else '',
+ 'qingqiuid': laoban_id if laoban_id else '',
+ 'chuliid': qianzhanghao,
+ 'liyou': tuikuan_liyou if tuikuan_liyou else '给老板退款',
+ 'sqzhuangtai': 1,
+ 'jine': jine,
+ 'dashou_fencheng': dingdan_obj.dashou_fencheng or 0,
+ 'jieshao': dingdan_obj.jieshao or '',
+ 'beizhu': '给老板退款',
+ 'nicheng': dingdan_obj.nicheng or '',
+ }
+ )
+ if not created:
+ tuikuan_jilu.sqzhuangtai = 1
+ if tuikuan_liyou:
+ tuikuan_jilu.liyou = tuikuan_liyou
+ tuikuan_jilu.chuliid = qianzhanghao
+ tuikuan_jilu.save()
+ except Exception as e:
+ logger.error(f"更新退款记录表异常: {str(e)}")
+
+ logger.info(f"平台退款成功,订单:{dingdan_id},退款单号:{weixin_refund_result.get('refund_id')}")
+
+ return Response({'code': 0, 'message': '退款处理成功', 'data': None})
+
+ except Exception as e:
+ logger.error(f"平台退款接口异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+ def call_weixin_refund(self, out_trade_no, jine, transaction_id=None):
+ """
+ 调用微信退款接口
+ :param out_trade_no: 商户订单号
+ :param jine: 退款金额
+ :param transaction_id: 微信支付订单号(优先使用)
+ :return: {'success': True/False, 'msg': '消息', 'refund_id': '退款单号'}
+ """
+ try:
+ # 检查配置
+ required_settings = ['WEIXIN_APPID', 'WEIXIN_MCHID', 'WEIXIN_SHANGHUMIYAO', 'WEIXIN_CERT_PATH', 'WEIXIN_KEY_PATH']
+ for setting in required_settings:
+ if not hasattr(settings, setting) or not getattr(settings, setting):
+ return {'success': False, 'msg': f'缺少微信支付配置: {setting}'}
+
+ # 生成退款单号
+ refund_id = f"TK{int(time.time())}{random.randint(1000, 9999)}"
+ total_fee_in_fen = int(float(jine) * 100)
+
+ # 构造请求参数
+ refund_data = {
+ 'appid': settings.WEIXIN_APPID,
+ 'mch_id': settings.WEIXIN_MCHID,
+ 'nonce_str': hashlib.md5(str(time.time()).encode()).hexdigest(),
+ 'out_refund_no': refund_id,
+ 'total_fee': total_fee_in_fen,
+ 'refund_fee': total_fee_in_fen,
+ 'op_user_id': settings.WEIXIN_MCHID,
+ 'refund_account': 'REFUND_SOURCE_UNSETTLED_FUNDS'
+ }
+ # 优先使用 transaction_id,否则用 out_trade_no
+ if transaction_id:
+ refund_data['transaction_id'] = transaction_id
+ else:
+ refund_data['out_trade_no'] = out_trade_no
+
+ # 生成签名
+ sorted_params = sorted(refund_data.items())
+ sign_string = '&'.join([f"{k}={v}" for k, v in sorted_params if v])
+ sign_string += f"&key={settings.WEIXIN_SHANGHUMIYAO}"
+ sign = hashlib.md5(sign_string.encode()).hexdigest().upper()
+ refund_data['sign'] = sign
+
+ xml_data = self.dict_to_xml(refund_data)
+ url = "https://api.mch.weixin.qq.com/secapi/pay/refund"
+ response = requests.post(
+ url,
+ data=xml_data.encode('utf-8'),
+ headers={'Content-Type': 'application/xml'},
+ cert=(settings.WEIXIN_CERT_PATH, settings.WEIXIN_KEY_PATH),
+ timeout=30
+ )
+
+ if response.status_code == 200:
+ result = self.xml_to_dict(response.content)
+ if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
+ logger.info(f"微信退款成功,订单:{out_trade_no},退款单号:{refund_id}")
+ return {'success': True, 'msg': '退款申请提交成功', 'refund_id': refund_id}
+ else:
+ error_msg = result.get('err_code_des', result.get('return_msg', '退款申请失败'))
+ logger.error(f"微信退款失败,订单:{out_trade_no},错误:{error_msg}")
+ return {'success': False, 'msg': f'微信退款失败: {error_msg}'}
+ else:
+ logger.error(f"微信退款接口调用失败,状态码:{response.status_code}")
+ return {'success': False, 'msg': '微信退款接口调用失败'}
+
+ except requests.exceptions.Timeout:
+ logger.error(f"微信退款接口请求超时,订单:{out_trade_no}")
+ return {'success': False, 'msg': '微信退款接口请求超时'}
+ except Exception as e:
+ logger.error(f"调用微信退款接口异常: {str(e)}")
+ return {'success': False, 'msg': f'退款处理异常: {str(e)}'}
+
+ def dict_to_xml(self, dict_data):
+ xml = [""]
+ for k, v in dict_data.items():
+ xml.append(f"<{k}>{v}{k}>")
+ xml.append("")
+ return "".join(xml)
+
+ def xml_to_dict(self, xml_data):
+ try:
+ root = ET.fromstring(xml_data)
+ return {child.tag: child.text for child in root}
+ except ET.ParseError as e:
+ logger.error(f"XML解析失败: {str(e)}")
+ return {'return_code': 'FAIL', 'return_msg': 'XML解析失败'}
+
+
+logger = logging.getLogger(__name__)
+
+
+class DashouHuoquLeixingView(APIView):
+ """
+ 打手获取商品类型接口 - 抢单页面使用
+ 接口地址:POST /dingdan/dsqdhqddlx
+ 功能:返回所有正常的商品类型,已按排序权重排序
+
+ 返回格式(与前端原数据格式完全一致):
+ {
+ "code": 200,
+ "msg": "成功",
+ "data": {
+ "list": [
+ {
+ "id": 1,
+ "jieshao": "王者荣耀",
+ "tupian_url": "leixing/wzry.jpg",
+ "yaoqiuleixing": null,
+ "huiyuan_id": null,
+ "yongjin": null,
+ "paixu": 100,
+ "shenhezhuangtai": 1
+ },
+ // ... 更多类型
+ ]
+ }
+ }
+ """
+
+ # 🔴 使用JWT Token认证
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ """
+ 处理POST请求,返回商品类型列表
+
+ 请求参数:无特殊参数,只验证用户身份
+
+ 安全验证:
+ 1. JWT Token验证用户身份
+ 2. 只返回 shenhezhuangtai=1(正常状态)的类型
+ 3. 数据已排序:paixu降序 → id升序
+ """
+ try:
+ logger.info(f"打手获取商品类型请求 - 用户ID: {request.user.yonghuid}")
+
+ # 🔴 导入商品类型模型
+ from shangpin.models import ShangpinLeixing
+
+ # 🔴 查询所有正常状态的商品类型
+ # 条件:shenhezhuangtai=1(正常状态)
+ # 排序:先按 paixu 降序(数值大的在前),再按 id 升序
+ leixing_queryset = ShangpinLeixing.objects.filter(
+ shenhezhuangtai=1 # 🔴 只取正常状态,排除审核专用
+ ).order_by('-paixu', 'id')
+
+ # 🔴 构建返回数据列表
+ leixing_list = []
+ for leixing in leixing_queryset:
+ leixing_list.append({
+ "id": leixing.id,
+ "jieshao": leixing.jieshao or "",
+ "tupian_url": leixing.tupian_url or "",
+ "yaoqiuleixing": leixing.yaoqiuleixing,
+ "huiyuan_id": leixing.huiyuan_id,
+ "yongjin": float(leixing.yongjin) if leixing.yongjin else None,
+ "paixu": leixing.paixu or 0,
+ "shenhezhuangtai": leixing.shenhezhuangtai or 1
+ })
+
+ # 🔴 记录日志
+ logger.info(f"返回商品类型数据:{len(leixing_list)}条,用户: {request.user.yonghuid}")
+
+ # 🔴 返回成功响应
+ # 注意:保持与前端原数据格式完全一致
+ return Response({
+ "code": 200,
+ "msg": "成功",
+ "data": {
+ "list": leixing_list # 🔴 保持原字段名 "list"
+ }
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ # 🔴 异常处理
+ logger.error(f"打手获取商品类型失败 - 用户ID: {request.user.yonghuid}, 错误: {str(e)}", exc_info=True)
+
+ return Response({
+ "code": 500,
+ "msg": f"服务器内部错误: {str(e)}",
+ "data": None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+logger = logging.getLogger(__name__)
+
+from peizhi.models import Club ,OrderTypeMapping
+
+logger = logging.getLogger(__name__)
+
+
+class PartnerSyncOrderView(APIView):
+ """
+ 接收对方平台派单通知接口
+ 对方平台商家派单成功后,异步调用此接口,将订单信息同步过来。
+ """
+ throttle_classes = [AnonRateThrottle]
+ permission_classes = [AllowAny]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ # 1. 验证 token
+ token = request.data.get('token')
+ if not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ logger.warning(f"跨平台派单 token 无效: {token}")
+ return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
+
+ # 2. 获取必要参数
+ our_club_id = request.data.get('our_club_id') # 对方平台发送的我方俱乐部ID(即我方在对方表中的 partner_club_id)
+ partner_club_id = request.data.get('partner_club_id') # 对方平台的俱乐部ID(对方自己的ID)
+ partner_order_id = request.data.get('partner_order_id')
+ jine = request.data.get('jine')
+ dashou_fencheng = request.data.get('dashou_fencheng')
+ jieshao = request.data.get('jieshao')
+ beizhu = request.data.get('beizhu', '')
+ create_time_str = request.data.get('create_time')
+ nicheng = request.data.get('nicheng')
+ leixing_id = request.data.get('leixing_id') # 对方平台的商品类型ID
+ partner_yonghu_id = request.data.get('user_id') # 对方下单用户ID
+
+ # 必需字段校验
+ required_fields = [our_club_id, partner_club_id, partner_order_id, jine, dashou_fencheng,
+ jieshao, create_time_str, nicheng, leixing_id]
+ if not all(required_fields):
+ logger.error(f"跨平台派单参数缺失: {request.data}")
+ return Response({'code': 400, 'message': 'Missing required parameters'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # 3. 验证我方俱乐部是否存在且同意接收订单
+ try:
+ my_club = Club.objects.get(partner_club_id=our_club_id)
+ except Club.DoesNotExist:
+ logger.warning(f"我方俱乐部不存在,partner_club_id={our_club_id}")
+ return Response({'code': 404, 'message': 'Club not found'}, status=status.HTTP_404_NOT_FOUND)
+
+ if not my_club.is_receive_enabled:
+ logger.warning(f"我方俱乐部 {my_club.club_id} 未同意接收订单")
+ return Response({'code': 403, 'message': 'Receiving not allowed'}, status=status.HTTP_403_FORBIDDEN)
+
+ # 4. 查找订单类型映射
+ try:
+ mapping = OrderTypeMapping.objects.get(
+ partner_club_id=our_club_id, # 对方在我方的标识
+ partner_type_id=leixing_id
+ )
+ our_type_id = mapping.our_type_id
+ except OrderTypeMapping.DoesNotExist:
+ logger.error(f"未找到订单类型映射: 对方俱乐部={our_club_id}, 对方类型ID={leixing_id}")
+ return Response({'code': 400, 'message': 'Order type mapping not found'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # 5. 获取我方商品类型信息(用于获取会员ID、佣金等)
+ try:
+ our_leixing = ShangpinLeixing.objects.get(id=our_type_id)
+ except ShangpinLeixing.DoesNotExist:
+ logger.error(f"我方商品类型不存在: {our_type_id}")
+ return Response({'code': 400, 'message': 'Local product type not found'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # 6. 幂等检查:如果已存在相同 partner_order_id 的订单,直接返回(附带 status='existed')
+ existing = Dingdan.objects.filter(partner_order_id=partner_order_id).first()
+ if existing:
+ logger.info(f"跨平台派单幂等返回: partner_order_id={partner_order_id} 已存在")
+ return Response({
+ 'code': 0,
+ 'data': {
+ 'local_order_id': existing.dingdan_id,
+ 'status': 'existed' # 明确告知已存在
+ }
+ })
+
+ # 7. 生成我方订单ID(确保唯一性)
+ def generate_dingdan_id():
+ timestamp = int(time.time() * 1000)
+ process_id = os.getpid() % 1000
+ random_num = random.randint(1000, 9999)
+ return f"SJ{timestamp}{process_id:03d}{random_num}"
+
+ # 8. 获取代理商家头像(用于订单图片)
+ agent_user = UserMain.objects.filter(yonghuid=settings.AGENT_SHANGJIA_ID).first()
+ agent_avatar = agent_user.avatar if agent_user else ''
+
+ # 9. 计算我方实际打手分成金额:对方传来的打手分成 × 我方接收费率
+ try:
+ actual_dashou_fencheng = Decimal(dashou_fencheng) * my_club.receive_rate
+ except Exception:
+ actual_dashou_fencheng = Decimal(dashou_fencheng)
+
+ # 10. 事务创建订单
+ try:
+ with transaction.atomic():
+ new_id = generate_dingdan_id()
+ # 创建主订单
+ dingdan = Dingdan.objects.create(
+ dingdan_id=new_id,
+ zhuangtai=1, # 普通订单(待接单)
+ fadan_pingtai=2, # 商家发单
+ jine=jine, # 原始订单金额
+ dashou_fencheng=actual_dashou_fencheng, # 调整后的打手分成
+ jieshao=jieshao,
+ beizhu=beizhu,
+ create_time=create_time_str,
+ yaoqiuleixing=our_leixing.yaoqiuleixing,
+ nicheng=nicheng,
+ huiyuan_id=our_leixing.huiyuan_id,
+ yongjin=our_leixing.yongjin,
+ leixing_id=our_type_id,
+ partner_order_id=partner_order_id, # 存储对方订单ID
+ is_cross=1,
+ dispatch_type=2, # 对方派单
+ partner_club_id=our_club_id, # 对方俱乐部ID
+ tupian=agent_avatar, # 使用代理商家头像作为订单图片
+ )
+ # 创建商家扩展表(使用代理商家)
+ DingdanShangjia.objects.create(
+ dingdan=dingdan,
+ shangjia_id=settings.AGENT_SHANGJIA_ID,
+ sjnicheng='跨平台代理',
+ partner_yonghu_id=partner_yonghu_id or '',
+ )
+
+ # 成功创建后更新每日派单统计(方向=对方派单)
+ try:
+ update_daily_dispatch_stat(
+ direction=2, # 对方派单
+ partner_club_id=our_club_id,
+ dispatch_amount=Decimal(jine), # 统计使用原始订单金额
+
+ )
+ except Exception as e:
+ logger.error(f"更新派单统计失败: {e}", exc_info=True)
+
+ logger.info(f"跨平台订单同步成功: partner_order_id={partner_order_id}, local_id={new_id}")
+
+
+ #🟢【7. 新增】支付成功后的广播通知(异步,不阻塞主流程)
+ from dingdan.tongzhi_tasks import dingdan_guangbo
+
+
+ # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用)
+ order_info = {
+ 'dingdan_id': dingdan.dingdan_id,
+ 'game_type': self._get_game_type_name(dingdan.leixing_id),
+ 'amount': str(dingdan.jine),
+ 'order_desc': (dingdan.jieshao or '')[:50],
+ }
+ dingdan_guangbo.delay(order_info) # 瞬间返回,不阻塞请求
+
+
+ return Response({
+ 'code': 0,
+ 'data': {
+ 'local_order_id': new_id,
+ 'status': 'created'
+ }
+ })
+
+ except Exception as e:
+ logger.error(f"跨平台订单同步失败: {str(e)}", exc_info=True)
+ return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ def _get_game_type_name(self, leixing_id):
+ try:
+ from shangpin.models import ShangpinLeixing
+ gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
+ return gt.jieshao if gt else '游戏订单'
+ except Exception as e:
+ logger.error(f"获取游戏类型名称异常: {e}")
+ return '游戏订单'
+
+
+from dingdan.utils import update_daily_dispatch_stat
+
+logger = logging.getLogger(__name__)
+
+
+class PartnerCheckClaimView(APIView):
+ """
+ 接收对方平台抢单询问(对方打手抢我方派出的订单)
+ 如果允许抢单:
+ 1. 订单状态改为2(已接单)
+ 2. 记录对方打手ID、对方俱乐部ID、对方订单ID
+ 3. 更新派单成交统计(success_count/success_amount)
+ """
+ throttle_classes = [AnonRateThrottle]
+ permission_classes = [AllowAny]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ token = request.data.get('token')
+ if not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ logger.warning(f"token无效: {token}")
+ return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
+
+ wofangdingdan_id = request.data.get('wofangdingdan_id')
+ partner_order_id = request.data.get('partner_order_id')
+ our_club_id = request.data.get('our_club_id')
+ dashou_id = request.data.get('dashou_id')
+ sj_id = request.data.get('sj_id')
+
+ if not all([wofangdingdan_id, partner_order_id, our_club_id, dashou_id, sj_id]):
+ logger.error(f"参数缺失: {request.data}")
+ return Response({'code': 400, 'message': 'Missing parameters'}, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ with transaction.atomic():
+ # 查询我方俱乐部配置
+ try:
+ club = Club.objects.select_for_update().get(partner_club_id=our_club_id)
+ except Club.DoesNotExist:
+ logger.warning(f"俱乐部配置不存在 partner_club_id={our_club_id}")
+ return Response({'code': 0, 'data': {'can_claim': False}})
+
+ if not club.is_interop_enabled:
+ logger.warning(f"俱乐部 {club.club_id} 未开启互通")
+ return Response({'code': 0, 'data': {'can_claim': False}})
+
+ # 锁定订单
+ try:
+ order = Dingdan.objects.select_for_update().get(dingdan_id=wofangdingdan_id)
+ except Dingdan.DoesNotExist:
+ logger.warning(f"订单不存在: {wofangdingdan_id}")
+ return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
+
+ # 只有状态1可抢
+ if order.zhuangtai != 1:
+ logger.info(f"订单 {order.dingdan_id} 状态 {order.zhuangtai} 不可抢")
+ return Response({'code': 0, 'data': {'can_claim': False}})
+
+ # 商品类型检查
+ try:
+ leixing = ShangpinLeixing.objects.get(id=order.leixing_id)
+ except ShangpinLeixing.DoesNotExist:
+ logger.warning(f"商品类型不存在: {order.leixing_id}")
+ return Response({'code': 0, 'data': {'can_claim': False}})
+
+ if not leixing.is_cross_enabled:
+ logger.warning(f"商品类型 {leixing.jieshao} 未开启跨平台")
+ return Response({'code': 0, 'data': {'can_claim': False}})
+
+ # 执行抢单:状态改为2(已接单)
+ order.zhuangtai = 2
+ order.jiedan_dashou_id = dashou_id
+ order.partner_club_id = our_club_id
+ order.partner_order_id = partner_order_id
+ if order.is_cross != 1:
+ order.is_cross = 1
+ # dispatch_type 保持原值(1-我方派单),不变
+ order.save()
+
+ # 更新派单成交统计(我方派单被成功接单)
+ update_daily_dispatch_stat(
+ direction=1,
+ partner_club_id=our_club_id,
+ claimed_amount=order.jine,
+
+ )
+
+ logger.info(f"对方抢单成功: 订单 {order.dingdan_id}, 对方打手 {dashou_id}")
+ return Response({'code': 0, 'data': {'can_claim': True}})
+
+ except Exception as e:
+ logger.error(f"异常: {e}", exc_info=True)
+ return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+class PartnerNotifyClaimView(APIView):
+ """
+ 接收对方平台抢单成功通知(对方派单被他们自己的打手抢了)
+ 我方根据 partner_order_id 找到订单,如果状态为1则改为20,不更新任何统计。
+ """
+ throttle_classes = [AnonRateThrottle]
+ permission_classes = [AllowAny]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ token = request.data.get('token')
+ if not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ logger.warning(f"token无效: {token}")
+ return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
+
+ partner_order_id = request.data.get('partner_order_id')
+ if not partner_order_id:
+ logger.error("缺少 partner_order_id")
+ return Response({'code': 400, 'message': 'Missing partner_order_id'}, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ with transaction.atomic():
+ order = Dingdan.objects.select_for_update().get(partner_order_id=partner_order_id)
+ # 只有状态为1(未被抢)时才改为20
+ if order.zhuangtai == 1:
+ order.zhuangtai = 20
+ order.save()
+ logger.info(f"订单 {order.dingdan_id} 状态改为20(被其他俱乐部抢走)")
+ else:
+ logger.info(f"订单 {order.dingdan_id} 当前状态 {order.zhuangtai},无需修改")
+ return Response({'code': 0, 'message': 'OK'})
+ except Dingdan.DoesNotExist:
+ logger.warning(f"订单不存在 partner_order_id={partner_order_id}")
+ return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ logger.error(f"异常: {e}", exc_info=True)
+ return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+class PartnerRefundNotifyView(APIView):
+ """
+ 接收对方平台退款通知接口
+ 对方平台商家申请退款时,调用此接口通知我方,我方将本地对应订单改为退款状态。
+ 请求方式: POST
+ 接口地址: /dingdan/partner_refund_notify
+ 请求参数:
+ token: 认证令牌,需与settings.CROSS_PLATFORM_TOKEN一致
+ order_id: 对方平台的订单ID(即我方记录的 partner_order_id)
+ 返回:
+ code: 0成功,其他失败
+ message: 描述信息
+ """
+ throttle_classes = [AnonRateThrottle]
+ permission_classes = [AllowAny]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ # 1. 验证token
+ token = request.data.get('token')
+ if not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ logger.warning(f"跨平台退款通知 token 无效: {token}")
+ return Response({'code': 403, 'message': 'Invalid token'}, status=status.HTTP_403_FORBIDDEN)
+
+ order_id = request.data.get('order_id') # 对方平台的订单ID(即我方的 partner_order_id)
+ if not order_id:
+ logger.error("跨平台退款通知参数缺失: order_id")
+ return Response({'code': 400, 'message': 'Missing order_id'}, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ with transaction.atomic():
+ # 2. 根据 partner_order_id 查找订单(必须存在且为跨平台订单)
+ order = Dingdan.objects.select_for_update().get(partner_order_id=order_id, is_cross=1)
+ # 如果订单已完成,不允许退款
+ if order.zhuangtai == 3:
+ logger.warning(f"跨平台退款通知:订单 {order_id} 已完成,不可退款")
+ return Response({'code': 400, 'message': 'Order already completed, cannot refund'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # 3. 如果订单状态不是5,则改为5(已退款),并更新打手信息
+ if order.zhuangtai != 5:
+ # 记录原打手ID
+ old_dashou_id = order.jiedan_dashou_id
+ if old_dashou_id:
+ try:
+ # 查询打手用户主表
+ dashou_user = UserMain.objects.get(yonghuid=old_dashou_id)
+ # 通过反向关系获取打手扩展表
+ if hasattr(dashou_user, 'dashou_profile'):
+ dashou_profile = dashou_user.dashou_profile
+ dashou_profile.zhuangtai = 1 # 恢复打手状态
+ dashou_profile.tuikuanliang += 1 # 退款订单量+1
+ dashou_profile.save()
+ logger.info(f"退款通知:打手 {old_dashou_id} 状态已恢复,退款订单+1")
+ except UserMain.DoesNotExist:
+ logger.warning(f"退款通知:打手用户不存在 {old_dashou_id}")
+ except Exception as e:
+ logger.error(f"退款通知:更新打手信息失败 {str(e)}")
+
+ # 更新订单状态为5
+ order.zhuangtai = 5
+ # 可选:清空接单打手ID
+ #order.jiedan_dashou_id = None
+ order.save()
+ logger.info(f"跨平台退款通知成功:订单 {order_id} 已改为退款状态")
+ else:
+ logger.info(f"跨平台退款通知:订单 {order_id} 已是退款状态,无需重复处理")
+
+ return Response({'code': 0, 'message': 'Refund processed successfully'})
+
+ except Dingdan.DoesNotExist:
+ logger.warning(f"跨平台退款通知:订单不存在或不是跨平台订单,partner_order_id={order_id}")
+ return Response({'code': 404, 'message': 'Order not found'}, status=status.HTTP_404_NOT_FOUND)
+ except Exception as e:
+ logger.error(f"跨平台退款通知异常: {str(e)}", exc_info=True)
+ return Response({'code': 500, 'message': 'Internal error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+logger = logging.getLogger(__name__)
+
+
+class AdJuJueJieSuan(APIView):
+ """
+ 后台拒绝结算接口(平台发单)
+ 权限: JWT Token认证 + 管理员身份验证
+ 前端调用: POST /dingdan/adddjjjs
+ 参数: {
+ "zhanghao": "管理员账号(手机号)",
+ "dingdan_id": "订单ID",
+ "tkly": "拒绝结算理由(必填)"
+ }
+ """
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ try:
+ # 1. 获取参数
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+ tkly = request.data.get('tkly', '').strip()
+
+ if not all([qianzhanghao, dingdan_id, tkly]):
+ return Response(
+ {'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id, tkly)', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ # 2. JWT用户验证
+ dangqianyonghu = request.user
+
+ # 3. 验证手机号匹配(管理员账号通常为手机号)
+ if str(dangqianyonghu.phone) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 4. 验证用户类型为管理员
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ # 5. 查询订单
+ try:
+ dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 6. 校验订单条件:平台发单(1)且状态为2或8
+ if dingdan.fadan_pingtai != 1 or dingdan.zhuangtai not in [2, 8]:
+ return Response(
+ {'code': 400, 'message': '仅平台发单且状态为进行中(2)或结算中(8)的订单可拒绝结算', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ jiedan_dashou_id = dingdan.jiedan_dashou_id
+ if not jiedan_dashou_id:
+ return Response(
+ {'code': 400, 'message': '订单暂无接单打手', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ with transaction.atomic():
+ # 7. 更新订单状态为5(已退款),并保存拒绝理由
+ dingdan.zhuangtai = 5
+ dingdan.tkly = tkly
+ dingdan.save()
+
+ # 8. 更新打手扩展表
+ try:
+ dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
+ dashou_profile = dashou_user.dashou_profile
+ dashou_profile.zhuangtai = 1 # 打手状态设为正常
+ dashou_profile.tuikuanliang += 1 # 退款订单总量+1
+ dashou_profile.save()
+ except (UserMain.DoesNotExist, AttributeError) as e:
+ logger.warning(f"拒绝结算:打手{jiedan_dashou_id}信息更新失败 - {str(e)}")
+
+ return Response({'code': 0, 'message': '拒绝结算成功', 'data': None})
+
+ except Exception as e:
+ logger.error(f"拒绝结算接口异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+
+class AdZhuanYiDaTing(APIView):
+ """
+ 后台转移大厅接口
+ 权限: JWT Token认证 + 管理员身份验证
+ 前端调用: POST /dingdan/adddzydt
+ 参数: {
+ "zhanghao": "管理员账号(手机号)",
+ "dingdan_id": "订单ID"
+ }
+ """
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ try:
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+
+ if not all([qianzhanghao, dingdan_id]):
+ return Response(
+ {'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id)', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ dangqianyonghu = request.user
+
+ if str(dangqianyonghu.phone) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ try:
+ dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 校验订单状态:允许2、8、4
+ if dingdan.zhuangtai not in [2, 8, 4]:
+ return Response(
+ {'code': 400, 'message': '仅状态为进行中(2)、结算中(8)或退款审核(4)的订单可转移大厅', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ jiedan_dashou_id = dingdan.jiedan_dashou_id
+
+ with transaction.atomic():
+ # 更新订单:状态改为1(下单中),清空接单打手ID
+ dingdan.zhuangtai = 1
+ dingdan.jiedan_dashou_id = None
+ dingdan.save()
+
+ # 如果有接单打手,更新其扩展表
+ if jiedan_dashou_id:
+ try:
+ dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id)
+ dashou_profile = dashou_user.dashou_profile
+ dashou_profile.zhuangtai = 1
+ dashou_profile.tuikuanliang += 1
+ dashou_profile.save()
+ except (UserMain.DoesNotExist, AttributeError) as e:
+ logger.warning(f"转移大厅:打手{jiedan_dashou_id}信息更新失败 - {str(e)}")
+
+ return Response({'code': 0, 'message': '转移大厅成功', 'data': None})
+
+ except Exception as e:
+ logger.error(f"转移大厅接口异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+
+class AdQuXiaoZhiDing(APIView):
+ """
+ 后台取消指定接口
+ 权限: JWT Token认证 + 管理员身份验证
+ 前端调用: POST /dingdan/adddqxzd
+ 参数: {
+ "zhanghao": "管理员账号(手机号)",
+ "dingdan_id": "订单ID"
+ }
+ """
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ try:
+ qianzhanghao = request.data.get('zhanghao', '').strip()
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+
+ if not all([qianzhanghao, dingdan_id]):
+ return Response(
+ {'code': 400, 'message': '参数不完整(需要zhanghao, dingdan_id)', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ dangqianyonghu = request.user
+
+ if str(dangqianyonghu.phone) != qianzhanghao:
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ if dangqianyonghu.user_type != 'admin':
+ return Response(
+ {'code': 401, 'message': '身份验证失败', 'data': None},
+ status=status.HTTP_401_UNAUTHORIZED
+ )
+
+ try:
+ dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response(
+ {'code': 404, 'message': '订单不存在', 'data': None},
+ status=status.HTTP_404_NOT_FOUND
+ )
+
+ # 校验订单状态:必须为7(指定中)
+ if dingdan.zhuangtai != 7:
+ return Response(
+ {'code': 400, 'message': '仅状态为指定中(7)的订单可取消指定', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST
+ )
+
+ with transaction.atomic():
+ # 更新订单:状态改为1(下单中),清空指定ID
+ dingdan.zhuangtai = 1
+ dingdan.zhiding_id = None
+ dingdan.save()
+
+ return Response({'code': 0, 'message': '取消指定成功', 'data': None})
+
+ except Exception as e:
+ logger.error(f"取消指定接口异常: {str(e)}", exc_info=True)
+ return Response(
+ {'code': 500, 'message': '服务器内部错误', 'data': None},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
+
+class DashouXiugaiView(APIView):
+ """
+ 打手修改订单交付内容接口
+ 请求方式:POST
+ 请求路径:/dingdan/dsxiugaidd
+ 权限:需携带有效JWT Token,且用户为打手身份
+ 请求参数:
+ dingdan_id : 订单ID(必填)
+ liuyan : 新的留言内容(可选,最长50字)
+ deleted_images : 要删除的图片URL列表(数组,每个元素为完整COS URL)
+ new_images : 新增的图片URL列表(数组,每个元素为完整COS URL)
+ 返回:
+ 成功:{"code":0,"msg":"修改成功","data":null}
+ 失败:返回相应错误码和提示
+ """
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ # 获取请求数据
+ data = request.data
+ dingdan_id = data.get('dingdan_id', '')
+ liuyan = data.get('liuyan', '')
+ deleted_images = data.get('deleted_images', [])
+ new_images = data.get('new_images', [])
+
+ # ---------- 参数校验 ----------
+ if not dingdan_id:
+ return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ if not isinstance(deleted_images, list):
+ return Response({'code': 2, 'msg': 'deleted_images 必须为数组', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ if not isinstance(new_images, list):
+ return Response({'code': 3, 'msg': 'new_images 必须为数组', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ if liuyan and len(liuyan) > 50:
+ return Response({'code': 4, 'msg': '留言最多50个字', 'data': None},
+ status=status.HTTP_400_BAD_REQUEST)
+
+ # ---------- 用户身份验证 ----------
+ current_user = request.user
+ yonghuid = current_user.yonghuid
+
+ # 检查是否为打手
+ try:
+ dashou_profile = current_user.dashou_profile # 假设User模型有related_name='dashou_profile'
+ except UserDashou.DoesNotExist:
+ return Response({'code': 5, 'msg': '用户不是打手身份', 'data': None},
+ status=status.HTTP_403_FORBIDDEN)
+
+ # ---------- 订单查询及权限验证 ----------
+ try:
+ # 订单必须属于该打手,且状态为4(退款中)或8(结算中)
+ dingdan = Dingdan.objects.get(
+ dingdan_id=dingdan_id,
+ jiedan_dashou_id=yonghuid,
+ zhuangtai__in=[4, 8] # 只允许修改退款中或结算中的订单
+ )
+ except Dingdan.DoesNotExist:
+ return Response({'code': 6, 'msg': '订单不存在或没有修改权限', 'data': None},
+ status=status.HTTP_403_FORBIDDEN)
+
+ # ---------- 执行修改(使用事务保证原子性) ----------
+ try:
+ with transaction.atomic():
+ # 1. 处理删除的图片:从 Dashoutupian 表中删除记录
+ if deleted_images:
+ # 根据订单ID、打手ID和图片URL删除记录
+ delete_count, _ = Dashoutupian.objects.filter(
+ dingdan_id=dingdan_id,
+ dashou=yonghuid,
+ tupian__in=deleted_images
+ ).delete()
+ # 可选:记录删除数量
+ logger.info(f"订单 {dingdan_id} 删除图片 {delete_count} 张")
+
+ # 2. 处理新增的图片:插入新记录
+ if new_images:
+ # 批量创建
+ new_records = []
+ for url in new_images:
+ new_records.append(Dashoutupian(
+ dingdan_id=dingdan_id,
+ dashou=yonghuid,
+ tupian=url,
+ create_time=timezone.now()
+ ))
+ Dashoutupian.objects.bulk_create(new_records)
+ logger.info(f"订单 {dingdan_id} 新增图片 {len(new_records)} 张")
+
+ # 3. 更新订单留言(如果发生变化)
+ # 注意:即使留言为空字符串,也允许更新,表示清空留言
+ if 'liuyan' in data: # 判断是否传了liuyan字段(可能为空字符串)
+ dingdan.dashou_liuyan = liuyan
+ dingdan.update_time = timezone.now()
+ dingdan.save(update_fields=['dashou_liuyan', 'update_time'])
+
+ # 全部成功,返回成功响应
+ return Response({
+ 'code': 0,
+ 'msg': '修改成功',
+ 'data': None
+ }, status=status.HTTP_200_OK)
+
+ except Exception as e:
+ logger.error(f"修改订单失败: {str(e)}", exc_info=True)
+ return Response({
+ 'code': 7,
+ 'msg': '服务器内部错误',
+ 'data': None
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+
+from django.core.exceptions import ObjectDoesNotExist
+from dingdan.tongzhi_tasks import dingdan_guangbo
+
+class ZxsjghdsView(APIView):
+ """
+ 商家更换打手接口
+ 路径:POST /dingdan/zxsjghds
+ 权限:仅限已认证商家(通过JWT)
+ 功能:
+ 1. 将原订单状态改为已退款(5),释放接单打手(若有)
+ 2. 更新打手统计(退款量、状态、每日统计)
+ 3. 写入退款记录
+ 4. 若为跨平台订单,通知对方平台退款(必须成功)
+ 5. 基于原订单生成新订单(状态直接为1),复制核心数据
+ 6. 不操作商家余额、退款次数等(替换订单)
+ """
+
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ # ========== 1. 获取当前商家用户 ==========
+ current_user = request.user
+ try:
+ yonghuid = current_user.yonghuid
+ except AttributeError:
+ return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
+
+ # ========== 2. 提取订单ID ==========
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+ if not dingdan_id:
+ return Response({'code': 2, 'msg': '订单ID不能为空'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # ========== 3. 查询订单并校验权限 ==========
+ try:
+ # 关联查询商家扩展表,确保当前商家是该订单的发布者
+ shangjia_kuozhan = DingdanShangjia.objects.select_related('dingdan').get(
+ dingdan__dingdan_id=dingdan_id,
+ shangjia_id=yonghuid
+ )
+ except DingdanShangjia.DoesNotExist:
+ return Response({'code': 3, 'msg': '订单不存在或您无权操作'}, status=status.HTTP_403_FORBIDDEN)
+
+ old_order = shangjia_kuozhan.dingdan
+
+ # ========== 4. 状态校验(仅允许进行中或结算中) ==========
+ if old_order.zhuangtai not in [2, 8]:
+ return Response({'code': 4, 'msg': '当前订单状态不允许更换打手'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # ========== 5. 收集订单关键数据(用于新订单复制) ==========
+ # 这些字段将在退款处理后用于创建新订单
+ jine = old_order.jine
+ dashou_fencheng = old_order.dashou_fencheng
+ leixing_id = old_order.leixing_id
+ yaoqiuleixing = old_order.yaoqiuleixing
+ huiyuan_id = old_order.huiyuan_id
+ yongjin = old_order.yongjin
+ jieshao = old_order.jieshao
+ nicheng = old_order.nicheng
+ beizhu = old_order.beizhu
+ user1_id = old_order.user1_id
+ tupian = old_order.tupian
+ fadan_pingtai = old_order.fadan_pingtai
+
+ # 跨平台相关
+ is_cross = old_order.is_cross
+ partner_club_id = old_order.partner_club_id
+ partner_order_id = old_order.partner_order_id
+ need_notify_partner = is_cross and partner_club_id and partner_order_id
+
+ dashou_id = old_order.jiedan_dashou_id # 可能为空
+
+ # ========== 6. 退款处理(事务块1:退款原订单 + 打手统计 + 跨平台通知) ==========
+ try:
+ with transaction.atomic():
+ # 6.1 订单状态更新为已退款
+ old_order.zhuangtai = 5
+ old_order.save(update_fields=['zhuangtai'])
+
+ # 6.2 释放打手
+ if dashou_id:
+ try:
+ dashou_user = UserMain.objects.get(yonghuid=dashou_id)
+ dashou_profile = dashou_user.dashou_profile
+ dashou_profile.tuikuanliang = (dashou_profile.tuikuanliang or 0) + 1
+ dashou_profile.zhuangtai = 1 # 空闲
+ dashou_profile.save(update_fields=['tuikuanliang', 'zhuangtai'])
+ except (UserMain.DoesNotExist, ObjectDoesNotExist):
+ logger.warning(f"打手 {dashou_id} 不存在或扩展表缺失,跳过更新")
+
+ # 6.3 更新退款记录
+ Tuikuanjilu.objects.update_or_create(
+ dingdan_id=dingdan_id,
+ defaults={
+ 'dashouid': dashou_id or '',
+ 'qingqiuid': yonghuid, # 申请人为当前商家
+ 'chuliid': yonghuid, # 处理人也是商家自己
+ 'liyou': '商家更换打手',
+ 'sqzhuangtai': 1, # 状态 1=成功
+ 'jine': jine or 0,
+ 'dashou_fencheng': dashou_fencheng or 0,
+ 'jieshao': jieshao or '',
+ 'beizhu': '更换打手系统自动退款',
+ 'nicheng': nicheng or '',
+ }
+ )
+
+ # 6.4 打手每日统计(退款行为)
+ if dashou_id:
+ try:
+ from houtai.utils import update_dashou_daily_by_action
+ update_dashou_daily_by_action(
+ yonghuid=dashou_id,
+ amount=dashou_fencheng,
+ action=3 # 3=退款
+ )
+ except Exception as e:
+ logger.error(f"更新打手每日统计失败: {e}")
+ # 统计失败不应阻断主流程,但可记录
+
+ # 6.5 跨平台退款通知
+ if need_notify_partner:
+ try:
+ from peizhi.models import ClubConfig, Club
+ my_club_config = ClubConfig.objects.filter(is_self=1).first()
+ if not my_club_config:
+ raise Exception("我方俱乐部配置不存在")
+ self_club_id = my_club_config.club_id
+ club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
+ partner_domain = club_rel.partner_domain.rstrip('/')
+
+ notify_url = f"{partner_domain}/houtai/dfddtk"
+ payload = {
+ 'order_id': partner_order_id,
+ 'club_id': self_club_id,
+ 'status': 5,
+ 'reason': '商家更换打手退款'
+ }
+ resp = requests.post(notify_url, json=payload, timeout=5)
+ if resp.status_code != 200:
+ transaction.set_rollback(True)
+ logger.error(f"对方平台接口HTTP错误: {resp.status_code}")
+ return Response({'code': 500, 'msg': '通知对方平台失败'})
+ resp_json = resp.json()
+ if resp_json.get('code') != 0:
+ transaction.set_rollback(True)
+ logger.error(f"对方平台退款通知失败: {resp_json.get('msg')}")
+ return Response({'code': 500, 'msg': f"对方平台通知失败: {resp_json.get('msg')}"})
+
+ # 更新跨平台扩展数据
+ from dingdan.models import CrossPlatformOrderData
+ CrossPlatformOrderData.objects.update_or_create(
+ dingdan_id=dingdan_id,
+ defaults={
+ 'partner_order_id': partner_order_id,
+ 'partner_club_id': partner_club_id,
+ 'partner_order_status': 5,
+ }
+ )
+ except Exception as e:
+ transaction.set_rollback(True)
+ logger.error(f"通知对方平台异常: {e}")
+ return Response({'code': 500, 'msg': f'通知对方平台失败: {str(e)}'})
+
+ except Exception as e:
+ logger.error(f"退款处理事务失败: {e}", exc_info=True)
+ return Response({'code': 500, 'msg': '退款处理失败'})
+
+ # ========== 7. 生成新订单(事务块2) ==========
+ try:
+ with transaction.atomic():
+ # 7.1 生成新的订单ID
+ def generate_dingdan_id():
+ timestamp = int(time.time() * 1000)
+ process_id = os.getpid() % 1000
+ random_num = random.randint(1000, 9999)
+ return f"SJ{timestamp}{process_id:03d}{random_num}"
+
+ new_dingdan_id = generate_dingdan_id()
+ # 确保唯一
+ while Dingdan.objects.filter(dingdan_id=new_dingdan_id).exists():
+ new_dingdan_id = generate_dingdan_id()
+
+ # 7.2 创建新订单(复制数据,状态为 1)
+ new_order = Dingdan.objects.create(
+ dingdan_id=new_dingdan_id,
+ zhuangtai=1, # 已下单
+ fadan_pingtai=fadan_pingtai,
+ jine=jine,
+ dashou_fencheng=dashou_fencheng,
+ leixing_id=leixing_id,
+ yaoqiuleixing=yaoqiuleixing,
+ huiyuan_id=huiyuan_id,
+ yongjin=yongjin,
+ jieshao=jieshao,
+ nicheng=nicheng,
+ beizhu=beizhu,
+ user1_id=user1_id, # 商家标识
+ tupian=tupian,
+ # 不继承:zhiding_id, jiedan_dashou_id, dashou_liuyan 等
+ )
+
+ # 7.3 创建商家扩展表(关联新订单)
+ DingdanShangjia.objects.create(
+ dingdan=new_order,
+ shangjia_id=yonghuid,
+ sjnicheng=shangjia_kuozhan.sjnicheng or '',
+ )
+
+ # 7.4 异步通知打手(广播新订单)
+ try:
+ order_info = {
+ 'dingdan_id': new_dingdan_id,
+ 'game_type': self._get_game_type_name(leixing_id),
+ 'amount': str(jine),
+ 'order_desc': (jieshao or '')[:50],
+ }
+ dingdan_guangbo.delay(order_info)
+ except Exception as e:
+ logger.error(f"广播通知失败: {e}")
+
+ # 7.5 跨平台同步(如果适用)
+ try:
+ sync_order_to_partners(new_dingdan_id)
+ except Exception as e:
+ logger.error(f"跨平台同步失败: {e}")
+
+ # 注意:此处不更新商家余额、发布量等统计,因为是替换订单
+
+ logger.info(f"商家{yonghuid}更换打手成功,旧订单{dingdan_id}已退款,新订单{new_dingdan_id}")
+
+ return Response({
+ 'code': 0,
+ 'msg': '更换成功',
+ 'data': {
+ 'xin_dingdan_id': new_dingdan_id,
+ }
+ })
+
+ except Exception as e:
+ logger.error(f"生成新订单事务失败: {e}", exc_info=True)
+ return Response({'code': 500, 'msg': '生成新订单失败'})
+
+ def _get_game_type_name(self, leixing_id):
+ """根据类型ID获取游戏类型名称"""
+ try:
+ gt = ShangpinLeixing.objects.filter(id=leixing_id).first()
+ return gt.jieshao if gt else '游戏订单'
+ except Exception:
+ return '游戏订单'
+
+
+# ===================== 视图:商家罚款申请 =====================
+# 文件路径:dingdan/views.py 或单独文件,根据项目结构放置
+
+
+class ShangjiaFakuanApplyView(APIView):
+ """
+ 商家罚款申请接口
+ 路径:POST /dingdan/sjfksq
+ 权限:JWT认证的商家用户
+ 参数:
+ dingdan_id - 订单ID (必填)
+ chufa_liyou - 罚款原因 (必填)
+ fakuanjine - 罚款金额,元 (必填)
+ yingxiang_qiangdan - 是否影响抢单 1是 0否 (必填)
+ 说明:
+ 1. 自动从订单中获取被打手ID,防止商家篡改目标。
+ 2. 同一订单同一打手不允许重复罚款。
+ 3. 跨平台订单若为我方派单,则通知对方平台处罚;否则本地生成罚单。
+ """
+
+ permission_classes = [IsAuthenticated]
+ parser_classes = [JSONParser]
+
+ def post(self, request):
+ # ========== 1. 身份验证 ==========
+ current_user = request.user
+ try:
+ shangjia_id = current_user.yonghuid # 商家的用户ID
+ except AttributeError:
+ return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
+
+ # ========== 2. 参数提取与校验 ==========
+ dingdan_id = request.data.get('dingdan_id', '').strip()
+ chufa_liyou = request.data.get('chufa_liyou', '').strip()
+ fakuanjine = request.data.get('fakuanjine', 0)
+ yingxiang_qiangdan = int(request.data.get('yingxiang_qiangdan', 1))
+
+ if not dingdan_id or not chufa_liyou or not fakuanjine:
+ return Response({'code': 2, 'msg': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST)
+
+ try:
+ fakuanjine = float(fakuanjine)
+ if fakuanjine <= 0:
+ raise ValueError
+ except (ValueError, TypeError):
+ return Response({'code': 2, 'msg': '罚款金额格式错误'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # ========== 3. 获取订单,校验归属商家 ==========
+ try:
+ # 关联查询商家扩展表,确保订单属于当前商家
+ shangjia_kuozhan = DingdanShangjia.objects.select_related('dingdan').get(
+ dingdan__dingdan_id=dingdan_id,
+ shangjia_id=shangjia_id
+ )
+ order = shangjia_kuozhan.dingdan
+ except DingdanShangjia.DoesNotExist:
+ return Response({'code': 3, 'msg': '订单不存在或您无权操作'}, status=status.HTTP_403_FORBIDDEN)
+
+ # ========== 4. 获取被打手ID ==========
+ dashou_id = order.jiedan_dashou_id
+ if not dashou_id:
+ return Response({'code': 4, 'msg': '该订单暂无接单打手,无法罚款'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # ========== 5. 重复罚款检查 ==========
+ if Fadan.objects.filter(beichufa_id=dashou_id, guanliandingdan_id=dingdan_id).exists():
+ return Response({'code': 5, 'msg': '该打手在此订单已被罚款,不能重复申请'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # ========== 6. 跨平台信息 ==========
+ is_cross = order.is_cross
+ partner_order_id = order.partner_order_id or ''
+ partner_club_id = order.partner_club_id or ''
+ dispatch_type = order.dispatch_type or 0 # 1=我方派单,2=对方派单
+
+ # 是否需要通知对方平台:我方派单且存在对方订单ID
+ need_notify_partner = is_cross and partner_order_id and dispatch_type == 1
+
+ # ========== 7. 本地创建罚单(可重用) ==========
+ def create_local_fadan():
+ Fadan.objects.create(
+ beichufa_id=dashou_id,
+ shenqing_chufa=shangjia_id, # 申请人为当前商家
+ shenfen=1, # 被打者身份:1=打手
+ chufaliyou=chufa_liyou,
+ fakuanjine=fakuanjine,
+ guanliandingdan_id=dingdan_id,
+ zhuangtai=1, # 待缴纳
+ yingxiang_qiangdan=yingxiang_qiangdan,
+ shenqingren_shenfen=5, # 申请人是商家
+ partner_club_id='',
+ )
+
+ # ========== 8. 本地处罚分支 ==========
+ if not need_notify_partner:
+ with transaction.atomic():
+ create_local_fadan()
+ logger.info(f"商家{shangjia_id}对订单{dingdan_id}打手{dashou_id}罚款成功,金额{fakuanjine}")
+ return Response({'code': 0, 'msg': '罚款申请已提交'})
+
+ # ========== 9. 跨平台通知分支 ==========
+ try:
+ # 9.1 获取我方俱乐部ID
+ my_club = ClubConfig.objects.filter(is_self=1).first()
+ if not my_club:
+ return Response({'code': 500, 'msg': '系统配置错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+ self_club_id = my_club.club_id
+
+ # 9.2 获取对方平台域名
+ club_rel = Club.objects.get(club_id=self_club_id, partner_club_id=partner_club_id)
+ partner_domain = club_rel.partner_domain.rstrip('/')
+ except ObjectDoesNotExist:
+ return Response({'code': 500, 'msg': '对方平台信息未配置'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ # 9.3 构造通知参数
+ notify_url = f"{partner_domain}/houtai/kffkdssq_notify" # 与后台接口保持一致
+ payload = {
+ 'order_id': partner_order_id,
+ 'club_id': self_club_id,
+ 'chufaliyou': chufa_liyou,
+ 'fakuanjine': fakuanjine,
+ 'yingxiang_qiangdan': yingxiang_qiangdan,
+ 'shenqingren_shenfen': 5, # 商家身份
+ 'operator': shangjia_id, # 操作人ID
+ }
+
+ # 9.4 带重试的通知
+ timeout = 3
+ retries = 2
+ success = False
+ for attempt in range(retries):
+ try:
+ resp = requests.post(notify_url, json=payload, timeout=timeout)
+ if resp.status_code == 200:
+ resp_data = resp.json()
+ if resp_data.get('code') == 0:
+ success = True
+ break
+ else:
+ logger.warning(f"对方平台罚款通知失败,响应: {resp_data}")
+ else:
+ logger.warning(f"对方平台HTTP错误: {resp.status_code}")
+ except Exception as e:
+ logger.warning(f"通知对方平台异常,第{attempt+1}次尝试: {e}")
+ continue
+
+ if not success:
+ return Response({'code': 500, 'msg': '通知对方平台失败,请稍后重试'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
+
+ # 9.5 通知成功后,本地也创建罚单(标记跨平台)
+ with transaction.atomic():
+ Fadan.objects.create(
+ beichufa_id=dashou_id,
+ shenqing_chufa=shangjia_id,
+ shenfen=1,
+ chufaliyou=chufa_liyou,
+ fakuanjine=fakuanjine,
+ guanliandingdan_id=dingdan_id,
+ zhuangtai=1,
+ yingxiang_qiangdan=yingxiang_qiangdan,
+ shenqingren_shenfen=5,
+ partner_club_id=partner_club_id, # 记录对方俱乐部ID
+ )
+
+ logger.info(f"商家{shangjia_id}跨平台罚款成功: {dingdan_id}")
+ return Response({'code': 0, 'msg': '罚款已提交至对方平台'})
+
+
+class ShangjiaFakuaiXiugaiView(APIView):
+ """
+ 商家端统一修改接口:罚款修改/撤销/再次申请,订单退款撤销/修改理由
+ URL: /dingdan/sjfkxgycx
+ """
+ permission_classes = [IsAuthenticated]
+
+ def post(self, request):
+ user = request.user
+ # 验证商家身份
+ try:
+ shangjia = user.shop_profile
+ except UserShangjia.DoesNotExist:
+ return Response({'code': 403, 'msg': '您不是商家'}, status=status.HTTP_403_FORBIDDEN)
+ if shangjia.zhuangtai != 1:
+ return Response({'code': 403, 'msg': '商家状态异常'}, status=status.HTTP_403_FORBIDDEN)
+
+ operation = request.data.get('operation')
+ dingdan_id = request.data.get('dingdan_id')
+ if not dingdan_id:
+ return Response({'code': 400, 'msg': '缺少订单ID'}, status=status.HTTP_400_BAD_REQUEST)
+
+ # 验证订单属于该商家
+ try:
+ dingdan = Dingdan.objects.select_related('shangjia_kuozhan').get(dingdan_id=dingdan_id)
+ except Dingdan.DoesNotExist:
+ return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
+ if not hasattr(dingdan, 'shangjia_kuozhan') or dingdan.shangjia_kuozhan.shangjia_id != user.yonghuid:
+ return Response({'code': 403, 'msg': '无权操作此订单'}, status=status.HTTP_403_FORBIDDEN)
+
+ # 处理订单相关操作
+ if operation == 'cancel_refund':
+ return self._cancel_refund(dingdan, request.data.get('liyou', ''))
+ elif operation == 'modify_refund_reason':
+ return self._modify_refund_reason(dingdan, request.data.get('liyou', ''))
+
+ # 处理罚款相关操作
+ fadan_id = request.data.get('fadan_id')
+ if not fadan_id:
+ return Response({'code': 400, 'msg': '缺少罚单ID'}, status=status.HTTP_400_BAD_REQUEST)
+ try:
+ fadan = Fadan.objects.get(id=fadan_id)
+ except Fadan.DoesNotExist:
+ return Response({'code': 404, 'msg': '罚单不存在'}, status=status.HTTP_404_NOT_FOUND)
+ if fadan.guanliandingdan_id != dingdan_id:
+ return Response({'code': 403, 'msg': '罚单与订单不匹配'}, status=status.HTTP_403_FORBIDDEN)
+
+ if operation == 'modify_penalty':
+ return self._modify_penalty(fadan, request.data.get('liyou', ''), request.data.get('jine'))
+ elif operation == 'cancel_penalty':
+ return self._cancel_penalty(fadan, request.data.get('liyou', ''))
+ elif operation == 'resubmit_penalty':
+ return self._resubmit_penalty(fadan, request.data.get('liyou', ''), request.data.get('jine'))
+ else:
+ return Response({'code': 400, 'msg': '无效的操作类型'}, status=status.HTTP_400_BAD_REQUEST)
+
+ def _cancel_refund(self, dingdan, liyou):
+ """撤销退款申请(状态4 → 状态8,清空退款理由)"""
+ if dingdan.zhuangtai != 4:
+ return Response({'code': 400, 'msg': '只有退款中的订单可以撤销退款申请'})
+ with transaction.atomic():
+ dingdan.zhuangtai = 8
+ dingdan.tkly = '' # 清空退款理由
+ dingdan.save()
+ # 记录撤销理由可存入日志(如需要可另建日志表,此处简单处理)
+ logger.info(f"商家撤销退款订单 {dingdan.dingdan_id},理由: {liyou}")
+ return Response({'code': 0, 'msg': '已撤销退款申请'})
+
+ def _modify_refund_reason(self, dingdan, liyou):
+ """修改退款理由(仅状态4时)"""
+ if dingdan.zhuangtai != 4:
+ return Response({'code': 400, 'msg': '只有退款中的订单可以修改退款理由'})
+ if not liyou.strip():
+ return Response({'code': 400, 'msg': '退款理由不能为空'})
+ dingdan.tkly = liyou[:500]
+ dingdan.save()
+ return Response({'code': 0, 'msg': '退款理由修改成功'})
+
+ def _modify_penalty(self, fadan, liyou, jine_str):
+ """修改罚款(仅状态1待缴纳 或 3申诉中)"""
+ if fadan.zhuangtai not in (1, 3):
+ return Response({'code': 400, 'msg': '当前罚单状态不可修改'})
+ if not liyou.strip():
+ return Response({'code': 400, 'msg': '罚款理由不能为空'})
+ try:
+ jine = Decimal(jine_str)
+ if jine <= 0:
+ raise ValueError
+ except:
+ return Response({'code': 400, 'msg': '罚款金额必须为正数'})
+ with transaction.atomic():
+ fadan.chufaliyou = liyou[:500]
+ fadan.fakuanjine = jine
+ # 如果之前是驳回状态修改,需要重置状态为待缴纳?不,修改不应改变状态
+ fadan.save()
+ return Response({'code': 0, 'msg': '罚款修改成功'})
+
+ def _cancel_penalty(self, fadan, liyou):
+ """撤销罚款(状态1或3 → 状态4 已驳回)"""
+ if fadan.zhuangtai not in (1, 3):
+ return Response({'code': 400, 'msg': '当前罚单状态不可撤销'})
+ if not liyou.strip():
+ return Response({'code': 400, 'msg': '驳回理由不能为空'})
+ with transaction.atomic():
+ fadan.zhuangtai = 4 # 已驳回
+ fadan.bohuiliyou = liyou[:500]
+ fadan.save()
+ return Response({'code': 0, 'msg': '罚款已撤销(驳回)'})
+
+ def _resubmit_penalty(self, fadan, liyou, jine_str):
+ """再次申请罚款(状态4 → 状态1,修改理由和金额)"""
+ if fadan.zhuangtai != 4:
+ return Response({'code': 400, 'msg': '只有已驳回的罚单可再次申请'})
+ if not liyou.strip():
+ return Response({'code': 400, 'msg': '罚款理由不能为空'})
+ try:
+ jine = Decimal(jine_str)
+ if jine <= 0:
+ raise ValueError
+ except:
+ return Response({'code': 400, 'msg': '罚款金额必须为正数'})
+ with transaction.atomic():
+ fadan.zhuangtai = 1 # 待缴纳
+ fadan.chufaliyou = liyou[:500]
+ fadan.fakuanjine = jine
+ fadan.bohuiliyou = '' # 清空驳回理由
+ fadan.save()
return Response({'code': 0, 'msg': '已重新申请罚款'})
\ No newline at end of file
diff --git a/houtai/view.py b/houtai/view.py
index e4bd717..f902651 100644
--- a/houtai/view.py
+++ b/houtai/view.py
@@ -1,3 +1,4 @@
+import hmac
import threading
import traceback
from calendar import monthrange
@@ -1234,6 +1235,9 @@ class PartnerGetOrderDataView(APIView):
parser_classes = [JSONParser]
def post(self, request):
+ # token = request.data.get('token')
+ # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
try:
# 1. 获取参数
partner_order_id = request.data.get('order_id', '').strip()
@@ -1339,6 +1343,9 @@ class PartnerRefundNotifyView(APIView):
parser_classes = [JSONParser]
def post(self, request):
+ # token = request.data.get('token')
+ # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
# 1. 参数校验(事务外执行)
partner_order_id = request.data.get('order_id', '').strip()
partner_club_id = request.data.get('club_id', '').strip()
@@ -2093,6 +2100,9 @@ class PartnerTransferHallNotifyView(APIView):
permission_classes = [AllowAny]
def post(self, request):
+ # token = request.data.get('token')
+ # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
try:
# 1. 获取参数
partner_order_id = request.data.get('order_id', '').strip()
@@ -2618,6 +2628,9 @@ class PartnerUpdateSelfConfigView(APIView):
parser_classes = [JSONParser]
def post(self, request):
+ # token = request.data.get('token')
+ # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
# 1. 获取参数
club_id = request.data.get('club_id', '').strip()
updates = request.data.get('updates', {})
@@ -3174,6 +3187,9 @@ class PartnerPunishNotifyView(APIView):
parser_classes = [JSONParser]
def post(self, request):
+ # token = request.data.get('token')
+ # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
try:
order_id = request.data.get('order_id', '').strip()
club_id = request.data.get('club_id', '').strip()
@@ -9722,6 +9738,9 @@ class PartnerFineNotifyView(APIView):
parser_classes = [JSONParser]
def post(self, request):
+ # token = request.data.get('token')
+ # if not token or not hmac.compare_digest(str(token), settings.CROSS_PLATFORM_TOKEN):
+ # return Response({'code': 403, 'msg': '无效的跨平台令牌'}, status=200)
try:
order_id = request.data.get('order_id', '').strip() # 对方平台订单ID
club_id = request.data.get('club_id', '').strip()
@@ -12453,8 +12472,8 @@ class KptxwztjbView(APIView):
return Response({'code': 400, 'msg': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST)
# 2. 验证同步令牌
- SYNC_TOKEN = getattr(settings, 'CROSS_PLATFORM_SYNC_TOKEN', 'xK9mP2vL8qW5rT7y')
- if token != SYNC_TOKEN:
+ SYNC_TOKEN = getattr(settings, 'CROSS_PLATFORM_SYNC_TOKEN', None)
+ if not SYNC_TOKEN or token != SYNC_TOKEN:
logger.warning(f"无效的同步令牌: {token[:8]}...")
return Response({'code': 403, 'msg': '无效令牌'}, status=status.HTTP_403_FORBIDDEN)
diff --git a/requirements.txt b/requirements.txt
index 0563dae..f1573cf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -11,6 +11,7 @@ click-repl==0.3.0
cos_python_sdk_v5==1.9.40
crcmod==1.7
cron_descriptor==2.0.6
+defusedxml
Django==4.2.27
django-celery-beat==2.5.0
django-celery-results==2.5.0
diff --git a/shangpin/views.py b/shangpin/views.py
index fa8f3f9..60d3512 100644
--- a/shangpin/views.py
+++ b/shangpin/views.py
@@ -465,7 +465,7 @@ import random
import string
import time
import hashlib
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
import requests
from django.conf import settings
from rest_framework.views import APIView
@@ -731,7 +731,7 @@ from django.views import View
from django.http import HttpResponse
from yonghu.models import UserDashou, UserMain
from shangpin.models import Czjilu
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
import hashlib
from django.conf import settings
import logging
@@ -1686,7 +1686,7 @@ import random
import string
import time
import hashlib
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
import requests
from django.conf import settings
import logging
@@ -2387,7 +2387,7 @@ class HuiyuanHuitiao(View):
from django.http import HttpResponse
from yonghu.models import UserDashou, UserMain, UserGuanshi
from shangpin.models import Czjilu, Huiyuan, Huiyuangoumai, Gsfenhong
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
import hashlib
from django.conf import settings
from django.utils import timezone
@@ -3016,7 +3016,7 @@ import time
import random
import string
import hashlib
-import xml.etree.ElementTree as ET
+import defusedxml.ElementTree as ET
import requests
from decimal import Decimal
from django.conf import settings
diff --git a/utils/exception_handler.py b/utils/exception_handler.py
index 55f4576..81cdfb2 100644
--- a/utils/exception_handler.py
+++ b/utils/exception_handler.py
@@ -54,5 +54,26 @@ def custom_exception_handler(exc, context):
return Response(standard_response, status=response.status_code)
- # 对于非DRF异常,这里可以根据需要处理
+ # 对于非DRF异常(response为None),生产环境返回通用错误信息
+ if response is None:
+ from django.conf import settings
+ request = context.get('request')
+ user_info = '未知'
+ if request:
+ user_info = f"用户: {request.user.id if request.user.is_authenticated else '未登录'}"
+ logger.error(
+ f"未处理异常 - 路径: {request.path if request else '未知'}, "
+ f"方法: {request.method if request else '未知'}, {user_info}, "
+ f"错误类型: {type(exc).__name__}, 错误: {str(exc)}"
+ )
+ if not settings.DEBUG:
+ return Response(
+ {'code': 500, 'message': '服务器内部错误,请稍后重试'},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+ return Response(
+ {'code': 500, 'message': str(exc)},
+ status=status.HTTP_500_INTERNAL_SERVER_ERROR
+ )
+
return response
\ No newline at end of file
diff --git a/utils/oss_utils.py b/utils/oss_utils.py
index 662dc5d..95cbfb7 100644
--- a/utils/oss_utils.py
+++ b/utils/oss_utils.py
@@ -5,255 +5,6 @@ from django.conf import settings
from PIL import Image, UnidentifiedImageError
import io
-'''try:
- from qcloud_cos import CosConfig, CosS3Client
-
- TENCENT_COS_AVAILABLE = True
-except ImportError:
- TENCENT_COS_AVAILABLE = False
-
-try:
- import oss2
-
- ALIYUN_OSS_AVAILABLE = True
-except ImportError:
- ALIYUN_OSS_AVAILABLE = False
-
-
-def validate_image(file):
- """
- 验证图片文件
- 使用Pillow检查图片是否有效
- """
- try:
- # 打开图片但不保存到磁盘
- img = Image.open(file)
- img.verify() # 验证文件完整性
-
- # 重置文件指针(因为PIL读取后会移动指针)
- file.seek(0)
-
- # 检查图片格式
- allowed_formats = ['JPEG', 'PNG', 'GIF', 'WEBP', 'BMP']
- if img.format not in allowed_formats:
- return False, f"不支持{img.format}格式,请使用JPEG、PNG、GIF、WEBP或BMP格式"
-
- # 检查图片大小(最大5MB)
- file_size = len(file.read())
- file.seek(0)
- if file_size > 5 * 1024 * 1024:
- return False, "图片大小不能超过5MB"
-
- # 检查图片尺寸(最大2000x2000)
- img = Image.open(file)
- file.seek(0)
- if img.width > 3000 or img.height > 3000:
- return False, "图片尺寸不能超过2000x2000像素"
-
- return True, "图片验证通过"
-
- except UnidentifiedImageError:
- return False, "文件不是有效的图片"
- except Exception as e:
- return False, f"图片验证失败: {str(e)}"
-
-
-def get_oss_client():
- """
- 获取OSS客户端
- 根据配置自动选择云厂商
- """
- oss_type = getattr(settings, 'OSS_TYPE', 'tencent') # 默认为腾讯云
-
- if oss_type == 'tencent' and TENCENT_COS_AVAILABLE:
- # 腾讯云COS
- config = CosConfig(
- Region=getattr(settings, 'COS_REGION', 'ap-shanghai'),
- SecretId=getattr(settings, 'COS_SECRET_ID', ''),
- SecretKey=getattr(settings, 'COS_SECRET_KEY', ''),
- Scheme=getattr(settings, 'COS_SCHEME', 'https')
- )
- return CosS3Client(config)
-
- elif oss_type == 'aliyun' and ALIYUN_OSS_AVAILABLE:
- # 阿里云OSS
- auth = oss2.Auth(
- getattr(settings, 'ALIYUN_ACCESS_KEY_ID', ''),
- getattr(settings, 'ALIYUN_ACCESS_KEY_SECRET', '')
- )
- endpoint = getattr(settings, 'ALIYUN_OSS_ENDPOINT', '')
- bucket_name = getattr(settings, 'ALIYUN_OSS_BUCKET', '')
- return oss2.Bucket(auth, endpoint, bucket_name)
-
- else:
- raise ImportError(f"不支持{oss_type}云存储或相关SDK未安装")
-
-
-def upload_to_oss(file_obj, file_path):
- """
- 通用上传文件到OSS
- file_obj: Django的UploadedFile对象或类文件对象
- file_path: 在OSS中的相对路径,如 'avatar/123/abc.jpg'
- 返回: 文件访问URL
- """
- try:
- oss_type = getattr(settings, 'OSS_TYPE', 'tencent')
-
- if oss_type == 'tencent':
- return _upload_to_tencent_cos(file_obj, file_path)
- elif oss_type == 'aliyun':
- return _upload_to_aliyun_oss(file_obj, file_path)
- else:
- raise ValueError(f"不支持的OSS类型: {oss_type}")
-
- except Exception as e:
- print(f"OSS上传失败: {e}")
- return None
-
-
-def _upload_to_tencent_cos(file_obj, file_path):
- """
- 上传到腾讯云COS
- """
- client = get_oss_client()
-
- # 获取文件扩展名
- ext = file_path.split('.')[-1].lower() if '.' in file_path else ''
- content_type_map = {
- 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg',
- 'png': 'image/png', 'gif': 'image/gif',
- 'webp': 'image/webp', 'bmp': 'image/bmp'
- }
- content_type = content_type_map.get(ext, 'image/jpeg')
-
- # 如果是Django的UploadedFile,需要读取内容
- if hasattr(file_obj, 'read'):
- file_content = file_obj.read()
- else:
- file_content = file_obj
-
- # 上传文件
- response = client.put_object(
- Bucket=getattr(settings, 'COS_BUCKET', ''),
- Body=file_content,
- Key=file_path,
- ContentType=content_type,
- ContentDisposition='inline'
- )
-
- # 返回完整URL
- domain = getattr(settings, 'COS_DOMAIN', '')
- return f"{domain}/{file_path}"
-
-
-def _upload_to_aliyun_oss(file_obj, file_path):
- """
- 上传到阿里云OSS
- """
- bucket = get_oss_client()
-
- # 如果是Django的UploadedFile,需要读取内容
- if hasattr(file_obj, 'read'):
- file_content = file_obj.read()
- else:
- file_content = file_obj
-
- # 上传文件
- result = bucket.put_object(file_path, file_content)
-
- if result.status == 200:
- # 阿里云OSS返回的URL
- domain = getattr(settings, 'ALIYUN_OSS_DOMAIN', '')
- return f"{domain}/{file_path}"
- else:
- return None
-
-
-def delete_from_oss(file_url):
- """
- 从OSS删除文件
- file_url: 可以是完整URL或相对路径
- """
- try:
- if not file_url:
- return False
-
- oss_type = getattr(settings, 'OSS_TYPE', 'tencent')
-
- # 从URL中提取文件路径
- if oss_type == 'tencent':
- domain = getattr(settings, 'COS_DOMAIN', '')
-
- # 判断是完整URL还是相对路径
- if file_url.startswith('http'):
- # 完整URL
- if domain not in file_url:
- return False # URL不属于我们的域名
- file_key = file_url.replace(f"{domain}/", "")
- else:
- # 相对路径,直接使用
- file_key = file_url
-
- return _delete_from_tencent_cos(file_key)
-
- elif oss_type == 'aliyun':
- domain = getattr(settings, 'ALIYUN_OSS_DOMAIN', '')
-
- if file_url.startswith('http'):
- if domain not in file_url:
- return False
- file_key = file_url.replace(f"{domain}/", "")
- else:
- file_key = file_url
-
- return _delete_from_aliyun_oss(file_key)
-
- else:
- return False
-
- except Exception as e:
- print(f"OSS删除失败: {e}")
- return False
-
-
-def _delete_from_tencent_cos(file_key):
- """
- 从腾讯云COS删除文件
- """
- client = get_oss_client()
-
- try:
- client.delete_object(
- Bucket=getattr(settings, 'COS_BUCKET', ''),
- Key=file_key
- )
- return True
- except Exception as e:
- print(f"腾讯云COS删除失败: {e}")
- return False
-
-
-def _delete_from_aliyun_oss(file_key):
- """
- 从阿里云OSS删除文件
- """
- bucket = get_oss_client()
-
- try:
- bucket.delete_object(file_key)
- return True
- except Exception as e:
- print(f"阿里云OSS删除失败: {e}")
- return False'''
-
-
-
-# utils/oss_utils.py
-import os
-import uuid
-from django.conf import settings
-from PIL import Image, UnidentifiedImageError
-import io
try:
from qcloud_cos import CosConfig, CosS3Client
@@ -508,4 +259,4 @@ def _delete_from_aliyun_oss(file_key):
return True
except Exception as e:
print(f"阿里云OSS删除失败: {e}")
- return False
\ No newline at end of file
+ return False
diff --git a/utils/redis_lock.py b/utils/redis_lock.py
index c46d078..44b168c 100644
--- a/utils/redis_lock.py
+++ b/utils/redis_lock.py
@@ -1,18 +1,57 @@
-import redis
+import uuid
+import logging
from django.conf import settings
+from django.core.cache import cache
+
+logger = logging.getLogger(__name__)
+
+# Redis客户端(延迟初始化)
+_redis_client = None
+
+def _get_redis_client():
+ global _redis_client
+ if _redis_client is None:
+ import redis
+ _redis_client = redis.Redis(
+ host=settings.REDIS_HOST,
+ port=settings.REDIS_PORT,
+ password=settings.REDIS_PASSWORD,
+ db=settings.REDIS_DB_CELERY,
+ decode_responses=True,
+ )
+ return _redis_client
-redis_client = redis.Redis(
- host=settings.REDIS_HOST,
- port=settings.REDIS_PORT,
- password=settings.REDIS_PASSWORD,
- db=2,
- decode_responses=True
-)
def acquire_lock(lock_key, timeout=10):
- """获取锁,返回是否成功"""
- return redis_client.set(lock_key, '1', nx=True, ex=timeout)
+ """获取分布式锁,返回锁标识符,失败返回None"""
+ client = _get_redis_client()
+ identifier = str(uuid.uuid4())
+ result = client.set(lock_key, identifier, nx=True, ex=timeout)
+ if result:
+ logger.debug(f"获取锁成功: {lock_key}, 标识: {identifier}")
+ return identifier
+ return None
-def release_lock(lock_key):
- """释放锁"""
- redis_client.delete(lock_key)
\ No newline at end of file
+
+def release_lock(lock_key, identifier):
+ """释放分布式锁(仅当标识符匹配时才释放)"""
+ if identifier is None:
+ return False
+ client = _get_redis_client()
+ lua_script = """
+ if redis.call('get', KEYS[1]) == ARGV[1] then
+ return redis.call('del', KEYS[1])
+ else
+ return 0
+ end
+ """
+ try:
+ result = client.eval(lua_script, 1, lock_key, identifier)
+ if result:
+ logger.debug(f"释放锁成功: {lock_key}")
+ else:
+ logger.warning(f"释放锁失败(标识不匹配或锁已过期): {lock_key}")
+ return bool(result)
+ except Exception as e:
+ logger.error(f"释放锁异常: {lock_key}, 错误: {e}")
+ return False
diff --git a/utils/wechat_v3.py b/utils/wechat_v3.py
index 4fa097c..0920856 100644
--- a/utils/wechat_v3.py
+++ b/utils/wechat_v3.py
@@ -1,112 +1,4 @@
-# utils/wechat_v3.py
-'''import time
-import hashlib
-import base64
-import json
-from urllib.parse import urlparse
-
-import rsa
-from Crypto.Cipher import AES
-
-from django.conf import settings
-
-
-def load_private_key():
- """加载商户私钥(PEM格式)"""
- with open(settings.WECHAT_PAY_V3_CONFIG['PRIVATE_KEY_PATH'], 'rb') as f:
- key_data = f.read()
- return rsa.PrivateKey.load_pkcs1(key_data)
-
-
-def build_authorization(method, url, body):
- """
- 生成V3接口的Authorization头
- :param method: GET/POST
- :param url: 完整URL(含域名和路径)
- :param body: 请求体JSON字符串
- :return: Authorization字符串
- """
- private_key = load_private_key()
- mchid = settings.WECHAT_PAY_V3_CONFIG['MCHID']
- serial_no = settings.WECHAT_PAY_V3_CONFIG['CERT_SERIAL_NO']
-
- # 提取URL路径(不含query)
- parsed = urlparse(url)
- path = parsed.path
- if parsed.query:
- path += '?' + parsed.query
-
- timestamp = str(int(time.time()))
- nonce = hashlib.md5((timestamp + 'wechat').encode()).hexdigest()[:32]
-
- # 构造签名串
- message = '\n'.join([method, path, timestamp, nonce, body]) + '\n'
-
- # 使用私钥签名(SHA256)
- signature = rsa.sign(message.encode('utf-8'), private_key, 'SHA-256')
- signature_b64 = base64.b64encode(signature).decode('utf-8')
-
- # 组装Authorization
- auth = f'WECHATPAY2-SHA256-RSA2048 mchid="{mchid}",nonce_str="{nonce}",timestamp="{timestamp}",serial_no="{serial_no}",signature="{signature_b64}"'
- return auth
-
-
-def verify_wechat_sign(headers, body):
- """
- 验证微信回调的签名
- 需要提前从微信平台下载证书公钥,存放在 PLATFORM_CERT_DIR 下,文件名 = 序列号.pem
- :param headers: 请求头
- :param body: 原始请求体字符串
- :return: bool
- """
- serial = headers.get('Wechatpay-Serial')
- signature = headers.get('Wechatpay-Signature')
- timestamp = headers.get('Wechatpay-Timestamp')
- nonce = headers.get('Wechatpay-Nonce')
-
- if not all([serial, signature, timestamp, nonce]):
- return False
-
- # 构造验签名串
- message = '\n'.join([timestamp, nonce, body]) + '\n'
-
- # 读取对应序列号的平台证书公钥
- cert_path = f"{settings.WECHAT_PAY_V3_CONFIG['PLATFORM_CERT_DIR']}/{serial}.pem"
- try:
- with open(cert_path, 'rb') as f:
- pub_key = rsa.PublicKey.load_pkcs1_openssl_pem(f.read())
- except FileNotFoundError:
- # 如果证书不存在,可尝试下载(生产环境建议定时下载)
- return False
-
- signature_bytes = base64.b64decode(signature)
- try:
- rsa.verify(message.encode('utf-8'), signature_bytes, pub_key)
- return True
- except rsa.VerificationError:
- return False
-
-
-def decrypt_callback_ciphertext(associated_data, nonce, ciphertext):
- """
- 使用APIv3密钥解密回调中的加密数据
- :param associated_data: 附加数据
- :param nonce: 随机串
- :param ciphertext: 密文(Base64)
- :return: 解密后的JSON字符串
- """
- api_v3_key = settings.WECHAT_PAY_V3_CONFIG['API_V3_KEY'].encode('utf-8')
- nonce_bytes = nonce.encode('utf-8')
- ciphertext_bytes = base64.b64decode(ciphertext)
-
- # AES-GCM解密
- cipher = AES.new(api_v3_key, AES.MODE_GCM, nonce=nonce_bytes)
- if associated_data:
- cipher.update(associated_data.encode('utf-8'))
- plaintext = cipher.decrypt_and_verify(ciphertext_bytes[:-16], ciphertext_bytes[-16:])
- return plaintext.decode('utf-8')'''
-
-# utils/wechat_v3.py
+import re
import time
import hashlib
import base64
@@ -178,6 +70,16 @@ def verify_wechat_sign(headers, body):
if not all([serial, signature, timestamp, nonce]):
return False
+ if not re.match(r'^[A-Za-z0-9-]+$', serial):
+ return False
+
+ try:
+ ts = int(headers.get('Wechatpay-Timestamp', '0'))
+ if abs(time.time() - ts) > 300: # 5分钟有效期
+ return False
+ except (ValueError, TypeError):
+ return False
+
message = '\n'.join([timestamp, nonce, body]) + '\n'
cert_path = f"{settings.WECHAT_PAY_V3_CONFIG['PLATFORM_CERT_DIR']}/{serial}.pem"
if not os.path.exists(cert_path):
@@ -204,4 +106,4 @@ def decrypt_callback_ciphertext(associated_data, nonce, ciphertext):
if associated_data:
cipher.update(associated_data.encode('utf-8'))
plaintext = cipher.decrypt_and_verify(ciphertext_bytes[:-16], ciphertext_bytes[-16:])
- return plaintext.decode('utf-8')
\ No newline at end of file
+ return plaintext.decode('utf-8')
diff --git a/yonghu/tixian_shenhe_views.py b/yonghu/tixian_shenhe_views.py
index 0886021..6e1b8a1 100644
--- a/yonghu/tixian_shenhe_views.py
+++ b/yonghu/tixian_shenhe_views.py
@@ -141,7 +141,8 @@ class TixianZddkshApplyView(APIView):
user_main = request.user
lock_key = f'tixian_audit_apply:{user_main.yonghuid}'
- if not acquire_lock(lock_key, timeout=10):
+ identifier = acquire_lock(lock_key, timeout=10)
+ if not identifier:
return Response({'code': 429, 'msg': '操作频繁,请稍后重试'})
try:
@@ -172,7 +173,7 @@ class TixianZddkshApplyView(APIView):
return Response({'code': 0, 'msg': '提现申请已提交,请等待审核', 'data': data})
finally:
- release_lock(lock_key)
+ release_lock(lock_key, identifier)
def process_audit_collect(request):
@@ -233,7 +234,8 @@ def process_audit_collect(request):
return quota_resp
lock_key = f'tixian_collect:{shenhe_danhao}'
- if not acquire_lock(lock_key, timeout=15):
+ identifier = acquire_lock(lock_key, timeout=15)
+ if not identifier:
return Response({'code': 429, 'msg': '操作频繁,请稍后重试'})
tixian_id = None
@@ -372,7 +374,7 @@ def process_audit_collect(request):
})
finally:
- release_lock(lock_key)
+ release_lock(lock_key, identifier)
except Exception as e:
logger.error(
diff --git a/yonghu/views.py b/yonghu/views.py
index d7e09b3..b626da0 100644
--- a/yonghu/views.py
+++ b/yonghu/views.py
@@ -375,7 +375,7 @@ import logging
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
from django.utils import timezone
-from xml.etree import ElementTree as ET
+import defusedxml.ElementTree as ET
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
@@ -526,122 +526,6 @@ class WeixinOfficialCallbackView(APIView):
-'''class WeixinOfficialCallbackView(APIView):
- throttle_classes = [AnonRateThrottle]
- permission_classes = [AllowAny]
- """
- 微信服务号回调接口
- 处理用户关注、取消关注等事件
- """
-
- def get(self, request):
- """验证服务器"""
- try:
- signature = request.GET.get('signature', '')
- timestamp = request.GET.get('timestamp', '')
- nonce = request.GET.get('nonce', '')
- echostr = request.GET.get('echostr', '')
-
- token = getattr(settings, 'WEIXIN_OFFICIAL_TOKEN', '')
-
- if not token:
- logger.error("服务号Token未配置")
- return HttpResponse('Token not configured', status=500)
-
- # 验证签名
- tmp_list = sorted([token, timestamp, nonce])
- tmp_str = ''.join(tmp_list)
- hash_str = hashlib.sha1(tmp_str.encode()).hexdigest()
-
- if hash_str == signature:
- return HttpResponse(echostr)
- else:
- logger.error(f"签名验证失败")
- return HttpResponse('Signature verification failed', status=403)
-
- except Exception as e:
- logger.error(f"验证服务器异常: {str(e)}")
- return HttpResponse('Internal Server Error', status=500)
-
- def post(self, request):
- """处理事件推送"""
- try:
- xml_str = request.body.decode('utf-8')
- logger.info(f"收到服务号事件: {xml_str[:200]}...")
-
- root = ET.fromstring(xml_str)
-
- msg_type = root.find('MsgType').text
- from_user = root.find('FromUserName').text # 服务号OpenID
-
- if msg_type == 'event':
- event = root.find('Event').text
-
- if event == 'subscribe':
- return self.handle_subscribe_event(root, from_user)
- elif event == 'unsubscribe':
- return self.handle_unsubscribe_event(root, from_user)
-
- return HttpResponse('success')
-
- except Exception as e:
- logger.error(f"处理服务号事件异常: {str(e)}")
- return HttpResponse('success')
-
- def handle_subscribe_event(self, root, from_user):
- """处理用户关注事件"""
- try:
- # 获取UnionID
- unionid_elem = root.find('UnionId')
- unionid = unionid_elem.text if unionid_elem is not None else None
-
- if unionid:
- # 保存或更新关联
- OfficialAccountUser.objects.update_or_create(
- unionid=unionid,
- defaults={
- 'official_openid': from_user,
- 'is_subscribed': True
- }
- )
- logger.info(f"用户关注成功: unionid={unionid}")
- else:
- logger.warning(f"用户关注但未获取到UnionID")
-
- # 返回欢迎消息
- return self.generate_text_response(root, "感谢关注!您将收到订单通知。")
-
- except Exception as e:
- logger.error(f"处理关注事件异常: {str(e)}")
- return HttpResponse('success')
-
- def handle_unsubscribe_event(self, root, from_user):
- """处理用户取消关注事件"""
- try:
- # 获取UnionID
- unionid_elem = root.find('UnionId')
- unionid = unionid_elem.text if unionid_elem is not None else None
-
- if unionid:
- OfficialAccountUser.objects.filter(unionid=unionid).update(is_subscribed=False)
- logger.info(f"用户取消关注: unionid={unionid}")
- else:
- OfficialAccountUser.objects.filter(official_openid=from_user).update(is_subscribed=False)
-
- return HttpResponse('success')
-
- except Exception as e:
- logger.error(f"处理取消关注事件异常: {str(e)}")
- return HttpResponse('success')
-
- def generate_text_response(self, root, content):
- """生成文本消息响应"""
- to_user = root.find('ToUserName').text
- from_user = root.find('FromUserName').text
-
- response_xml = f"""{int(time.time())}"""
-
- return HttpResponse(response_xml, content_type='application/xml')'''
@@ -814,259 +698,6 @@ class UserInfoUpdateView(APIView):
-'''class UserInfoUpdateView(APIView):
- """
- 用户个人信息修改接口
-
- 功能:
- - 上传 / 更新头像
- - 修改昵称
- - 更新手机号(支持明文 phone 或微信 getPhoneNumber 组件返回的 shoujihao_code)
- 当传 shoujihao_code 时,会通过微信接口解密得到真实手机号并自动设置 shoujihao_renzheng=True
- """
- permission_classes = [permissions.IsAuthenticated]
-
- def post(self, request):
- """
- 处理 POST 请求,支持 multipart/form-data 格式
- """
- try:
- # ---------- 获取当前用户 ----------
- user_main = request.user
-
- # ---------- 获取或创建老板扩展表记录 ----------
- try:
- user_boss = UserBoss.objects.get(user=user_main)
- except UserBoss.DoesNotExist:
- user_boss = UserBoss.objects.create(user=user_main, nickname='微信用户')
-
- response_data = {}
-
- # ==================== 1. 处理头像上传 ====================
- avatar_file = request.FILES.get('avatar')
- if avatar_file:
- # 验证图片格式 / 大小
- is_valid, error_msg = validate_image(avatar_file)
- if not is_valid:
- return Response({
- 'code': 1,
- 'msg': f'图片验证失败: {error_msg}',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 上传头像到 OSS / COS
- avatar_url = self.handle_avatar_upload(user_main, avatar_file)
- if avatar_url:
- response_data['touxiang'] = avatar_url
- user_main.avatar = avatar_url
- else:
- return Response({
- 'code': 2,
- 'msg': '头像上传失败',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # ==================== 2. 处理昵称更新 ====================
- nicheng = request.data.get('nicheng')
- if nicheng is not None:
- nicheng = str(nicheng).strip()
- if nicheng:
- if len(nicheng) > 20:
- return Response({
- 'code': 3,
- 'msg': '昵称不能超过20个字符',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- user_boss.nickname = nicheng
- response_data['nicheng'] = nicheng
-
- # ==================== 3. 处理手机号更新 ====================
- # 明文手机号(传统方式)
- phone =None
- shoujihao_code = request.data.get('shoujihao_code') # 微信手机号快速验证组件返回的临时 code
-
- if shoujihao_code:
- # 如果前端传了 shoujihao_code,则通过微信接口解密得到真实手机号
- try:
- phone = self._get_phone_by_code(shoujihao_code)
- logger.info(f"手机号解密成功: {phone}")
- except Exception as e:
- logger.error(f"手机号解密失败: {str(e)}")
- return Response({
- 'code': 6,
- 'msg': f'获取手机号失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- if phone is not None:
- phone = str(phone).strip()
- if phone:
- # 验证手机号格式(11 位数字)
- if not phone.isdigit() or len(phone) != 11:
- return Response({
- 'code': 4,
- 'msg': '请输入11位有效的手机号码',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 更新主表手机号,并标记手机号已认证
- user_main.phone = phone
- user_main.shoujihao_renzheng = True
- response_data['phone'] = phone
-
- # ==================== 4. 检查是否有任何更新 ====================
- if not response_data:
- return Response({
- 'code': 5,
- 'msg': '没有需要更新的数据',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # ==================== 5. 事务保存 ====================
- with transaction.atomic():
- user_main.save()
- user_boss.save()
-
- # ==================== 6. 返回成功 ====================
- return Response({
- 'code': 0,
- 'msg': '更新成功',
- 'data': response_data
- })
-
- except Exception as e:
- logger.exception("用户信息更新异常")
- return Response({
- 'code': 99,
- 'msg': f'系统错误: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # ==================== 原有方法:头像上传 ====================
- def handle_avatar_upload(self, user_main, avatar_file):
- """
- 上传头像到 OSS / COS,并返回存储的相对路径
- """
- try:
- yonghuid = user_main.yonghuid
- if not yonghuid:
- logger.warning("用户 ID 为空,无法上传头像")
- return None
-
- # 获取旧头像,用于后续删除
- old_avatar_url = user_main.avatar
-
- # 生成新文件名
- file_extension = os.path.splitext(avatar_file.name)[1].lower()
- if not file_extension:
- file_extension = '.jpg'
- unique_filename = f"{uuid.uuid4().hex}{file_extension}"
-
- # OSS 文件路径:avatar/{yonghuid}/{filename}
- avatar_folder = 'avatar'
- oss_file_path = f"{avatar_folder}/{yonghuid}/{unique_filename}"
-
- # 删除旧头像(如果存在)
- if old_avatar_url and old_avatar_url.strip():
- delete_from_oss(old_avatar_url)
-
- # 上传新头像
- new_avatar_url = upload_to_oss(avatar_file, oss_file_path)
- if new_avatar_url:
- logger.info(f"头像上传成功: {oss_file_path}")
- return oss_file_path
- else:
- logger.error("头像上传失败,upload_to_oss 返回 None")
- return None
-
- except Exception as e:
- logger.exception("处理头像上传失败")
- return None
-
- # ==================== 新增方法:微信接口相关 ====================
-
- def _get_access_token(self):
- """
- 获取微信小程序全局 access_token,带缓存
- access_token 有效期 7200 秒,提前 300 秒过期
- """
- import requests
- from django.core.cache import cache
-
- # 尝试从缓存中读取
- token = cache.get('wechat_access_token')
- if token:
- return token
-
- # 从 settings 读取 AppID 和 AppSecret
- appid = getattr(settings, 'WEIXIN_APPID', None)
- secret = getattr(settings, 'WEIXIN_SECRET', None)
- if not appid or not secret:
- raise ValueError(
- 'settings.py 中缺少 WECHAT_APPID 或 WECHAT_APPSECRET,'
- '请添加:\n'
- "WECHAT_APPID = 'wx0e4be86faac4a8d1'\n"
- "WECHAT_APPSECRET = '你的小程序密钥'"
- )
-
- url = 'https://api.weixin.qq.com/cgi-bin/token'
- params = {
- 'grant_type': 'client_credential',
- 'appid': appid,
- 'secret': secret
- }
- try:
- resp = requests.get(url, params=params)
- data = resp.json()
- except Exception as e:
- raise Exception(f'请求微信 access_token 接口异常: {str(e)}')
-
- if 'access_token' in data:
- token = data['access_token']
- # 缓存时间设 7100 秒,提前 5 分钟过期
- cache.set('wechat_access_token', token, timeout=7100)
- logger.info("成功获取微信 access_token")
- return token
- else:
- errcode = data.get('errcode')
- errmsg = data.get('errmsg')
- logger.error(f"获取 access_token 失败: errcode={errcode}, errmsg={errmsg}")
- raise Exception(f"获取 access_token 失败: {errmsg}")
-
- def _get_phone_by_code(self, code):
- """
- 通过微信手机号快速验证组件返回的 code 换取真实手机号
- 接口文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
- """
- import requests
-
- # 先获取有效的 access_token
- access_token = self._get_access_token()
-
- # 构建请求 URL
- url = f'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={access_token}'
-
- # 请求体:code 必须放在 JSON 中
- payload = {'code': code}
-
- try:
- resp = requests.post(url, json=payload, timeout=10)
- data = resp.json()
- except Exception as e:
- raise Exception(f'请求微信手机号解密接口异常: {str(e)}')
-
- errcode = data.get('errcode')
- if errcode == 0:
- phone_info = data.get('phone_info', {})
- pure_phone = phone_info.get('purePhoneNumber')
- if not pure_phone:
- raise Exception('手机号为空,可能用户未授权真实手机号')
- return pure_phone
- else:
- errmsg = data.get('errmsg')
- logger.error(f"手机号解密失败: errcode={errcode}, errmsg={errmsg}")
- raise Exception(f'微信接口错误 (errcode={errcode}): {errmsg}')'''
@@ -1522,43 +1153,8 @@ class ShangJiaXinXiView(APIView):
-'''class ShangJiaXinXiView(APIView):
- """
- 获取商家信息接口 (POST /yonghu/shangjiaxinxi)
- 权限:需JWT认证
- """
- permission_classes = [IsAuthenticated]
- def post(self, request):
- try:
- # 关键:使用 get() 方法。如果反向关联不存在,会抛出 UserShangjia.DoesNotExist 异常。
- shangjia = request.user.shop_profile
- # 如果反向关系存在,但你想更显式地查询,也可以这样写(效果相同):
- # shangjia = UserShangjia.objects.get(user=request.user)
- except ObjectDoesNotExist:
- # 捕获“不存在”异常,说明当前用户还没有商家扩展资料
- return Response({
- 'code': 4001, # 你可以定义特定的业务错误码
- 'msg': '您还未注册成为商家或商家资料不存在',
- 'data': None
- }, status=status.HTTP_200_OK) # 业务错误,HTTP状态码通常仍用200
-
- # 构建返回给前端的数据
- response_data = {
- 'sjzhzhuangtai': shangjia.zhuangtai, # 商家账号状态
- 'fadanzong': shangjia.fabu, # 发单总量
- 'tuikuanzong': shangjia.tuikuan, # 退款总量
- 'sjyue': float(shangjia.yue), # 商家余额
- 'riliushui': float(shangjia.jinriliushui), # 今日流水
- 'yueliushui': float(shangjia.jinyueliushui), # 今月流水
- }
-
- return Response({
- 'code': 200,
- 'msg': '获取商家信息成功',
- 'data': response_data
- })'''
@@ -1755,12 +1351,6 @@ class DashouZhuceView(APIView):
}, status=status.HTTP_404_NOT_FOUND)
# 4. 验证管事状态
- '''if guanshi_profile.zhuangtai != 1:
- return Response({
- 'code': 403,
- 'message': '该邀请码对应的管事账号已被禁用',
- 'data': None
- }, status=status.HTTP_403_FORBIDDEN)'''
# 5. 核心:在数据库事务中创建用户的所有身份
try:
@@ -1778,8 +1368,6 @@ class DashouZhuceView(APIView):
)
# 5.2 创建商家扩展表
- # 尝试从老板表获取昵称,没有则用默认
- '''boss_nickname = '普通商家'
try:
boss_profile = current_user.boss_profile
if boss_profile.nickname:
@@ -1787,23 +1375,8 @@ class DashouZhuceView(APIView):
except:
pass
- # 商家昵称需要唯一,这里用 用户ID + “的店铺” 来确保唯一性
- shangjia_nicheng = f"{current_user.yonghuid}的店铺"
- shop_profile = UserShangjia.objects.create(
- user=current_user,
- nicheng=shangjia_nicheng,
- # 其他字段使用默认值
- )'''
# 5.3 创建管事扩展表
- # 生成管事的邀请码
- '''guanshi_yaoqingma = chuangjianYaoqingma(str(current_user.yonghuid))
- # 如果生成失败,guanshi_yaoqingma可能为None或空,根据你的算法调整
- new_guanshi_profile = UserGuanshi.objects.create(
- user=current_user,
- yaoqingma=guanshi_yaoqingma,
- # 其他字段使用默认值
- )'''
# 5.4 更新主表的用户类型 (如果需要,可以设为'dashou',或保持原逻辑)
# current_user.user_type = 'dashou' # 根据你的业务逻辑决定
@@ -2091,42 +1664,7 @@ class TixianXinxiHuoquView(APIView):
'msg': f'系统错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-'''class TixianXinxiHuoquView(APIView):
- """
- 获取用户提现收款信息接口
- 返回用户已设置的手机号、收款码、收款账号
- """
- permission_classes = [permissions.IsAuthenticated]
- def post(self, request):
- """
- 处理获取收款信息的POST请求
- 直接返回用户当前的收款设置
- """
- try:
- # 从request.user获取当前用户
- user_main = request.user
-
- # 构建返回数据
- response_data = {
- 'txdianhua': user_main.phone if user_main.phone else '',
- 'txtupian': user_main.zhifu if user_main.zhifu else '',
- 'txzh': user_main.skzhanghao if user_main.skzhanghao else ''
- }
-
- # 返回成功响应
- return Response({
- 'code': 0,
- 'msg': '获取成功',
- 'data': response_data
- })
-
- except Exception as e:
- return Response({
- 'code': 99,
- 'msg': f'系统错误: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
from rest_framework.parsers import MultiPartParser, FormParser,JSONParser
@@ -2297,559 +1835,7 @@ import traceback
from .models import UserMain, UserDashou, UserGuanshi, Tixianjilu
-'''class TixianShenqingView(APIView):
- """
- 用户提现申请接口(修复版)
- 支持打手佣金提现、管事分红提现、组长分红提现
- 严格校验所有业务条件,修复400错误问题
- """
- permission_classes = [permissions.IsAuthenticated]
- def post(self, request):
- """
- 处理提现申请的POST请求
- 加强参数验证和错误处理
- """
- try:
- # 获取当前用户
- user_main = request.user
- yonghuid = user_main.yonghuid
-
- # 1. 获取并严格验证前端参数
- leixing = request.data.get('leixing') # 提现类型:1-佣金 2-分红 3-组长分红
- jine = request.data.get('jine')
- fangshi = request.data.get('fangshi')
- txskfs = request.data.get('txskfs', 0)
-
- # ==================== 【新增】罚单检查 ====================
- from utils.fadan_utils import check_fadan_qiangdan_eligible
- fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(request.user.yonghuid, 2)
- if not fadan_ok:
- return Response({'code': 400, 'msg': fadan_msg})
- # =========================================================
-
- # 验证必需参数是否存在
- missing_params = []
- if leixing is None:
- missing_params.append('leixing')
- if jine is None:
- missing_params.append('jine')
- if fangshi is None:
- missing_params.append('fangshi')
-
- if missing_params:
- return Response({
- 'code': 1,
- 'msg': f'缺少必需参数: {", ".join(missing_params)}',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 验证参数类型和格式
- try:
- leixing = int(leixing)
- if isinstance(jine, str):
- jine = decimal.Decimal(jine)
- else:
- jine = decimal.Decimal(str(jine))
- fangshi = int(fangshi)
- txskfs = int(txskfs)
- except (ValueError, TypeError, decimal.InvalidOperation) as e:
- return Response({
- 'code': 2,
- 'msg': f'参数格式错误: {str(e)}',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 验证提现金额必须大于0
- if jine <= decimal.Decimal('0'):
- return Response({
- 'code': 3,
- 'msg': '提现金额必须大于0',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 验证提现类型有效性(新增类型3)
- if leixing not in [1, 2, 3,4]:
- return Response({
- 'code': 4,
- 'msg': '无效的提现类型,只能是1(佣金)、2(分红)或3(组长分红)',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 验证收款方式有效性
- if fangshi not in [1, 2]:
- return Response({
- 'code': 5,
- 'msg': '无效的收款方式,只能是1(微信)或2(支付宝)',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 🔥【组长支持】获取对应费率(增加组长)
- current_rate = decimal.Decimal('0.00')
- if leixing == 1:
- dashou_rate_obj = Lilubiao.objects.filter(fadanpingtai='5').first()
- if dashou_rate_obj:
- current_rate = decimal.Decimal(str(dashou_rate_obj.lilu))
- elif leixing == 2:
- guanshi_rate_obj = Lilubiao.objects.filter(fadanpingtai='6').first()
- if guanshi_rate_obj:
- current_rate = decimal.Decimal(str(guanshi_rate_obj.lilu))
- elif leixing == 3:
- # 组长提现费率 (fadanpingtai='8')
- zuzhang_rate_obj = Lilubiao.objects.filter(fadanpingtai='8').first()
- if zuzhang_rate_obj:
- current_rate = decimal.Decimal(str(zuzhang_rate_obj.lilu))
- else:
- return Response({
- 'code': 31,
- 'msg': '组长费率未配置,无法提现',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- elif leixing == 4:
- # 审核官提现费率 (fadanpingtai='9',数字可自定义)
- kaoheguan_rate_obj = Lilubiao.objects.filter(fadanpingtai='9').first()
- if kaoheguan_rate_obj:
- current_rate = decimal.Decimal(str(kaoheguan_rate_obj.lilu))
- else:
- return Response({
- 'code': 41,
- 'msg': '审核官费率未配置,无法提现',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 计算手续费和实际到账金额
- shouxufei = jine * current_rate
- shijidaozhang = jine - shouxufei
-
- # 验证手续费和实际到账金额的有效性
- if shijidaozhang <= decimal.Decimal('0'):
- return Response({
- 'code': 6,
- 'msg': '提现金额过低,扣除手续费后无实际到账',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 2. 根据提现类型进行不同的校验
- if leixing == 1:
- # 佣金提现 - 打手
- result = self.yanzheng_dashou_tixian(user_main, jine)
- if result['code'] != 0:
- return Response(result, status=status.HTTP_400_BAD_REQUEST)
-
- dashou_info = result['data']
- nicheng = dashou_info.get('nicheng', '')
-
- # 使用事务更新打手余额
- try:
- with transaction.atomic():
- dashou_profile = UserDashou.objects.select_for_update().get(user=user_main)
- if dashou_profile.yue < jine:
- return Response({
- 'code': 13,
- 'msg': '余额不足,无法提现',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
- dashou_profile.yue = F('yue') - jine
- dashou_profile.save()
- dashou_profile.refresh_from_db()
- except Exception as e:
- return Response({
- 'code': 99,
- 'msg': f'更新余额失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- elif leixing == 2:
- # 分红提现 - 管事
- result = self.yanzheng_guanshi_tixian(user_main, jine)
- if result['code'] != 0:
- return Response(result, status=status.HTTP_400_BAD_REQUEST)
-
- nicheng = "管事用户"
-
- try:
- with transaction.atomic():
- guanshi_profile = UserGuanshi.objects.select_for_update().get(user=user_main)
- if guanshi_profile.yue < jine:
- return Response({
- 'code': 22,
- 'msg': '余额不足,无法提现',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
- guanshi_profile.yue = F('yue') - jine
- guanshi_profile.save()
- guanshi_profile.refresh_from_db()
- except Exception as e:
- return Response({
- 'code': 99,
- 'msg': f'更新余额失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 🔥【组长支持】新增组长提现分支
- elif leixing == 3:
- # 组长分红提现
- result = self.yanzheng_zuzhang_tixian(user_main, jine)
- if result['code'] != 0:
- return Response(result, status=status.HTTP_400_BAD_REQUEST)
-
- zuzhang_info = result['data']
- nicheng = zuzhang_info.get('nicheng', '')
-
- try:
- with transaction.atomic():
- zuzhang_profile = UserZuzhang.objects.select_for_update().get(user=user_main)
- if zuzhang_profile.ketixian_jine < jine:
- return Response({
- 'code': 32,
- 'msg': '组长可提现金额不足',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
- # 扣除申请金额
- zuzhang_profile.ketixian_jine = F('ketixian_jine') - jine
- zuzhang_profile.save()
- zuzhang_profile.refresh_from_db()
- except Exception as e:
- return Response({
- 'code': 99,
- 'msg': f'更新组长余额失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
- # 🔥【审核官支持】新增审核官提现分支
- elif leixing == 4:
- # 审核官分佣提现
- result = self.yanzheng_kaoheguan_tixian(user_main, jine)
- if result['code'] != 0:
- return Response(result, status=status.HTTP_400_BAD_REQUEST)
-
- kaoheguan_info = result['data']
- nicheng = kaoheguan_info.get('nicheng', '')
-
- try:
- with transaction.atomic():
- kaoheguan_profile = UserShenheguan.objects.select_for_update().get(user=user_main)
- if kaoheguan_profile.yue < jine:
- return Response({
- 'code': 42,
- 'msg': '审核官可提现金额不足',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
- # 扣除申请金额
- kaoheguan_profile.yue = F('yue') - jine
- kaoheguan_profile.save()
- kaoheguan_profile.refresh_from_db()
- except Exception as e:
- return Response({
- 'code': 99,
- 'msg': f'更新审核官余额失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
-
-
- # 3. 创建提现记录
- try:
- avatar = user_main.avatar if user_main.avatar else ''
- phone = user_main.phone if user_main.phone else ''
- zhifu = user_main.zhifu if user_main.zhifu else ''
- skzhanghao = user_main.skzhanghao if user_main.skzhanghao else ''
-
- # 对于打手或组长,从扩展表获取昵称
- if leixing == 1:
- try:
- dashou_profile = UserDashou.objects.get(user=user_main)
- nicheng = dashou_profile.nicheng if dashou_profile.nicheng else ''
- except UserDashou.DoesNotExist:
- nicheng = ''
- elif leixing == 3:
- # 组长昵称从组长扩展表获取(如果需要)
- try:
- zuzhang_profile = UserZuzhang.objects.get(user=user_main)
- # 组长扩展表可能没有昵称字段,可以从UserMain获取
- nicheng = user_main.nicheng if hasattr(user_main, 'nicheng') else ''
- except UserZuzhang.DoesNotExist:
- nicheng = ''
-
- tixian_jilu = Tixianjilu.objects.create(
- yonghuid=yonghuid,
- avatar=avatar,
- phone=phone,
- nicheng=nicheng,
- leixing=leixing,
- zhifu=zhifu,
- skzhanghao=skzhanghao,
- jine=shijidaozhang, # 记录实际到账金额
- zhuangtai=1,
- fangshi=fangshi,
- shenheid=None,
- bhliyou='',
- )
-
- # 4. 返回成功响应
- return Response({
- 'code': 0,
- 'msg': '提现申请提交成功',
- 'data': {
- 'tixian_id': tixian_jilu.id,
- 'leixing': leixing,
- 'jine': str(jine.quantize(decimal.Decimal('0.01'))),
- 'fangshi': fangshi,
- 'zhuangtai': 1,
- 'create_time': tixian_jilu.create_time.strftime('%Y-%m-%d %H:%M:%S'),
- 'current_rate': str(current_rate.quantize(decimal.Decimal('0.0000'))),
- 'shouxufei': str(shouxufei.quantize(decimal.Decimal('0.01'))),
- 'shijidaozhang': str(shijidaozhang.quantize(decimal.Decimal('0.01'))),
- 'tip': f'本次提现手续费率{current_rate * 100}%,手续费{shouxufei}元,实际到账{shijidaozhang}元'
- }
- })
-
- except Exception as e:
- traceback.print_exc()
- return Response({
- 'code': 99,
- 'msg': f'创建提现记录失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- except Exception as e:
- traceback.print_exc()
- return Response({
- 'code': 99,
- 'msg': f'系统错误: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
- # 原有校验函数不变
- # 原有校验函数不变
-
- def yanzheng_kaoheguan_tixian(self, user_main, jine):
- """
- 校验审核官分佣提现条件
- """
- try:
- # 查询审核官扩展表
- try:
- kaoheguan_profile = UserShenheguan.objects.get(user=user_main)
- except UserShenheguan.DoesNotExist:
- return {
- 'code': 40,
- 'msg': '您不是审核官身份,无法提现分佣',
- 'data': None
- }
-
- # 校验1: 审核官账号状态 (zhuangtai != 1)
- if kaoheguan_profile.zhuangtai != 1:
- return {
- 'code': 41,
- 'msg': '审核官账号已被封禁,无法提现',
- 'data': None
- }
-
- # 校验2: 可提现金额是否足够 (yue < jine)
- if kaoheguan_profile.yue < jine:
- return {
- 'code': 42,
- 'msg': f'审核官可提现金额不足,当前可提: {kaoheguan_profile.yue},申请金额: {jine}',
- 'data': None
- }
-
- # 所有校验通过
- return {
- 'code': 0,
- 'msg': '校验通过',
- 'data': {
- 'nicheng': '审核官', # 可根据需要调整
- 'yue': kaoheguan_profile.yue
- }
- }
-
- except Exception as e:
- return {
- 'code': 99,
- 'msg': f'审核官提现校验过程出错: {str(e)}',
- 'data': None
- }
-
- def yanzheng_dashou_tixian(self, user_main, jine):
- """
- 校验打手佣金提现条件(修复版)
- 修正了状态判断逻辑
- """
- try:
- # print(f"开始校验打手提现 - 用户: {user_main.yonghuid}, 金额: {jine}")
-
- # 查询打手扩展表
- try:
- dashou_profile = UserDashou.objects.get(user=user_main)
- # print(
- # f"打手信息 - 状态: {dashou_profile.zhuangtai}, 账号状态: {dashou_profile.zhanghaozhuangtai}, 积分: {dashou_profile.jifen}, 余额: {dashou_profile.yue}")
- except UserDashou.DoesNotExist:
- # print("打手扩展表记录不存在")
- return {
- 'code': 10,
- 'msg': '您不是打手身份,无法提现佣金',
- 'data': None
- }
-
- # 校验1: 打手账号状态 (zhanghaozhuangtai != 1 则封禁)
- if dashou_profile.zhanghaozhuangtai != 1:
- return {
- 'code': 11,
- 'msg': '打手账号已被封禁,无法提现',
- 'data': None
- }
-
- # 校验2: 打手接单状态 (zhuangtai != 1 则有订单进行中) - 修复这里
- if dashou_profile.zhuangtai != 1:
- return {
- 'code': 12,
- 'msg': '您有订单进行中,请完成后提现',
- 'data': None
- }
-
- # 校验3: 积分检查 (jifen != 10 则不足) - 新增
- if dashou_profile.jifen != 10:
- return {
- 'code': 15, # 使用新的错误码
- 'msg': '积分不足10分,请补充积分后提现',
- 'data': None
- }
-
- # 校验4: 余额是否足够 (yue < jine)
- if dashou_profile.yue < jine:
- return {
- 'code': 13,
- 'msg': f'余额不足,当前余额: {dashou_profile.yue},提现金额: {jine}',
- 'data': None
- }
-
- # print("打手提现校验通过")
-
- # 所有校验通过
- return {
- 'code': 0,
- 'msg': '校验通过',
- 'data': {
- 'nicheng': dashou_profile.nicheng if dashou_profile.nicheng else '',
- 'yue': dashou_profile.yue,
- 'jifen': dashou_profile.jifen
- }
- }
-
- except Exception as e:
- # print(f"打手提现校验异常: {e}")
- return {
- 'code': 99,
- 'msg': f'校验过程出错: {str(e)}',
- 'data': None
- }
-
- def yanzheng_guanshi_tixian(self, user_main, jine):
- """
- 校验管事分红提现条件
- """
- try:
- # print(f"开始校验管事提现 - 用户: {user_main.yonghuid}, 金额: {jine}")
-
- # 查询管事扩展表
- try:
- guanshi_profile = UserGuanshi.objects.get(user=user_main)
- # print(f"管事信息 - 状态: {guanshi_profile.zhuangtai}, 余额: {guanshi_profile.yue}")
- except UserGuanshi.DoesNotExist:
- # print("管事扩展表记录不存在")
- return {
- 'code': 20,
- 'msg': '您不是管事身份,无法提现分红',
- 'data': None
- }
-
- # 校验1: 管事账号状态 (zhuangtai != 1)
- if guanshi_profile.zhuangtai != 1:
- return {
- 'code': 21,
- 'msg': '管事账号已被封禁,无法提现',
- 'data': None
- }
-
- # 校验2: 余额是否足够 (yue < jine)
- if guanshi_profile.yue < jine:
- return {
- 'code': 22,
- 'msg': f'余额不足,当前余额: {guanshi_profile.yue},提现金额: {jine}',
- 'data': None
- }
-
- # print("管事提现校验通过")
-
- # 所有校验通过
- return {
- 'code': 0,
- 'msg': '校验通过',
- 'data': {
- 'yue': guanshi_profile.yue
- }
- }
-
- except Exception as e:
- # print(f"管事提现校验异常: {e}")
- return {
- 'code': 99,
- 'msg': f'校验过程出错: {str(e)}',
- 'data': None
- }
-
-
- # 🔥【组长支持】新增组长校验函数
- def yanzheng_zuzhang_tixian(self, user_main, jine):
- """
- 校验组长分红提现条件
- """
- try:
- # 查询组长扩展表
- try:
- zuzhang_profile = UserZuzhang.objects.get(user=user_main)
- except UserZuzhang.DoesNotExist:
- return {
- 'code': 30,
- 'msg': '您不是组长身份,无法提现组长分红',
- 'data': None
- }
-
- # 校验1: 组长账号状态 (zhuangtai != 1)
- if zuzhang_profile.zhuangtai != 1:
- return {
- 'code': 31,
- 'msg': '组长账号已被封禁,无法提现',
- 'data': None
- }
-
- # 校验2: 可提现金额是否足够
- if zuzhang_profile.ketixian_jine < jine:
- return {
- 'code': 32,
- 'msg': f'组长可提现金额不足,当前可提: {zuzhang_profile.ketixian_jine},申请金额: {jine}',
- 'data': None
- }
-
- # 所有校验通过
- return {
- 'code': 0,
- 'msg': '校验通过',
- 'data': {
- 'nicheng': user_main.nicheng if hasattr(user_main, 'nicheng') else '',
- 'ketixian_jine': zuzhang_profile.ketixian_jine
- }
- }
-
- except Exception as e:
- return {
- 'code': 99,
- 'msg': f'组长提现校验过程出错: {str(e)}',
- 'data': None
- }'''
@@ -3218,113 +2204,7 @@ class TixianShenqingView(APIView):
-'''class TixianJiluHuoquViewV2(APIView):
- """
- 提现记录获取接口(优化版)
- 使用更高效的查询方式,减少数据库压力
- """
- permission_classes = [permissions.IsAuthenticated]
- def post(self, request):
- try:
- # 获取当前用户
- user_main = request.user
- yonghuid = user_main.yonghuid
-
- # 获取分页参数
- page = request.data.get('page', 1)
- limit = request.data.get('limit', 50)
-
- # 参数验证
- try:
- page = int(page)
- limit = int(limit)
- if page < 1 or limit < 1:
- return Response({
- 'code': 1,
- 'msg': '参数错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
- except:
- return Response({
- 'code': 1,
- 'msg': '参数格式错误',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 限制最大查询数量
- if limit > 100:
- limit = 100
-
- # 计算偏移量
- offset = (page - 1) * limit
-
- # 🔴【修改开始】添加必要字段:id, phone, skzhanghao, zhifu
- records_queryset = Tixianjilu.objects.filter(
- yonghuid=yonghuid
- ).order_by('-create_time').values(
- 'id', # 🔴【关键】添加ID字段
- 'leixing',
- 'jine',
- 'zhuangtai',
- 'create_time',
- 'fangshi',
- 'phone', # 🔴【新增】手机号
- 'skzhanghao', # 🔴【新增】收款账号
- 'zhifu' , # 🔴【新增】收款码图片
- 'bhliyou'
- )[offset:offset + limit + 1] # 🔴【修复】使用limit + 1
-
- # 转换为列表
- records_list = list(records_queryset)
- records_count = len(records_list)
-
- # 判断是否有更多数据
- has_more = records_count > limit
-
- # 如果查询到的数量大于limit,只返回limit条
- if has_more:
- records_to_return = records_list[:limit]
- else:
- records_to_return = records_list
-
- # 格式化数据
- formatted_records = []
- for record in records_to_return:
- formatted_records.append({
- 'tixian_id': record['id'], # 🔴【关键】使用tixian_id作为字段名
- 'leixing': record['leixing'],
- 'jine': float(record['jine']) if record['jine'] else 0.00,
- 'zhuangtai': record['zhuangtai'],
- 'create': record['create_time'].strftime('%Y-%m-%d %H:%M:%S'),
- 'fangshi': record['fangshi'],
- 'txdianhua': record['phone'] or '', # 🔴【新增】手机号
- 'txzh': record['skzhanghao'] or '', # 🔴【新增】收款账号
- 'txtupian': record['zhifu'] or '', # 🔴【新增】收款码图片
- 'shibaiyuanyin':record['bhliyou'] or '',
- })
-
- # 构建响应
- response_data = {
- 'list': formatted_records,
- 'has_more': has_more,
- 'page': page,
- 'limit': limit,
- 'current_count': len(formatted_records)
- }
-
- return Response({
- 'code': 0,
- 'msg': '获取成功',
- 'data': response_data
- })
-
- except Exception as e:
- return Response({
- 'code': 99,
- 'msg': f'系统错误: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
class TixianJiluHuoquViewV2(APIView):
@@ -3542,12 +2422,7 @@ class AdminLoginView(APIView):
# 处理IP地址长度问题
# 如果原ip字段长度不够,截取前11个字符(保持兼容性)
- '''if len(kehuduan_ip) > 11:
- # 如果ip字段长度限制是11,我们截取前11个字符
- # 建议在数据库中修改ip字段为50长度
- cunchu_ip = kehuduan_ip[:11]
- else:
- cunchu_ip = kehuduan_ip'''
+
cunchu_ip = kehuduan_ip
#print(cunchu_ip)
@@ -4822,399 +3697,12 @@ class CfGuanLi(APIView):
-'''from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.parsers import JSONParser
-from rest_framework import status
-from django.db.models import Q
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-class CfGuanLi(APIView):
- """
- 后台处罚管理列表接口
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /yonghu/cfgl
- 参数: {
- "zhanghao": "管理员账号",
- "qingqiu_tongji": true/false, # 是否只请求统计信息
- "sqzhuangtai": 0 或 [1,2], # 处罚申请状态筛选(可选)
- "page": "页码(从1开始)", # 分页参数
- "page_size": "每页数量" # 分页参数
- }
- 返回: {
- "code": 0,
- "message": "成功",
- "data": {
- "list": [处罚记录列表],
- "zongshu": 总数,
- "daichuli": 待处理数量,
- "yichuli": 已处理数量,
- "haiyougengduo": true/false
- }
- }
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- qingqiu_tongji = request.data.get('qingqiu_tongji', False)
- sqzhuangtai = request.data.get('sqzhuangtai')
- page = int(request.data.get('page', 1))
- page_size = int(request.data.get('page_size', 30))
-
- # 验证参数
- if not qianzhanghao:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 导入模型
- from dingdan.models import Chufajilu
-
- # 7. 获取统计信息(总是需要统计)
- zongshu = Chufajilu.objects.count()
- daichuli = Chufajilu.objects.filter(sqzhuangtai=0).count()
- yichuli = Chufajilu.objects.filter(sqzhuangtai__in=[1, 2]).count()
-
- # 8. 如果只请求统计信息,直接返回
- if qingqiu_tongji:
- return Response({
- 'code': 0,
- 'message': '成功',
- 'data': {
- 'zongshu': zongshu,
- 'daichuli': daichuli,
- 'yichuli': yichuli
- }
- })
-
- # 9. 构建查询集
- queryset = Chufajilu.objects.all()
-
- # 10. 根据状态筛选
- if sqzhuangtai is not None:
- if isinstance(sqzhuangtai, list):
- # 如果是数组,表示多个状态
- queryset = queryset.filter(sqzhuangtai__in=sqzhuangtai)
- else:
- # 单个状态
- queryset = queryset.filter(sqzhuangtai=sqzhuangtai)
-
- # 11. 获取总数(用于分页计算)
- total_count = queryset.count()
-
- # 12. 计算分页偏移量
- offset = (page - 1) * page_size
-
- # 13. 分页查询
- chufa_list = list(queryset.order_by('-create_time')[offset:offset + page_size])
-
- # 14. 构建返回数据
- chufa_data = []
- for chufa in chufa_list:
- chufa_data.append({
- 'dashouid': chufa.dashouid,
- 'qingqiuid': chufa.qingqiuid,
- 'chuliid': chufa.chuliid,
- 'cfliyou': chufa.cfliyou,
- 'sqzhuangtai': chufa.sqzhuangtai,
- 'bhliyou': chufa.bhliyou,
- 'jifen': chufa.jifen,
- 'dingdan_id': chufa.dingdan_id,
- 'create_time': chufa.create_time,
- 'update_time': chufa.update_time
- })
-
- # 15. 计算是否还有更多数据
- current_count = len(chufa_list)
- haiyougengduo = (offset + current_count) < total_count
-
- return Response({
- 'code': 0,
- 'message': '成功',
- 'data': {
- 'list': chufa_data,
- 'zongshu': zongshu,
- 'daichuli': daichuli,
- 'yichuli': yichuli,
- 'haiyougengduo': haiyougengduo
- }
- })
-
- except ValueError:
- return Response(
- {'code': 400, 'message': '参数类型错误', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
- except Exception as e:
- logger.error(f"处罚管理列表接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )'''
-'''logger = logging.getLogger(__name__)
-class AdTongYiChuFa(APIView):
- """
- 后台同意/驳回处罚接口(已修复重复记录问题)
- 权限: JWT Token认证 + 管理员身份验证
- 前端调用: POST /yonghu/adtycf
- 参数: {
- "zhanghao": "管理员账号",
- "dingdan_id": "订单ID",
- "caozuo": 1或2, # 1=同意处罚,2=驳回处罚
- "bohui_liyou": "驳回理由" # 驳回处罚时必填
- }
- 返回: {
- "code": 0,
- "message": "成功",
- "data": null
- }
- """
-
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- try:
- # 1. 获取前端参数
- qianzhanghao = request.data.get('zhanghao', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
- caozuo = request.data.get('caozuo') # 1=同意,2=驳回
- bohui_liyou = request.data.get('bohui_liyou', '').strip()
-
- # 验证参数
- if not qianzhanghao or not dingdan_id or caozuo is None:
- return Response(
- {'code': 400, 'message': '参数不完整', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 验证操作类型
- if caozuo not in [1, 2]:
- return Response(
- {'code': 400, 'message': '操作类型错误', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 如果是驳回处罚,需要驳回理由
- if caozuo == 2 and not bohui_liyou:
- return Response(
- {'code': 400, 'message': '驳回处罚需要填写理由', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 2. JWT Token身份验证
- dangqianyonghu = request.user
-
- # 3. 验证手机号是否匹配
- if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 4. 验证用户类型是否为管理员
- if dangqianyonghu.user_type != 'admin':
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 5. 验证管理员扩展表是否存在
- try:
- guanliyuan = dangqianyonghu.admin_profile
- except AttributeError:
- return Response(
- {'code': 401, 'message': '身份验证失败', 'data': None},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 6. 使用事务保证数据一致性
- with transaction.atomic():
- # 7. 导入模型
- from dingdan.models import Chufajilu, Dingdan, DingdanShangjia
- from yonghu.models import UserDashou
-
- # 8. 🔴 修复:查询处罚记录(处理重复记录问题)
- # 使用 filter().first() 而不是 get(),以防出现多条记录
- chufajilu = Chufajilu.objects.filter(
- dingdan_id=dingdan_id,
- sqzhuangtai=0 # 只查询待处理状态的记录
- ).order_by('-create_time').first() # 取最新的一条
-
- if not chufajilu:
- # 如果没有待处理的处罚记录,检查是否有已处理的
- exists_processed = Chufajilu.objects.filter(dingdan_id=dingdan_id).exists()
- if exists_processed:
- return Response(
- {'code': 400, 'message': '该处罚记录已处理', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
- else:
- return Response(
- {'code': 404, 'message': '处罚记录不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 9. 验证处罚记录状态(必须是待处理状态才能处理)
- if chufajilu.sqzhuangtai != 0:
- return Response(
- {'code': 400, 'message': '该处罚记录已处理', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 10. 🔴 修复:检查是否有重复的待处理记录,并记录警告
- duplicate_count = Chufajilu.objects.filter(
- dingdan_id=dingdan_id,
- sqzhuangtai=0
- ).count()
-
- if duplicate_count > 1:
- # 记录警告日志
- logger.warning(f"订单ID {dingdan_id} 存在 {duplicate_count} 条待处理处罚记录,处理最新的一条")
-
- # 🔴 可选:将其他重复的待处理记录标记为已驳回
- # 防止用户重复提交相同订单的处罚申请
- duplicate_records = Chufajilu.objects.filter(
- dingdan_id=dingdan_id,
- sqzhuangtai=0
- ).exclude(id=chufajilu.id)
-
- for dup in duplicate_records:
- dup.sqzhuangtai = 2 # 已驳回
- dup.chuliid = qianzhanghao
- dup.bhliyou = "系统自动驳回:存在重复处罚记录"
- dup.save()
-
- # 11. 根据操作类型处理
- if caozuo == 1:
- # 同意处罚
- # 11.1 检查打手是否存在
- if not chufajilu.dashouid:
- return Response(
- {'code': 400, 'message': '被处罚打手ID不存在', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
-
- # 11.2 查询打手扩展表
- try:
- dashou = UserDashou.objects.get(user__yonghuid=chufajilu.dashouid)
- except UserDashou.DoesNotExist:
- return Response(
- {'code': 404, 'message': '被处罚打手不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 11.3 扣除打手积分(确保积分不为负数)
- jifen_kouchu = chufajilu.jifen
- if jifen_kouchu > 0:
- # 检查积分是否足够
- if dashou.jifen < jifen_kouchu:
- # 如果积分不足,设置为0
- dashou.jifen = 0
- else:
- dashou.jifen -= jifen_kouchu
- dashou.save()
-
- # 11.4 更新处罚记录
- chufajilu.sqzhuangtai = 1 # 已处罚
- chufajilu.chuliid = qianzhanghao # 处理人ID为管理员账号
- chufajilu.save()
-
- # 11.5 更新商家订单扩展表
- try:
- dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
- if hasattr(dingdan, 'shangjia_kuozhan'):
- shangjia_kuozhan = dingdan.shangjia_kuozhan
- shangjia_kuozhan.sqzhuangtai = 1 # 已处罚
- shangjia_kuozhan.save()
- except (Dingdan.DoesNotExist, AttributeError):
- # 如果订单或扩展表不存在,继续执行,不中断
- pass
-
- #logger.info(f"同意处罚成功:订单ID={dingdan_id},打手ID={chufajilu.dashouid},扣除积分={jifen_kouchu}")
-
- elif caozuo == 2:
- # 驳回处罚
- # 11.6 更新处罚记录
- chufajilu.sqzhuangtai = 2 # 已驳回
- chufajilu.chuliid = qianzhanghao # 处理人ID为管理员账号
- chufajilu.bhliyou = bohui_liyou # 驳回理由
- chufajilu.save()
-
- # 11.7 更新商家订单扩展表
- try:
- dingdan = Dingdan.objects.get(dingdan_id=dingdan_id)
- if hasattr(dingdan, 'shangjia_kuozhan'):
- shangjia_kuozhan = dingdan.shangjia_kuozhan
- shangjia_kuozhan.sqzhuangtai = 2 # 已驳回
- shangjia_kuozhan.bhliyou = bohui_liyou # 驳回理由
- shangjia_kuozhan.save()
- except (Dingdan.DoesNotExist, AttributeError):
- # 如果订单或扩展表不存在,继续执行,不中断
- pass
-
- #logger.info(f"驳回处罚成功:订单ID={dingdan_id},驳回理由={bohui_liyou}")
-
- # 12. 返回成功响应
- return Response({
- 'code': 0,
- 'message': '处理成功',
- 'data': None
- })
-
- except ValueError:
- return Response(
- {'code': 400, 'message': '参数类型错误', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
- except Exception as e:
- logger.error(f"处理处罚接口异常: {str(e)}", exc_info=True)
- return Response(
- {'code': 500, 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )'''
@@ -5973,69 +4461,6 @@ class AdcjxgView(APIView):
modify_record_data['qitashuoming'] += f' 修改了字段: {",".join(non_sensitive_fields)}'
# 处理打手会员添加(只有打手可以添加会员)
- '''if user_type == 2 and 'huiyuan_id' in request.data and 'days' in request.data:
- huiyuan_id = request.data.get('huiyuan_id')
- days = int(request.data.get('days', 0))
-
- if huiyuan_id and days > 0:
- if days < 1 or days > 10000:
- return Response({
- 'code': 400,
- 'message': '会员天数必须在1-10000之间',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 检查会员是否存在
- huiyuan = Huiyuan.objects.filter(huiyuan_id=huiyuan_id).first()
- if not huiyuan:
- return Response({
- 'code': 404,
- 'message': '会员不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
-
- # 检查是否已购买该会员
- existing_record = Huiyuangoumai.objects.filter(
- yonghu_id=uid,
- huiyuan_id=huiyuan_id
- ).first()
-
- if existing_record:
- # 续费:延长到期时间
- if existing_record.daoqi_time < timezone.now():
- # 已过期,从当前时间开始算
- new_daoqi_time = timezone.now() + timedelta(days=days)
- else:
- # 未过期,从原到期时间开始算
- new_daoqi_time = existing_record.daoqi_time + timedelta(days=days)
-
- existing_record.daoqi_time = new_daoqi_time
- existing_record.save()
-
- # 记录会员续费
- has_sensitive_change = True
- modify_record_data['huiyuants'] = days
- modify_record_data['huiyuan_id'] = huiyuan_id
- modify_record_data['qitashuoming'] += f' 续费会员 {huiyuan.jieshao} {days}天'
- else:
- # 新购买
- daoqi_time = timezone.now() + timedelta(days=days)
- new_record = Huiyuangoumai.objects.create(
- huiyuan_id=huiyuan_id,
- yonghu_id=uid,
- jieshao=huiyuan.jieshao,
- daoqi_time=daoqi_time
- )
-
- # 记录会员购买
- has_sensitive_change = True
- modify_record_data['huiyuants'] = days
- modify_record_data['huiyuan_id'] = huiyuan_id
- modify_record_data['qitashuoming'] += f' 购买会员 {huiyuan.jieshao} {days}天'
-
- # 更新会员购买次数
- huiyuan.goumai_cishu += 1
- huiyuan.save()'''
# 🔥🔥🔥 后端修改:支持正负数会员天数
# 修改会员处理逻辑部分
@@ -6477,87 +4902,7 @@ class AdtixianqkView(APIView):
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-'''class AdtixianqkView(APIView):
- """
- 处理提现申请接口(保持不变)
- """
- permission_classes = [IsAuthenticated]
- @admin_required
- def post(self, request):
- try:
- zhanghao = request.data.get('zhanghao')
- tixian_id = request.data.get('tixian_id')
- result = int(request.data.get('result', 0))
- reason = request.data.get('reason', '')
-
- if not tixian_id:
- return Response({
- 'code': 400,
- 'message': '提现记录ID不能为空',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- if result not in [2, 3]:
- return Response({
- 'code': 400,
- 'message': '操作结果无效',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 如果是拒绝,需要理由
- if result == 3 and not reason:
- return Response({
- 'code': 400,
- 'message': '拒绝提现需要提供理由',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 获取提现记录
- try:
- withdrawal = Tixianjilu.objects.get(id=tixian_id)
- except Tixianjilu.DoesNotExist:
- return Response({
- 'code': 404,
- 'message': '提现记录不存在',
- 'data': None
- }, status=status.HTTP_404_NOT_FOUND)
-
- # 检查状态,只有待审核的可以处理
- if withdrawal.zhuangtai != 1:
- return Response({
- 'code': 400,
- 'message': '该提现申请已被处理',
- 'data': None
- }, status=status.HTTP_400_BAD_REQUEST)
-
- # 更新提现记录
- withdrawal.zhuangtai = result
- withdrawal.shenheid = request.user.yonghuid
-
- if result == 3: # 拒绝
- withdrawal.bhliyou = reason
-
- withdrawal.update_time = timezone.now()
- withdrawal.save()
-
- return Response({
- 'code': 0,
- 'message': '处理成功',
- 'data': {
- 'id': withdrawal.id,
- 'zhuangtai': withdrawal.zhuangtai,
- 'shenheid': withdrawal.shenheid,
- 'update_time': withdrawal.update_time
- }
- }, status=status.HTTP_200_OK)
-
- except Exception as e:
- return Response({
- 'code': 500,
- 'message': f'处理提现失败: {str(e)}',
- 'data': None
- }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)'''
# views.py (yonghu应用)
from rest_framework.views import APIView
@@ -7271,9 +5616,6 @@ class WechatLoginAndDashouRegisterView(APIView):
return self.fanhuiZhihouDengluXinxi(user_main, yaoqingma_wuxiao=True)
# 验证管事状态
- '''if guanshi_profile.zhuangtai != 1:
- # 管事账号已被禁用
- return self.fanhuiZhihouDengluXinxi(user_main, guanshi_jinyong=True)'''
# 4.3 检查用户是否已是打手
dashou_exists = UserDashou.objects.filter(user=user_main).exists()
@@ -7295,21 +5637,8 @@ class WechatLoginAndDashouRegisterView(APIView):
)
# 创建商家扩展表
- # 商家昵称需要唯一,这里用 用户ID + "的店铺" 来确保唯一性
- '''shangjia_nicheng = f"{user_main.yonghuid}的店铺"
- UserShangjia.objects.create(
- user=user_main,
- nicheng=shangjia_nicheng,
- # 其他字段使用默认值
- )'''
# 创建管事扩展表
- '''guanshi_yaoqingma = chuangjianYaoqingma(str(user_main.yonghuid))
- new_guanshi_profile = UserGuanshi.objects.create(
- user=user_main,
- yaoqingma=guanshi_yaoqingma,
- # 其他字段使用默认值
- )'''
# 更新原管事的邀请数量
locked_guanshi = UserGuanshi.objects.select_for_update().get(pk=guanshi_profile.pk)
@@ -8954,319 +7283,6 @@ from dingdan.models import Dingdan, DingdanPingtai
logger = logging.getLogger(__name__)
-'''class KefuGetOrderListView(APIView):
- """
- 客服获取平台订单列表接口(发单平台=1)
- 请求参数:
- phone: 客服账号
- page: 页码
- pageSize: 每页条数
- dingdan_id: 订单ID(精确搜索)
- laoban_id: 老板ID(搜索平台扩展表中的老板)
- dashou_id: 打手ID(搜索主表中的接单打手)
- leixing: 商品类型ID
- zhuangtai: 状态(多个用逗号分隔,如 "1,7")
- date: 日期(YYYY-MM-DD),筛选订单创建时间
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- start_total = time.time()
-
- phone = request.data.get('phone', '').strip()
- page = int(request.data.get('page', 1))
- page_size = int(request.data.get('pageSize', 20))
-
- dingdan_id = request.data.get('dingdan_id', '').strip()
- laoban_id = request.data.get('laoban_id', '').strip()
- dashou_id = request.data.get('dashou_id', '').strip()
- leixing = request.data.get('leixing')
- zhuangtai = request.data.get('zhuangtai', '')
- date = request.data.get('date', '').strip() # 日期筛选
-
- if not phone:
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- current_user = request.user
- # 1. 获取前端参数
- username = request.data.get('phone', '').strip()
- if not username:
- return Response({'code': 400, 'msg': '缺少username'})
-
- # 2. 公共权限校验
- kefu, permissions = verify_kefu_permission(request, username)
- if kefu is None:
- return permissions
-
- # 3. 检查平台订单管理权限(002ab)
- if '002ab' not in permissions:
- return Response({'code': 403, 'msg': '您无权查看平台订单列表'})
-
- # ---------- 查询条件 ----------
- base_q = Q(fadan_pingtai=1) # 平台订单
-
- if dingdan_id:
- q_conditions = base_q & Q(dingdan_id=dingdan_id)
- else:
- q_conditions = base_q
- if leixing is not None:
- try:
- leixing_int = int(leixing)
- q_conditions &= Q(leixing_id=leixing_int)
- except (ValueError, TypeError):
- pass
- if zhuangtai and zhuangtai != 'all':
- try:
- status_list = [int(s.strip()) for s in zhuangtai.split(',') if s.strip()]
- if status_list:
- q_conditions &= Q(zhuangtai__in=status_list)
- except (ValueError, TypeError):
- pass
- if dashou_id:
- q_conditions &= Q(jiedan_dashou_id=dashou_id)
- if laoban_id:
- q_conditions &= Q(pingtai_kuozhan__laoban_id=laoban_id)
- if date:
- q_conditions &= Q(create_time__date=date) # 按日期筛选
-
- # ---------- 统计(仅按发单平台) ----------
- stats = Dingdan.objects.filter(fadan_pingtai=1).aggregate(
- total_orders=Count('id'),
- completed_orders=Count('id', filter=Q(zhuangtai=3)),
- refund_orders=Count('id', filter=Q(zhuangtai=5)),
- pending_orders=Count('id', filter=Q(zhuangtai__in=[4,8]))
- )
-
- # ---------- 状态计数(所有状态都返回) ----------
- status_counts = {
- 'all': Dingdan.objects.filter(fadan_pingtai=1).count(),
- '1,7': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai__in=[1,7]).count(),
- '2': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=2).count(),
- '8': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=8).count(),
- '3': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=3).count(),
- '4': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=4).count(),
- '5': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=5).count(),
- '6': Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=6).count(),
- }
-
- # ---------- 分页查询 ----------
- total_count = Dingdan.objects.filter(q_conditions).count()
-
- orders_qs = Dingdan.objects.filter(q_conditions).select_related(
- 'pingtai_kuozhan' # 预加载平台扩展表
- ).only(
- 'dingdan_id', 'jieshao', 'zhuangtai', 'leixing_id', 'fadan_pingtai',
- 'jine', 'tupian', 'create_time', 'nicheng', 'jiedan_dashou_id',
- 'pingtai_kuozhan__laoban_id'
- ).order_by('-create_time')[(page-1)*page_size : page*page_size]
-
- # ---------- 获取老板昵称 ----------
- laoban_ids = [o.pingtai_kuozhan.laoban_id for o in orders_qs if hasattr(o, 'pingtai_kuozhan') and o.pingtai_kuozhan.laoban_id]
- nickname_map = {}
- if laoban_ids:
- users = UserMain.objects.filter(yonghuid__in=laoban_ids).select_related('boss_profile').only(
- 'yonghuid', 'boss_profile__nickname'
- )
- for user in users:
- nickname_map[user.yonghuid] = user.boss_profile.nickname or '' if hasattr(user, 'boss_profile') else ''
-
- result_list = []
- for order in orders_qs:
- ext = getattr(order, 'pingtai_kuozhan', None)
- laoban_id = ext.laoban_id if ext else ''
- youxi_nicheng = nickname_map.get(laoban_id, order.nicheng or '')
- result_list.append({
- 'dingdan_id': order.dingdan_id or '',
- 'jieshao': order.jieshao or '',
- 'zhuangtai': order.zhuangtai,
- 'leixing': order.leixing_id,
- 'fadanpingtai': order.fadan_pingtai,
- 'jiage': float(order.jine) if order.jine else 0.00,
- 'tupian_url': order.tupian or '',
- 'fadanshijian': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else '',
- 'youxi_nicheng': youxi_nicheng
- })
-
- response_data = {
- 'stats': {
- 'totalOrders': stats['total_orders'] or 0,
- 'completedOrders': stats['completed_orders'] or 0,
- 'refundOrders': stats['refund_orders'] or 0,
- 'pendingOrders': stats['pending_orders'] or 0
- },
- 'statusCounts': status_counts,
- 'list': result_list,
- 'total': total_count,
- 'page': page,
- 'pageSize': page_size
- }
-
- logger.info(f"平台订单接口总耗时: {time.time()-start_total:.3f}s")
- return Response({'code': 0, 'data': response_data})
-
-
-
-
-
-
-
-logger = logging.getLogger(__name__)
-
-class KefuGetShangjiaOrderListView(APIView):
- """
- 客服获取商家订单列表接口(发单平台=2)
- 请求参数:
- phone: 客服账号
- page: 页码
- pageSize: 每页条数
- dingdan_id: 订单ID(精确搜索)
- shangjia_id: 商家ID(搜索商家扩展表中的商家ID)
- dashou_id: 打手ID(搜索主表中的接单打手)
- leixing: 商品类型ID
- zhuangtai: 状态(多个用逗号分隔,如 "1,7")
- date: 日期(YYYY-MM-DD),筛选订单创建时间
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- start_total = time.time()
-
- phone = request.data.get('phone', '').strip()
- page = int(request.data.get('page', 1))
- page_size = int(request.data.get('pageSize', 20))
-
- dingdan_id = request.data.get('dingdan_id', '').strip()
- shangjia_id = request.data.get('shangjia_id', '').strip()
- dashou_id = request.data.get('dashou_id', '').strip()
- leixing = request.data.get('leixing')
- zhuangtai = request.data.get('zhuangtai', '')
- date = request.data.get('date', '').strip() # 格式 YYYY-MM-DD
-
- if not phone:
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- current_user = request.user
- # 1. 获取前端参数
- username = request.data.get('phone', '').strip()
- if not username:
- return Response({'code': 400, 'msg': '缺少username'})
-
- # 2. 公共权限校验
- kefu, permissions = verify_kefu_permission(request, username)
- if kefu is None:
- return permissions
-
- # 3. 检查商家订单管理权限(使用 002ac,如果没有可改用 002ab)
- if '002ac' not in permissions:
- return Response({'code': 403, 'msg': '您无权查看商家订单列表'})
-
- # ---------- 查询条件 ----------
- base_q = Q(fadan_pingtai=2) # 商家订单
-
- if dingdan_id:
- q_conditions = base_q & Q(dingdan_id=dingdan_id)
- else:
- q_conditions = base_q
- if leixing is not None:
- try:
- leixing_int = int(leixing)
- q_conditions &= Q(leixing_id=leixing_int)
- except (ValueError, TypeError):
- pass
- if zhuangtai and zhuangtai != 'all':
- try:
- status_list = [int(s.strip()) for s in zhuangtai.split(',') if s.strip()]
- if status_list:
- q_conditions &= Q(zhuangtai__in=status_list)
- except (ValueError, TypeError):
- pass
- if dashou_id:
- q_conditions &= Q(jiedan_dashou_id=dashou_id)
- if shangjia_id:
- q_conditions &= Q(shangjia_kuozhan__shangjia_id=shangjia_id) # 关联商家扩展表
- if date:
- q_conditions &= Q(create_time__date=date) # 按日期筛选
-
- # ---------- 统计(仅按发单平台) ----------
- stats = Dingdan.objects.filter(fadan_pingtai=2).aggregate(
- total_orders=Count('id'),
- completed_orders=Count('id', filter=Q(zhuangtai=3)),
- refund_orders=Count('id', filter=Q(zhuangtai=5)),
- pending_orders=Count('id', filter=Q(zhuangtai__in=[4,8]))
- )
-
- # ---------- 状态计数(所有状态都返回) ----------
- status_counts = {
- 'all': Dingdan.objects.filter(fadan_pingtai=2).count(),
- '1,7': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai__in=[1,7]).count(),
- '2': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=2).count(),
- '8': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=8).count(),
- '3': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=3).count(),
- '4': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=4).count(),
- '5': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=5).count(),
- '6': Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=6).count(),
- }
-
- # ---------- 分页查询 ----------
- total_count = Dingdan.objects.filter(q_conditions).count()
-
- orders_qs = Dingdan.objects.filter(q_conditions).select_related(
- 'shangjia_kuozhan' # 预加载商家扩展表
- ).only(
- 'dingdan_id', 'jieshao', 'zhuangtai', 'leixing_id', 'fadan_pingtai',
- 'jine', 'tupian', 'create_time', 'nicheng', 'jiedan_dashou_id',
- 'shangjia_kuozhan__shangjia_id' # 商家ID
- ).order_by('-create_time')[(page-1)*page_size : page*page_size]
-
- # ---------- 获取商家昵称(如果有) ----------
- # 假设商家昵称在 UserShangjia 扩展表中,通过 shangjia_id 关联 UserMain,再关联 UserShangjia
- # 此处简化,实际可根据需要查询
- shangjia_ids = [o.shangjia_kuozhan.shangjia_id for o in orders_qs if hasattr(o, 'shangjia_kuozhan') and o.shangjia_kuozhan.shangjia_id]
- nickname_map = {}
- if shangjia_ids:
- users = UserMain.objects.filter(yonghuid__in=shangjia_ids).select_related('shop_profile').only(
- 'yonghuid', 'shop_profile__nicheng'
- )
- for user in users:
- nickname_map[user.yonghuid] = user.shop_profile.nicheng or '' if hasattr(user, 'shop_profile') else ''
-
- result_list = []
- for order in orders_qs:
- ext = getattr(order, 'shangjia_kuozhan', None)
- shangjia_id = ext.shangjia_id if ext else ''
- # 游戏昵称从订单主表的 nicheng 取,或从老板昵称取(商家订单可能没有老板昵称,保持简单)
- youxi_nicheng = order.nicheng or ''
- result_list.append({
- 'dingdan_id': order.dingdan_id or '',
- 'jieshao': order.jieshao or '',
- 'zhuangtai': order.zhuangtai,
- 'leixing': order.leixing_id,
- 'fadanpingtai': order.fadan_pingtai,
- 'jiage': float(order.jine) if order.jine else 0.00,
- 'tupian_url': order.tupian or '',
- 'fadanshijian': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else '',
- 'youxi_nicheng': youxi_nicheng
- })
-
- response_data = {
- 'stats': {
- 'totalOrders': stats['total_orders'] or 0,
- 'completedOrders': stats['completed_orders'] or 0,
- 'refundOrders': stats['refund_orders'] or 0,
- 'pendingOrders': stats['pending_orders'] or 0
- },
- 'statusCounts': status_counts,
- 'list': result_list,
- 'total': total_count,
- 'page': page,
- 'pageSize': page_size
- }
-
- logger.info(f"商家订单接口总耗时: {time.time()-start_total:.3f}s")
- return Response({'code': 0, 'data': response_data})'''
-
@@ -10336,84 +8352,7 @@ class KefuRejectRefundView(APIView):
-'''class KefuRejectRefundView(APIView):
- """
- 客服拒绝退款接口
- 请求:POST /yonghu/kefujjtk
- 参数:{
- "phone": "客服手机号",
- "dingdan_id": "订单ID",
- "jujue_liyou": "拒绝理由(可选)"
- }
- 认证:JWT
- 返回:code=0 成功
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
- def post(self, request):
- # 1. 获取参数
- phone = request.data.get('phone', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
- jujue_liyou = request.data.get('jujue_liyou', '').strip()
-
- if not phone or not dingdan_id:
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 客服身份验证
- current_user = request.user
- if getattr(current_user, 'phone', '') != phone:
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- if current_user.user_type != 'kefu':
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- try:
- kefu = current_user.kefu_profile
- except ObjectDoesNotExist:
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- if kefu.zhuangtai != 1:
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- # 3. 查询订单
- try:
- order = Dingdan.objects.get(dingdan_id=dingdan_id)
- except Dingdan.DoesNotExist:
- return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND)
-
- # 4. 验证订单状态为退款审核中(4)
- if order.zhuangtai != 4:
- return Response({'code': 400, 'msg': '订单状态不是退款审核中'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 5. 使用事务更新
- with transaction.atomic():
- # 5.1 更新订单状态为退款失败(6)
- order.zhuangtai = 6
- order.clkf = current_user.yonghuid
- if jujue_liyou:
- order.tkly = jujue_liyou # 存储拒绝理由
- order.save()
-
- # 5.2 更新退款记录表(如果存在)
- try:
- refund_record = Tuikuanjilu.objects.get(dingdan_id=dingdan_id)
- refund_record.sqzhuangtai = 2 # 2表示拒绝
- refund_record.chuliid = phone
- refund_record.save()
- logger.info(f"拒绝退款:更新退款记录表,订单 {dingdan_id}")
- except Tuikuanjilu.DoesNotExist:
- pass
- except Exception as e:
- logger.error(f"拒绝退款更新退款记录异常: {str(e)}", exc_info=True)
-
- # 5.3 更新客服统计数据
- kefu.jinrichuli += 1
- kefu.jinyuechuli += 1
- kefu.zongchuli += 1
- kefu.save()
-
- return Response({'code': 0, 'msg': '拒绝退款成功'})'''
@@ -11095,6 +9034,7 @@ from houtai.utils import verify_kefu_permission
logger = logging.getLogger(__name__)
class KefuUpdateDashouView(APIView):
+ permission_classes = [IsAuthenticated]
"""
客服修改打手信息接口
支持操作:
@@ -11365,134 +9305,7 @@ class KefuUpdateDashouView(APIView):
return Response({'code': 500, 'msg': '服务器内部错误'})
-'''class KefuUpdateDashouView(APIView):
- """
- 客服修改打手信息接口
- 请求:POST /yonghu/kefuxgds
- 参数:{
- "phone": "客服手机号",
- "uid": "打手ID",
- // 以下字段可选,只传需要修改的字段
- "yue": 100.00,
- "jifen": 200,
- "yajin": 50.00,
- "zaixianzhuangtai": 0/1,
- "zhanghaozhuangtai": 0/1,
- "zhuangtai": 1/2
- }
- 认证:JWT
- 返回:code=0 成功
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
- def post(self, request):
- # 1. 获取参数
- phone = request.data.get('phone', '').strip()
- uid = request.data.get('uid', '').strip()
-
- if not phone or not uid:
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- # 2. 客服身份验证
- current_user = request.user
- if getattr(current_user, 'phone', '') != phone:
- logger.warning(f"手机号不匹配: {phone}")
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- if current_user.user_type != 'kefu':
- logger.warning(f"用户类型非客服: {current_user.yonghuid}")
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- try:
- kefu = current_user.kefu_profile
- except ObjectDoesNotExist:
- logger.warning(f"客服扩展表不存在: {current_user.yonghuid}")
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- if kefu.zhuangtai != 1:
- logger.warning(f"客服账号已禁用: {current_user.yonghuid}")
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- # 3. 查询打手主表和扩展表
- try:
- user_main = UserMain.objects.select_related('dashou_profile').get(
- yonghuid=uid
- )
- except UserMain.DoesNotExist:
- return Response({'code': 404, 'msg': '打手不存在'}, status=status.HTTP_404_NOT_FOUND)
-
- try:
- dashou = user_main.dashou_profile
- except ObjectDoesNotExist:
- return Response({'code': 400, 'msg': '该用户未注册打手身份'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 4. 收集要修改的字段
- update_fields = {}
- editable_fields = [
- 'yue', 'jifen', 'yajin',
- 'zaixianzhuangtai', 'zhanghaozhuangtai', 'zhuangtai'
- ]
-
- for field in editable_fields:
- if field in request.data:
- value = request.data.get(field)
- # 类型转换
- if field in ['yue', 'yajin']:
- try:
- value = Decimal(str(value))
- except:
- return Response({'code': 400, 'msg': f'{field} 格式错误'}, status=status.HTTP_400_BAD_REQUEST)
- elif field in ['jifen']:
- try:
- value = int(value)
- except:
- return Response({'code': 400, 'msg': f'{field} 必须为整数'}, status=status.HTTP_400_BAD_REQUEST)
- elif field in ['zaixianzhuangtai', 'zhanghaozhuangtai', 'zhuangtai']:
- try:
- value = int(value)
- if field == 'zhuangtai' and value not in [1, 2]:
- return Response({'code': 400, 'msg': f'{field} 值无效'}, status=status.HTTP_400_BAD_REQUEST)
- if field != 'zhuangtai' and value not in [0, 1]:
- return Response({'code': 400, 'msg': f'{field} 值无效'}, status=status.HTTP_400_BAD_REQUEST)
- except:
- return Response({'code': 400, 'msg': f'{field} 必须为数字'}, status=status.HTTP_400_BAD_REQUEST)
- update_fields[field] = value
-
- if not update_fields:
- return Response({'code': 400, 'msg': '没有要修改的字段'}, status=status.HTTP_400_BAD_REQUEST)
-
- # 5. 使用事务更新并记录修改日志
- with transaction.atomic():
- # 保存旧值用于记录(可选)
- old_values = {field: getattr(dashou, field) for field in update_fields}
-
- for field, value in update_fields.items():
- setattr(dashou, field, value)
- dashou.save()
-
- # 记录修改日志
- try:
- # 构建修改描述
- desc_parts = []
- for field in update_fields:
- old = old_values.get(field)
- new = update_fields[field]
- desc_parts.append(f"{field}: {old} -> {new}")
- desc = "; ".join(desc_parts)
-
- Xiugaijilu.objects.create(
- yonghuid=uid,
- xiugaiid=phone, # 客服手机号
- leixing=2, # 打手类型
- qitashuoming=desc,
- create_time=timezone.now()
- )
- except Exception as e:
- logger.error(f"记录修改日志失败: {e}")
- # 不影响主流程,但记录错误
-
- return Response({'code': 0, 'msg': '修改成功'})'''
@@ -11770,183 +9583,7 @@ class KefuGetOrderDetailView(APIView):
-'''import time
-import logging
-from django.db import models
-from django.core.exceptions import ObjectDoesNotExist
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework import status
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.parsers import JSONParser
-from yonghu.models import UserMain, UserKefu
-from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia, Dashoutupian, Tuikuanjilu
-
-logger = logging.getLogger(__name__)
-
-class KefuGetOrderDetailView(APIView):
- """
- 客服获取订单详情接口
- 请求:POST /yonghu/kefuhqddxq
- 参数:{"phone": "客服手机号", "dingdan_id": "订单ID"}
- 认证:JWT
- 返回:code=0 + 订单详情数据
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
-
- def post(self, request):
- start_time = time.time()
-
- # 1. 获取参数
- phone = request.data.get('phone', '').strip()
- dingdan_id = request.data.get('dingdan_id', '').strip()
-
- if not phone or not dingdan_id:
- return Response(
- {'code': 401, 'msg': '认证失败'},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 2. 身份验证
- current_user = request.user
- if getattr(current_user, 'phone', '') != phone:
- logger.warning(f"手机号不匹配: {phone}")
- return Response(
- {'code': 401, 'msg': '认证失败'},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- if current_user.user_type != 'kefu':
- logger.warning(f"用户类型非客服: {current_user.yonghuid}")
- return Response(
- {'code': 401, 'msg': '认证失败'},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- try:
- kefu = current_user.kefu_profile
- except ObjectDoesNotExist:
- logger.warning(f"客服扩展表不存在: {current_user.yonghuid}")
- return Response(
- {'code': 401, 'msg': '认证失败'},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- if kefu.zhuangtai != 1:
- logger.warning(f"客服账号已禁用: {current_user.yonghuid}")
- return Response(
- {'code': 401, 'msg': '认证失败'},
- status=status.HTTP_401_UNAUTHORIZED
- )
-
- # 3. 查询订单
- try:
- # 使用 select_related 预加载扩展表,避免后续 N+1 查询
- dingdan_obj = Dingdan.objects.select_related(
- 'pingtai_kuozhan',
- 'shangjia_kuozhan'
- ).only(
- 'dingdan_id', 'zhuangtai', 'fadan_pingtai', 'jine', 'dashou_fencheng',
- 'jiedan_dashou_id', 'dashou_liuyan', 'zhiding_id', 'shangpin_id',
- 'leixing_id', 'tupian', 'jieshao', 'beizhu', 'tkly', 'nicheng',
- 'clkf', 'create_time', 'update_time'
- ).get(dingdan_id=dingdan_id)
-
- except Dingdan.DoesNotExist:
- logger.warning(f"订单不存在: {dingdan_id}")
- return Response(
- {'code': 404, 'msg': '订单不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
-
- # 4. 构建基础响应数据(主表字段)
- response_data = {
- 'dingdan_id': dingdan_obj.dingdan_id or '',
- 'zhuangtai': dingdan_obj.zhuangtai or 0,
- 'fadanpingtai': dingdan_obj.fadan_pingtai or 0,
- 'jiage': float(dingdan_obj.jine) if dingdan_obj.jine else 0.00,
- 'dashou_fencheng': float(dingdan_obj.dashou_fencheng) if dingdan_obj.dashou_fencheng else 0.00,
- 'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '',
- 'dashou_liuyan': dingdan_obj.dashou_liuyan or '',
- 'zhiding_id': dingdan_obj.zhiding_id or '',
- 'shangpin_id': dingdan_obj.shangpin_id or '',
- 'leixing': dingdan_obj.leixing_id or 0,
- 'tupian_url': dingdan_obj.tupian or '',
- 'jieshao': dingdan_obj.jieshao or '',
- 'beizhu': dingdan_obj.beizhu or '',
- 'tkly': dingdan_obj.tkly or '',
- 'nicheng': dingdan_obj.nicheng or '',
- 'clkf': dingdan_obj.clkf or '',
- 'chuangjianshijian': dingdan_obj.create_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan_obj.create_time else '',
- 'genggaishijian': dingdan_obj.update_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan_obj.update_time else '',
- }
-
- # 5. 根据发单平台添加扩展表数据
- fadan_pingtai = dingdan_obj.fadan_pingtai
-
- if fadan_pingtai == 1:
- # 平台订单扩展
- if hasattr(dingdan_obj, 'pingtai_kuozhan') and dingdan_obj.pingtai_kuozhan:
- pingtai = dingdan_obj.pingtai_kuozhan
- response_data['pingtai_kuozhan'] = {
- 'laoban_id': pingtai.laoban_id or '',
- 'laoban_pingjia': pingtai.laoban_pingjia or '',
- }
- else:
- response_data['pingtai_kuozhan'] = {
- 'laoban_id': '',
- 'laoban_pingjia': '',
- }
-
- elif fadan_pingtai == 2:
- # 商家订单扩展
- if hasattr(dingdan_obj, 'shangjia_kuozhan') and dingdan_obj.shangjia_kuozhan:
- shangjia = dingdan_obj.shangjia_kuozhan
- response_data['shangjia_kuozhan'] = {
- 'shangjia_id': shangjia.shangjia_id or '',
- 'sjnicheng': shangjia.sjnicheng or '',
- 'shangjia_pingjia': shangjia.shangjia_pingjia or '',
- 'cfliyou': shangjia.cfliyou or '',
- 'sqzhuangtai': shangjia.sqzhuangtai if shangjia.sqzhuangtai is not None else 0,
- 'bhliyou': shangjia.bhliyou or '',
- }
- else:
- response_data['shangjia_kuozhan'] = {
- 'shangjia_id': '',
- 'sjnicheng': '',
- 'shangjia_pingjia': '',
- 'cfliyou': '',
- 'sqzhuangtai': 0,
- 'bhliyou': '',
- }
-
- # 6. 查询打手提交的图片
- try:
- dashou_images = Dashoutupian.objects.filter(
- dingdan_id=dingdan_id
- ).values_list('tupian', flat=True)
- response_data['dashou_images'] = [img for img in dashou_images if img]
- except Exception as e:
- logger.warning(f"查询打手图片失败: {str(e)}")
- response_data['dashou_images'] = []
-
- # 7. 查询退款理由(从退款记录表取最新一条)
- try:
- tuikuan_record = Tuikuanjilu.objects.filter(
- dingdan_id=dingdan_id
- ).order_by('-create_time').first()
- if tuikuan_record and tuikuan_record.liyou:
- response_data['tuikuan_liyou'] = tuikuan_record.liyou
- else:
- response_data['tuikuan_liyou'] = ''
- except Exception as e:
- logger.warning(f"查询退款记录失败: {str(e)}")
- response_data['tuikuan_liyou'] = ''
-
- logger.info(f"订单详情接口耗时: {time.time()-start_time:.3f}s")
- return Response({'code': 0, 'data': response_data})'''
@@ -12376,137 +10013,7 @@ class KefuForceCompleteView(APIView):
-'''class KefuWithdrawListView(APIView):
- """
- 客服获取提现审核列表接口
- 请求:POST /yonghu/kefu_txsh_list
- 参数:{
- "phone": "客服手机号",
- "page": 1,
- "page_size": 20,
- "status": 1(待审核)/2(已处理),
- "type": 1(打手)/2(管事) 可选,
- "payment": 1(微信)/2(支付宝) 可选,
- "search_uid": "用户ID搜索" 可选,
- "date": "YYYY-MM-DD" 可选 按创建日期筛选
- }
- 认证:JWT
- 返回:{
- "code": 0,
- "data": {
- "list": [提现记录],
- "total": 总数,
- "stats": {
- "total": 总记录,
- "awaiting": 待审核数,
- "processed": 已处理数
- }
- }
- }
- """
- permission_classes = [IsAuthenticated]
- parser_classes = [JSONParser]
- def post(self, request):
- phone = request.data.get('phone', '').strip()
- try:
- page = int(request.data.get('page', 1))
- page_size = int(request.data.get('page_size', 20))
- except ValueError:
- return Response({'code': 400, 'msg': '分页参数错误'}, status=status.HTTP_400_BAD_REQUEST)
-
- status_filter = request.data.get('status') # 1待审核 2已处理
- type_filter = request.data.get('type')
- payment_filter = request.data.get('payment')
- search_uid = request.data.get('search_uid', '').strip()
- date_filter = request.data.get('date', '').strip()
-
- if not phone:
- return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED)
-
- # 客服身份验证
- current_user = request.user
- # 1. 获取前端参数
- username = request.data.get('phone', '').strip()
- if not username:
- return Response({'code': 400, 'msg': '缺少username'})
-
- # 2. 公共权限校验
- kefu, permissions = verify_kefu_permission(request, username)
- if kefu is None:
- return permissions
-
- # 3. 检查平台订单管理权限(002ab)
- if '005bb' not in permissions:
- return Response({'code': 403, 'msg': '您无权查看平台订单列表'})
-
- # 构建查询条件
- q = Q()
-
- if status_filter == 1:
- q &= Q(zhuangtai=1)
- elif status_filter == 2:
- q &= Q(zhuangtai__in=[2, 3])
-
- if type_filter:
- try:
- q &= Q(leixing=int(type_filter))
- except ValueError:
- pass
-
- if payment_filter:
- try:
- q &= Q(fangshi=int(payment_filter))
- except ValueError:
- pass
-
- if search_uid:
- q &= Q(yonghuid=search_uid)
-
- if date_filter:
- q &= Q(create_time__date=date_filter)
-
- # 统计信息(整体,不考虑分页)
- stats = {
- 'total': Tixianjilu.objects.filter(q).count(),
- 'awaiting': Tixianjilu.objects.filter(q & Q(zhuangtai=1)).count(),
- 'processed': Tixianjilu.objects.filter(q & Q(zhuangtai__in=[2, 3])).count(),
- }
-
- # 分页查询
- total = Tixianjilu.objects.filter(q).count()
- queryset = Tixianjilu.objects.filter(q).order_by('create_time') # 按申请时间升序
- records = queryset[(page-1)*page_size : page*page_size]
-
- # 构建返回列表
- list_data = []
- for item in records:
- list_data.append({
- 'id': item.id,
- 'yonghuid': item.yonghuid,
- 'avatar': item.avatar or '',
- 'phone': item.phone or '',
- 'nicheng': item.nicheng or '',
- 'leixing': item.leixing,
- 'zhifu': item.zhifu or '',
- 'skzhanghao': item.skzhanghao or '',
- 'jine': str(item.jine) if item.jine else '0.00',
- 'zhuangtai': item.zhuangtai,
- 'fangshi': item.fangshi,
- 'shenheid': item.shenheid or '',
- 'bhliyou': item.bhliyou or '',
- 'create_time': item.create_time,
- 'update_time': item.update_time,
- })
-
- return Response({
- 'code': 0,
- 'data': {
- 'list': list_data,
- 'total': total,
- 'stats': stats
- }
- })'''
from django.db.models import Q, Sum