Files
Django/backend/views/kefu.py

1487 lines
63 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import hmac
import threading
import traceback
import uuid
import hashlib
import xmltodict
import time
import logging
import requests
import os
import secrets
import random
import string
from calendar import monthrange
from decimal import Decimal
from datetime import date, datetime, timedelta
from django.utils import timezone
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.db import models, transaction, IntegrityError
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
from gvsdsdk.fluent import db, func, FQ
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
from django.contrib.auth.hashers import make_password
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
# 工具类导入
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
from backend.utils import (
update_dashou_daily_by_action,
update_shangjia_daily
)
from jituan.services.club_context import filter_club_char_field, filter_queryset_by_club, filter_user_related_by_club, filter_user_qs_by_club, orders_for_request
from jituan.services.catalog_scope import block_non_group_catalog_scope
from jituan.services.club_penalty import (
filter_penalty_qs, resolve_penalty_club_id, filter_gsfenhong_qs,
forbid_penalty_out_of_scope, resolve_penalty_record_club_id,
)
from jituan.services.club_user_access import forbid_if_user_out_of_scope, list_response_meta
from shop.utils import update_dianpu_daily_stat
from products.utils import update_shangpin_daily_stat
from orders.notice_tasks import dingdan_guangbo
from ..utils import (
verify_kefu_permission,
PERMISSION_TO_SHENFEN,
SHENFEN_PROFILE_MAP,
has_fadan_view_permission,
has_merchant_order_permission,
check_fadan_permission,
pick_order_id,
pick_penalty_create_params,
write_xiugai_log,
XIUGAI_LEIXING_DASHOU,
XIUGAI_LEIXING_GUANSHI,
XIUGAI_LEIXING_SHANGJIA,
XIUGAI_LEIXING_ZUZHANG,
XIUGAI_LEIXING_LABEL,
)
# models 集中导入
## gvsdsdk RBAC 模型(使用 PascalCase 字段名,匹配实际数据库表结构)
from gvsdsdk.models import Role, Permission, RolePermission, UserRole
## backend
from backend.models import WithdrawalDailyStats
## users
from users.models import (
UserGuanshi, UserBoss, UserZuzhang,
UserKefu, UserDashou, Xiugaijilu, UserShangjia, UserShenheguan
)
from users.business_models import User
## orders
from orders.models import (
CommissionRate, PlayerDeliveryImage, PenaltyRecord,
OrderPlayerHistory, Order, RefundRecord,
MerchantOrderExt, Penalty, PenaltyAppealImage, PenaltyBonus
)
## shop
from shop.models import Dianpu, DianpuMorenPeizhi, YonghuPingzheng, ShangpinLeixingDianpu, DianpuShangpinShenheShezhi
## products
from products.models import (
Shangpin, ShangpinLeixing, ShangpinZhuanqu, Huiyuan,
DuociFenhong, Czjilu, Huiyuangoumai, Gsfenhong
)
## config
from config.models import (
WithdrawConfig, ShangjiaLianjie, AccountPermission,
TixianQuotaDefault,
DailyIncomeStat, DailyPayoutStat, PopupPage, PopupConfig, PopupImage
)
## rank
from rank.models import (
KaoheguanBankuai, Bankuai, Chenghao, KaoheCishuFeiyong,
YonghuChenghao, ShenheJilu, KaoheJiluFeiyong, KaoheJujueJilu
)
# 序列化器
from ..serializers import PopupPageSerializer
# 全局常量、日志对象
logger = logging.getLogger('houtai')
SHOP_PRODUCT_PERMS = ['1199ab', '1199abc', '1199abd']
# 与前端约定的聊天权限码(原位于 shops.py按功能组归类到客服模块
CHAT_PERM_CODES = ['abca1', 'baac2', 'cabc3', 'cb3a2', 'bcaa4']
class KefuGetDashouListView(APIView):
"""
客服获取打手列表接口(仅打手类型)
需要拥有以下任一权限001aa,001bb,001cc,001dd,001ee,001ff,001gg
请求POST /yonghu/kefuhqdslb
参数:
username: 客服手机号(用于权限校验)
keyword: 搜索关键词可选搜索用户ID或昵称
page: 页码从1开始
page_size: 每页数量
返回:
code: 0
data: {
list: [打手对象],
has_more: bool,
total: int, // 总打手数
valid_dashou_count: int // 有效打手数(有有效会员)
}
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
# 1. 获取参数(前端字段为 username
username = request.data.get('username', '').strip()
keyword = request.data.get('keyword', '').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': '分页参数错误'})
if not username:
return Response({'code': 401, 'msg': '认证失败'})
# 2. 公共权限校验(验证客服身份,获取权限列表)
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions # 直接返回错误响应
# 3. 检查是否拥有打手管理相关权限(至少一个)
required_perms = {'001aa', '001bb', '001cc', '001dd', '001ee', '001ff', '001gg'}
if not any(p in permissions for p in required_perms):
return Response({'code': 403, 'msg': '您没有权限查看打手列表'})
# 4. 构建打手查询集(仅打手类型)
dashou_qs = filter_user_related_by_club(
UserDashou.query.select_related('user').all(), request,
)
# 关键词筛选用户ID或昵称
if keyword:
dashou_qs = dashou_qs.filter(
Q(user__UserUID__icontains=keyword) |
Q(nicheng__icontains=keyword)
)
# 5. 统计打手总数
total_dashou = dashou_qs.count()
# 6. 统计有效打手数(有会员且未过期)
dashou_user_ids = dashou_qs.values_list('user__UserUID', flat=True)
now = timezone.now()
hy_qs = filter_queryset_by_club(Huiyuangoumai.query.all(), request)
valid_dashou_count = hy_qs.filter(
yonghu_id__in=dashou_user_ids,
huiyuan_zhuangtai=1,
daoqi_time__gt=now,
).values('yonghu_id').distinct().count()
# 7. 分页
offset = (page - 1) * page_size
dashou_list = dashou_qs.order_by('-CreateTime')[offset:offset + page_size]
# 8. 构建返回数据
result = []
for dashou in dashou_list:
user = dashou.user
result.append({
'yonghuid': user.UserUID,
'avatar': user.Avatar or '',
'zaixianzhuangtai': dashou.zaixianzhuangtai,
'zhanghaozhuangtai': dashou.zhanghaozhuangtai,
'nicheng': dashou.nicheng or '',
'zhuangtai': dashou.zhuangtai,
})
has_more = (offset + len(dashou_list)) < total_dashou
meta = list_response_meta(request)
return Response({
'code': 0,
'club_id': meta['club_id'],
'scope': meta['scope'],
'data': {
'list': result,
'has_more': has_more,
'total': total_dashou,
'valid_dashou_count': valid_dashou_count,
}
})
class KefuGetDashouDetailView(APIView):
"""
客服获取打手详情接口
请求POST /yonghu/kefuhqdsxq
参数:{
"username": "客服手机号",
"uid": "打手ID (yonghuid)"
}
认证JWT
返回code=0 + 打手信息 + 会员列表 + 所有会员类型 + 客服权限列表
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
# 1. 获取参数
username = request.data.get('phone', '').strip()
uid = request.data.get('uid', '').strip()
if not username or not uid:
return Response({'code': 400, 'msg': '缺少参数'})
# 2. 公共权限校验(验证客服身份,获取权限列表)
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
# 3. 检查是否拥有打手管理相关权限(至少一个)
required_perms = {'001aa', '001bb', '001cc', '001dd', '001ee', '001ff', '001gg'}
if not any(p in permissions for p in required_perms):
return Response({'code': 403, 'msg': '您没有权限查看打手详情'})
# 4. 查询打手主表和扩展表
try:
user_main = User.query.select_related('DashouProfile').get(
UserUID=uid
)
except User.DoesNotExist:
return Response({'code': 404, 'msg': '打手不存在'})
denied = forbid_if_user_out_of_scope(request, user_main)
if denied:
return denied
try:
dashou = user_main.DashouProfile
except ObjectDoesNotExist:
# 用户存在但未注册打手身份,返回基本信息
user_info = {
'yonghuid': user_main.UserUID,
'avatar': user_main.Avatar or '',
'zaixianzhuangtai': 0,
'zhanghaozhuangtai': 0,
'zhuangtai': 0,
'nicheng': '',
'chenghao': '',
'dianhua': '',
'wechat': '',
'phone': user_main.Phone or '',
'CreateTime': user_main.UserCreateTime,
'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '',
'ip': user_main.IP or '',
'jiedanzongliang': 0,
'chengjiaozongliang': 0,
'tuikuanliang': 0,
'jinrijiedan': 0,
'yue': 0.00,
'zonge': 0.00,
'jifen': 0,
'yajin': 0.00,
'jinrishouyi': 0.00,
'jinyueshouyi': 0.00,
'yaoqingren': '',
'jieshao': '',
}
member_list = []
# 仍然需要返回 all_members 和 permissions
all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao'))
return Response({
'code': 0,
'data': {
'user_info': user_info,
'member_list': member_list,
'gift_member_list': [],
'all_members': all_members,
'permissions': permissions,
}
})
# 5. 构建打手信息
user_info = {
'yonghuid': user_main.UserUID,
'avatar': user_main.Avatar or '',
'zaixianzhuangtai': dashou.zaixianzhuangtai,
'zhanghaozhuangtai': dashou.zhanghaozhuangtai,
'zhuangtai': dashou.zhuangtai,
'nicheng': dashou.nicheng or '',
'chenghao': dashou.chenghao or '',
'dianhua': dashou.dianhua or '',
'wechat': dashou.wechat or '',
'phone': user_main.Phone or '',
'CreateTime': user_main.UserCreateTime,
'create_time': user_main.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user_main, 'UserCreateTime', None) else '',
'ip': user_main.IP or '',
'jiedanzongliang': dashou.jiedanzongliang,
'chengjiaozongliang': dashou.chengjiaozongliang,
'tuikuanliang': dashou.tuikuanliang,
'jinrijiedan': dashou.jinrijiedan,
'yue': float(dashou.yue) if dashou.yue else 0.00,
'dongjie_yue': float(getattr(dashou, 'dongjie_yue', 0) or 0),
'risk_fine_rate': float(getattr(dashou, 'risk_fine_rate', 0) or 0),
'risk_complaint_rate': float(getattr(dashou, 'risk_complaint_rate', 0) or 0),
'zonge': float(dashou.zonge) if dashou.zonge else 0.00,
'jifen': dashou.jifen,
'yajin': float(dashou.yajin) if dashou.yajin else 0.00,
'jinrishouyi': float(dashou.jinrishouyi) if dashou.jinrishouyi else 0.00,
'jinyueshouyi': float(dashou.jinyueshouyi) if dashou.jinyueshouyi else 0.00,
'yaoqingren': dashou.yaoqingren or '',
'jieshao': dashou.jieshao or '',
}
# 6. 查询打手已拥有的会员(未过期)
member_records = Huiyuangoumai.query.filter(
yonghu_id=uid
).order_by('-CreateTime')
# 批量预取会员信息
_huiyuan_ids = [r.huiyuan_id for r in member_records]
_huiyuan_map = {
h.huiyuan_id: h.jieshao
for h in Huiyuan.query.filter(huiyuan_id__in=_huiyuan_ids).only('huiyuan_id', 'jieshao')
}
member_list = []
for record in member_records:
# 检查是否过期
if hasattr(record, 'jiance_shifou_daoqi'):
is_expired = record.jiance_shifou_daoqi()
else:
is_expired = record.daoqi_time < timezone.now() if record.daoqi_time else True
if not is_expired:
huiyuan_name = _huiyuan_map.get(record.huiyuan_id, '未知会员')
member_list.append({
'id': record.id,
'huiyuan_name': huiyuan_name,
'daoqi_time': record.daoqi_time,
'is_active': record.huiyuan_zhuangtai == 1,
'huiyuan_id': record.huiyuan_id, # 用于删除操作
})
# 7. 查询所有会员类型(用于前端添加会员)
all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao'))
from jituan.services.gift_huiyuan import list_gift_huiyuan_for_admin
gift_member_list = list_gift_huiyuan_for_admin(uid)
return Response({
'code': 0,
'data': {
'user_info': user_info,
'member_list': member_list,
'gift_member_list': gift_member_list,
'all_members': all_members,
'permissions': permissions, # 返回当前客服的权限列表
}
})
class KefuUpdateDashouView(APIView):
"""
客服修改打手信息接口
支持操作:
- jian_yue / jia_yue : 减/加打手余额 (权限001aa/001bb)
- jian_yajin / jia_yajin : 减/加打手押金 (权限001aa/001bb)
- jian_jifen / jia_jifen : 减/加打手积分 (权限001cc/001dd)
- gai_zhanghaozhuangtai : 修改打手账号状态(封禁/解封) (权限001ee)
- gai_zaixianzhuangtai : 修改打手在线状态 (权限001ee)
- gai_zhuangtai : 修改打手接单状态(空闲/忙碌) (权限001ee)
- jia_huiyuan / jian_huiyuan : 加/减会员 (权限001ff/001gg)
- gai_chenghao : 修改打手称号(升级/降级金牌) (权限001hh)
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# 1. 获取前端参数
username = request.data.get('username', '').strip()
dashou_id = request.data.get('dashou_id', '').strip()
caozuo = request.data.get('caozuo', '').strip()
value = request.data.get('value') # 金额、积分、天数、称号等
huiyuan_id = request.data.get('huiyuan_id', '').strip()
if not username or not dashou_id or not caozuo:
return Response({'code': 400, 'msg': '参数不完整'})
# 2. 权限校验
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
# 3. 查询打手
try:
dashou_user = User.query.get(UserUID=dashou_id)
dashou_profile = dashou_user.DashouProfile
except User.DoesNotExist:
return Response({'code': 404, 'msg': '打手不存在'})
except UserDashou.DoesNotExist:
return Response({'code': 404, 'msg': '打手扩展信息缺失'})
with transaction.atomic():
try:
before = {
'yue': dashou_profile.yue,
'yajin': dashou_profile.yajin,
'jifen': dashou_profile.jifen,
'zhanghaozhuangtai': dashou_profile.zhanghaozhuangtai,
'zaixianzhuangtai': dashou_profile.zaixianzhuangtai,
'zhuangtai': dashou_profile.zhuangtai,
'chenghao': dashou_profile.chenghao,
}
# ---------- 余额操作 ----------
if caozuo == 'jian_yue':
if '001aa' not in permissions:
return Response({'code': 403, 'msg': '无权限减打手余额'})
amount = Decimal(str(value))
if dashou_profile.yue < amount:
return Response({'code': 400, 'msg': '余额不足'})
dashou_profile.yue -= amount
dashou_profile.save()
after = dashou_profile.yue
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
xiugaitijiao=after,
xiugaitijiaoq=before['yue'],
qitashuoming=(
f'【后台】减少打手可提现余额:{before["yue"]}元 → {after}元,'
f'减少{amount}打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '减余额成功', 'data': {'new_yue': str(after)}})
elif caozuo == 'jia_yue':
if '001bb' not in permissions:
return Response({'code': 403, 'msg': '无权限加打手余额'})
from jituan.services.group_finance_gate import deny_group_finance_exec
deny = deny_group_finance_exec(request.user, request)
if deny:
return deny
amount = Decimal(str(value))
dashou_profile.yue += amount
dashou_profile.save()
after = dashou_profile.yue
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
xiugaitijiao=after,
xiugaitijiaoq=before['yue'],
qitashuoming=(
f'【后台】增加打手可提现余额:{before["yue"]}元 → {after}元,'
f'增加{amount}打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '加余额成功', 'data': {'new_yue': str(after)}})
# ---------- 押金操作 ----------
elif caozuo == 'jian_yajin':
if '001aa' not in permissions:
return Response({'code': 403, 'msg': '无权限减打手押金'})
amount = Decimal(str(value))
if dashou_profile.yajin < amount:
return Response({'code': 400, 'msg': '押金不足'})
dashou_profile.yajin -= amount
dashou_profile.save()
after = dashou_profile.yajin
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
yajin=after,
yyajin=before['yajin'],
qitashuoming=(
f'【后台】减少打手押金:{before["yajin"]}元 → {after}元,'
f'减少{amount}打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '减押金成功', 'data': {'new_yajin': str(after)}})
elif caozuo == 'jia_yajin':
if '001bb' not in permissions:
return Response({'code': 403, 'msg': '无权限加打手押金'})
from jituan.services.group_finance_gate import deny_group_finance_exec
deny = deny_group_finance_exec(request.user, request)
if deny:
return deny
amount = Decimal(str(value))
dashou_profile.yajin += amount
dashou_profile.save()
after = dashou_profile.yajin
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
yajin=after,
yyajin=before['yajin'],
qitashuoming=(
f'【后台】增加打手押金:{before["yajin"]}元 → {after}元,'
f'增加{amount}打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '加押金成功', 'data': {'new_yajin': str(after)}})
# ---------- 积分操作 ----------
elif caozuo == 'jian_jifen':
if '001cc' not in permissions:
return Response({'code': 403, 'msg': '无权限减打手积分'})
jifen = int(value)
if dashou_profile.jifen < jifen:
return Response({'code': 400, 'msg': '积分不足'})
dashou_profile.jifen -= jifen
dashou_profile.save()
after = dashou_profile.jifen
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
jifen=after,
yjifen=before['jifen'],
qitashuoming=(
f'【后台】减少打手积分:{before["jifen"]}{after}'
f'减少{jifen}打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '减积分成功', 'data': {'new_jifen': after}})
elif caozuo == 'jia_jifen':
if '001dd' not in permissions:
return Response({'code': 403, 'msg': '无权限加打手积分'})
jifen = int(value)
dashou_profile.jifen += jifen
dashou_profile.save()
after = dashou_profile.jifen
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
jifen=after,
yjifen=before['jifen'],
qitashuoming=(
f'【后台】增加打手积分:{before["jifen"]}{after}'
f'增加{jifen}打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '加积分成功', 'data': {'new_jifen': after}})
# ---------- 状态修改 ----------
elif caozuo == 'gai_zhanghaozhuangtai':
if '001ee' not in permissions:
return Response({'code': 403, 'msg': '无权限修改打手账号状态'})
new_status = int(value)
if new_status not in [0, 1]:
return Response({'code': 400, 'msg': '状态值无效0=封禁,1=正常)'})
dashou_profile.zhanghaozhuangtai = new_status
dashou_profile.save()
status_label = '正常' if new_status == 1 else '封禁'
old_label = '正常' if before['zhanghaozhuangtai'] == 1 else '封禁'
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
qitashuoming=(
f'【后台】修改打手账号状态:{old_label}{status_label}'
f'打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '修改账号状态成功', 'data': {'new_zhanghaozhuangtai': new_status}})
elif caozuo == 'gai_zaixianzhuangtai':
if '001ee' not in permissions:
return Response({'code': 403, 'msg': '无权限修改打手在线状态'})
new_status = int(value)
if new_status not in [0, 1]:
return Response({'code': 400, 'msg': '状态值无效0=离线,1=在线)'})
dashou_profile.zaixianzhuangtai = new_status
dashou_profile.save()
status_label = '在线' if new_status == 1 else '离线'
old_label = '在线' if before['zaixianzhuangtai'] == 1 else '离线'
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
qitashuoming=(
f'【后台】修改打手在线状态:{old_label}{status_label}'
f'打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '修改在线状态成功', 'data': {'new_zaixianzhuangtai': new_status}})
elif caozuo == 'gai_zhuangtai':
if '001ee' not in permissions:
return Response({'code': 403, 'msg': '无权限修改打手接单状态'})
new_status = int(value)
if new_status not in [0, 1]:
return Response({'code': 400, 'msg': '状态值无效0=忙碌,1=空闲)'})
dashou_profile.zhuangtai = new_status
dashou_profile.save()
status_label = '空闲' if new_status == 1 else '忙碌'
old_label = '空闲' if before['zhuangtai'] == 1 else '忙碌'
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
qitashuoming=(
f'【后台】修改打手接单状态:{old_label}{status_label}'
f'打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '修改接单状态成功', 'data': {'new_zhuangtai': new_status}})
# ---------- 会员操作 ----------
elif caozuo == 'jia_huiyuan':
if '001ff' not in permissions and '000001' not in permissions:
return Response({'code': 403, 'msg': '无权限给打手加会员'})
if not huiyuan_id:
return Response({'code': 400, 'msg': '缺少会员ID'})
try:
huiyuan = Huiyuan.query.get(huiyuan_id=huiyuan_id)
except Huiyuan.DoesNotExist:
return Response({'code': 404, 'msg': '会员类型不存在'})
from jituan.services.club_user import get_user_club_id
from jituan.services.member_recharge import extend_member_entitlement_admin, get_club_huiyuan_row
player_club = get_user_club_id(dashou_user)
row = get_club_huiyuan_row(player_club, huiyuan_id, require_enabled=False)
days = int(value) if value else int(row.formal_days or 30) if row else 30
record, err = extend_member_entitlement_admin(
dashou_id, huiyuan_id, days, player_club,
)
if err:
return Response({'code': 400, 'msg': err})
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
huiyuants=days,
huiyuan_id=huiyuan_id,
qitashuoming=(
f'【后台】给打手添加正式会员会员ID={huiyuan_id}{huiyuan.jieshao}'
f'增加{days}天,到期时间={record.daoqi_time.strftime("%Y-%m-%d %H:%M:%S")}'
f'打手ID={dashou_id},操作人={kefu.user.Phone}(无支付单、无分红)'
),
)
return Response({'code': 0, 'msg': '加会员成功', 'data': {'new_daoqi': record.daoqi_time.strftime('%Y-%m-%d %H:%M:%S')}})
elif caozuo == 'jian_huiyuan':
if '001gg' not in permissions:
return Response({'code': 403, 'msg': '无权限给打手减会员'})
if not huiyuan_id:
return Response({'code': 400, 'msg': '缺少会员ID'})
from jituan.services.member_recharge import revoke_member_entitlement_admin
removed, err = revoke_member_entitlement_admin(dashou_id, huiyuan_id)
if err:
return Response({'code': 404, 'msg': err})
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
huiyuan_id=huiyuan_id,
qitashuoming=(
f'【后台】移除打手会员会员ID={huiyuan_id}'
f'打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '减会员成功'})
# ---------- 称号修改(金牌打手) ----------
elif caozuo == 'gai_chenghao':
if '001hh' not in permissions:
return Response({'code': 403, 'msg': '无权限修改打手称号'})
new_chenghao = str(value).strip()
if not new_chenghao:
return Response({'code': 400, 'msg': '称号不能为空'})
# 可限制合法值,这里只记录日志
dashou_profile.chenghao = new_chenghao
dashou_profile.save()
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_DASHOU,
qitashuoming=(
f'【后台】修改打手称号:{before["chenghao"] or ""}{new_chenghao}'
f'打手ID={dashou_id},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '修改称号成功', 'data': {'new_chenghao': new_chenghao}})
else:
return Response({'code': 400, 'msg': '未知操作类型'})
except Exception as e:
logger.error(f"修改打手信息异常: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '服务器内部错误'})
class KefuGetShangjiaListView(APIView):
"""
客服/管理员获取商家列表接口
需要拥有以下任一权限1100a, 1100b
请求POST /houtai/hqsjgllb
参数:
username: 客服手机号(用于权限校验,必填)
keyword: 搜索关键词可选搜索商家用户ID或昵称
balance_op: 余额比较符gte(>=) / lte(<=)(可选)
balance_value: 余额数值可选需与balance_op同时存在
order_count_op: 派单量比较符gte(>=) / lte(<=)(可选)
order_count_value: 派单量数值可选需与order_count_op同时存在
status: 商家状态1=正常2=禁用(可选)
page: 页码从1开始默认1
page_size: 每页数量默认20
返回:
code: 0 成功,其他失败
data: {
list: [商家对象],
total: int, // 总商家数
}
"""
permission_classes = [] # 不依赖全局认证,手动校验
parser_classes = [JSONParser]
def post(self, request):
# 1. 获取参数
username = request.data.get('username', '').strip()
keyword = request.data.get('keyword', '').strip()
balance_op = request.data.get('balance_op', '').strip().lower()
balance_value_str = request.data.get('balance_value')
order_count_op = request.data.get('order_count_op', '').strip().lower()
order_count_value_str = request.data.get('order_count_value')
status_str = request.data.get('status')
try:
page = int(request.data.get('page', 1))
page_size = int(request.data.get('page_size', 20))
if page < 1 or page_size < 1:
raise ValueError
except ValueError:
return Response({'code': 400, 'msg': '分页参数错误'})
if not username:
return Response({'code': 401, 'msg': '认证失败缺少username'})
# 2. 公共权限校验(复用现有函数 verify_kefu_permission
# 假设 verify_kefu_permission 定义在某个公共模块,这里引入
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions # 直接返回错误响应
# 3. 检查商家管理权限1100a 或 1100b
required_perms = {'1100a', '1100b'}
if not any(p in permissions for p in required_perms):
return Response({'code': 403, 'msg': '您没有权限查看商家列表'})
# 4. 构建商家查询集(商家扩展表 + 关联用户主表)
shangjia_qs = filter_user_related_by_club(
UserShangjia.query.select_related('user').all(), request,
)
# 关键词筛选用户ID或昵称
if keyword:
shangjia_qs = shangjia_qs.filter(
Q(user__UserUID__icontains=keyword) |
Q(nicheng__icontains=keyword)
)
# 余额筛选
if balance_op and balance_value_str is not None and balance_value_str != '':
try:
balance_value = Decimal(str(balance_value_str))
if balance_op == 'gte':
shangjia_qs = shangjia_qs.filter(yue__gte=balance_value)
elif balance_op == 'lte':
shangjia_qs = shangjia_qs.filter(yue__lte=balance_value)
else:
return Response({'code': 400, 'msg': '余额比较符只支持gte或lte'})
except Exception:
return Response({'code': 400, 'msg': '余额数值格式错误'})
# 派单总量筛选fabu字段
if order_count_op and order_count_value_str is not None and order_count_value_str != '':
try:
order_count_value = int(order_count_value_str)
if order_count_op == 'gte':
shangjia_qs = shangjia_qs.filter(fabu__gte=order_count_value)
elif order_count_op == 'lte':
shangjia_qs = shangjia_qs.filter(fabu__lte=order_count_value)
else:
return Response({'code': 400, 'msg': '派单量比较符只支持gte或lte'})
except Exception:
return Response({'code': 400, 'msg': '派单量数值必须为整数'})
# 商家状态筛选
if status_str is not None and status_str != '':
try:
status_val = int(status_str)
if status_val not in [1, 2]:
return Response({'code': 400, 'msg': '商家状态值无效应为1或2'})
shangjia_qs = shangjia_qs.filter(zhuangtai=status_val)
except ValueError:
return Response({'code': 400, 'msg': '商家状态格式错误'})
# 5. 统计总商家数(应用筛选后)
_agg = shangjia_qs.aggregate(
total=Count('id'),
valid=Count('id', filter=Q(zhuangtai=1)),
)
total = _agg['total']
valid_merchant_count = _agg['valid']
# 6. 分页
offset = (page - 1) * page_size
shangjia_list = shangjia_qs.order_by('-CreateTime')[offset:offset + page_size]
# 7. 构建返回数据
result = []
for shangjia in shangjia_list:
user = shangjia.user
result.append({
'yonghuid': user.UserUID,
'avatar': user.Avatar or '',
'nicheng': shangjia.nicheng or '',
'yue': str(shangjia.yue), # 转换为字符串,避免前端精度问题
'fabu': shangjia.fabu,
'chengjiao': shangjia.chengjiao,
'zhuangtai': shangjia.zhuangtai, # 1=正常,2=禁用
})
meta = list_response_meta(request)
return Response({
'code': 0,
'club_id': meta['club_id'],
'scope': meta['scope'],
'data': {
'list': result,
'total': total,
'valid_merchant_count': valid_merchant_count,
}
})
# yonghu/views.py 中添加
class KefuGetShangjiaDetailView(APIView):
"""
客服/管理员获取商家详情接口
需要权限1100a 或 1100b
请求POST /houtai/hthqsjxq
参数:
username: 客服手机号(用于权限校验,必填)
yonghuid: 商家用户ID必填
返回:
code: 0 成功
data: 商家所有字段(含主表头像等)
"""
permission_classes = []
parser_classes = [JSONParser]
def post(self, request):
username = request.data.get('username', '').strip()
yonghuid = request.data.get('yonghuid', '').strip()
if not username:
return Response({'code': 401, 'msg': '认证失败缺少username'})
if not yonghuid:
return Response({'code': 400, 'msg': '缺少商家ID'})
# 权限校验
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
required_perms = {'1100a', '1100b'}
if not any(p in permissions for p in required_perms):
return Response({'code': 403, 'msg': '您没有权限查看商家详情'})
# 查询商家
try:
shangjia = UserShangjia.query.select_related('user').get(user__UserUID=yonghuid)
except UserShangjia.DoesNotExist:
return Response({'code': 404, 'msg': '商家不存在'})
denied = forbid_if_user_out_of_scope(request, shangjia.user)
if denied:
return denied
user = shangjia.user
data = {
'yonghuid': user.UserUID,
'avatar': user.Avatar or '',
'nicheng': shangjia.nicheng,
'zhuangtai': shangjia.zhuangtai, # 1正常2禁用
'dianhua': shangjia.dianhua,
'wechat': shangjia.wechat,
'yue': str(shangjia.yue),
'fabu': shangjia.fabu,
'chengjiao': shangjia.chengjiao,
'tuikuan': shangjia.tuikuan,
'jinridingdan': shangjia.jinridingdan,
'jinriliushui': str(shangjia.jinriliushui),
'jinyuedingdan': shangjia.jinyuedingdan,
'jinyueliushui': str(shangjia.jinyueliushui),
'is_youzhi_shangjia': bool(shangjia.is_youzhi_shangjia),
'CreateTime': shangjia.CreateTime.isoformat() if shangjia.CreateTime else None,
'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
}
return Response({'code': 0, 'data': data})
class KefuUpdateShangjiaView(APIView):
"""
客服/管理员修改商家信息(余额、状态)
POST /houtai/xgsjxx
参数:
username, yonghuid, action
action可选add_balance, sub_balance, ban, unban
amountadd_balance/sub_balance时必填
"""
permission_classes = []
parser_classes = [JSONParser]
def post(self, request):
username = request.data.get('username', '').strip()
yonghuid = request.data.get('yonghuid', '').strip()
action = request.data.get('action', '').strip().lower()
if not username:
return Response({'code': 401, 'msg': '缺少username'})
if not yonghuid:
return Response({'code': 400, 'msg': '缺少商家ID'})
if action not in ('add_balance', 'sub_balance', 'ban', 'unban', 'set_youzhi'):
return Response({'code': 400, 'msg': 'action参数无效'})
# 权限校验
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
# 操作权限校验
if action in ('add_balance', 'ban', 'unban', 'set_youzhi') and '1100a' not in permissions:
return Response({'code': 403, 'msg': '无权限执行此操作需要1100a'})
if action == 'sub_balance' and '1100b' not in permissions:
return Response({'code': 403, 'msg': '无权限减少商家余额需要1100b'})
# 使用事务select_for_update 必须在事务内
with transaction.atomic():
try:
shangjia = UserShangjia.objects.select_for_update().get(user__UserUID=yonghuid)
except UserShangjia.DoesNotExist:
return Response({'code': 404, 'msg': '商家不存在'})
# 增加余额
if action == 'add_balance':
from jituan.services.group_finance_gate import deny_group_finance_exec
deny = deny_group_finance_exec(request.user, request)
if deny:
return deny
amount_str = request.data.get('amount')
try:
amount = Decimal(str(amount_str))
if amount <= 0 or amount > 1000000:
return Response({'code': 400, 'msg': '金额需在0.01~1000000之间'})
except:
return Response({'code': 400, 'msg': '金额格式错误'})
old_yue = shangjia.yue
new_yue = shangjia.yue + amount
if new_yue > 999999999.99:
return Response({'code': 400, 'msg': '增加后余额超出上限'})
shangjia.yue = new_yue
shangjia.save(update_fields=['yue'])
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_SHANGJIA,
shangjiayue=new_yue,
shangjiayueq=old_yue,
qitashuoming=(
f'【后台】增加商家余额:{old_yue}元 → {new_yue}元,增加{amount}元,'
f'商家ID={yonghuid},昵称={shangjia.nicheng},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '余额增加成功', 'data': {'new_balance': str(shangjia.yue)}})
# 减少余额
if action == 'sub_balance':
amount_str = request.data.get('amount')
try:
amount = Decimal(str(amount_str))
if amount <= 0 or amount > 1000000:
return Response({'code': 400, 'msg': '金额需在0.01~1000000之间'})
except:
return Response({'code': 400, 'msg': '金额格式错误'})
if shangjia.yue < amount:
return Response({'code': 400, 'msg': '余额不足'})
old_yue = shangjia.yue
shangjia.yue = shangjia.yue - amount
shangjia.save(update_fields=['yue'])
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_SHANGJIA,
shangjiayue=shangjia.yue,
shangjiayueq=old_yue,
qitashuoming=(
f'【后台】减少商家余额:{old_yue}元 → {shangjia.yue}元,减少{amount}元,'
f'商家ID={yonghuid},昵称={shangjia.nicheng},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '余额减少成功', 'data': {'new_balance': str(shangjia.yue)}})
# 封禁
if action == 'ban':
if shangjia.zhuangtai == 2:
return Response({'code': 400, 'msg': '商家已是封禁状态'})
shangjia.zhuangtai = 2
shangjia.save(update_fields=['zhuangtai'])
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_SHANGJIA,
qitashuoming=(
f'【后台】封禁商家账号:正常 → 禁用,'
f'商家ID={yonghuid},昵称={shangjia.nicheng},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '商家已封禁', 'data': {'zhuangtai': 2}})
# 解封
if action == 'unban':
if shangjia.zhuangtai == 1:
return Response({'code': 400, 'msg': '商家已是正常状态'})
shangjia.zhuangtai = 1
shangjia.save(update_fields=['zhuangtai'])
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_SHANGJIA,
qitashuoming=(
f'【后台】解封商家账号:禁用 → 正常,'
f'商家ID={yonghuid},昵称={shangjia.nicheng},操作人={kefu.user.Phone}'
),
)
return Response({'code': 0, 'msg': '商家已解封', 'data': {'zhuangtai': 1}})
if action == 'set_youzhi':
if '1100a' not in permissions:
return Response({'code': 403, 'msg': '无权限设置优质商家需要1100a'})
is_youzhi = bool(request.data.get('is_youzhi_shangjia', False))
shangjia.is_youzhi_shangjia = is_youzhi
shangjia.save(update_fields=['is_youzhi_shangjia'])
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_SHANGJIA,
qitashuoming=(
f'【后台】设置优质商家:{is_youzhi}'
f'商家ID={yonghuid},昵称={shangjia.nicheng},操作人={kefu.user.Phone}'
),
)
return Response({
'code': 0,
'msg': '优质商家设置成功',
'data': {'is_youzhi_shangjia': is_youzhi},
})
return Response({'code': 400, 'msg': '未知操作'})
class KefuAddShangjiaView(APIView):
"""
后台按用户ID开通商家分俱乐部禁止前端自助注册场景使用
POST /houtai/tjsj 或 /jituan/houtai/tjsj
参数username, yonghuid用户业务ID, nicheng可选
权限1100a
"""
permission_classes = []
parser_classes = [JSONParser]
def post(self, request):
username = request.data.get('username', '').strip()
yonghuid = str(request.data.get('yonghuid', '')).strip()
nicheng_in = (request.data.get('nicheng') or '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少username'})
if not yonghuid:
return Response({'code': 400, 'msg': '请填写用户ID'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if '1100a' not in permissions:
return Response({'code': 403, 'msg': '无权限添加商家需要1100a'})
try:
user = User.query.get(UserUID=yonghuid)
except User.DoesNotExist:
return Response({'code': 404, 'msg': '用户不存在请确认用户ID'})
denied = forbid_if_user_out_of_scope(request, user)
if denied:
return denied
if not getattr(user, 'IsActive', True):
return Response({'code': 400, 'msg': '该账号已被封禁,无法开通商家'})
try:
dashou = user.DashouProfile
if int(getattr(dashou, 'zhanghaozhuangtai', 1) or 0) != 1:
return Response({'code': 400, 'msg': '该账号已被封禁,无法开通商家'})
except UserDashou.DoesNotExist:
pass
except Exception:
pass
if UserShangjia.query.filter(user=user).exists():
return Response({'code': 400, 'msg': '该用户已是商家,无需重复添加'})
from merchant_ops.services.authz import assert_no_active_staff_when_merchant_register, StaffAuthError
try:
assert_no_active_staff_when_merchant_register(user)
except StaffAuthError as e:
return Response({'code': 403, 'msg': getattr(e, 'msg', None) or str(e)})
except Exception:
pass
boss_nickname = '普通商家'
try:
boss = user.BossProfile
if boss.nickname and boss.nickname.strip():
boss_nickname = boss.nickname.strip()
except UserBoss.DoesNotExist:
pass
shangjia_nicheng = nicheng_in or boss_nickname
if UserShangjia.query.filter(nicheng=shangjia_nicheng).exists():
shangjia_nicheng = f"{shangjia_nicheng}{random.randint(1000, 9999)}"
try:
with transaction.atomic():
shop = UserShangjia.query.create(
user=user,
nicheng=shangjia_nicheng,
zhuangtai=1,
yue=0.00,
fabu=0,
tuikuan=0,
chengjiao=0,
jinridingdan=0,
jinriliushui=0.00,
jinyuedingdan=0,
jinyueliushui=0.00,
dianhua='',
wechat='',
)
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=kefu.user.Phone,
leixing=XIUGAI_LEIXING_SHANGJIA,
qitashuoming=(
f'【后台】添加商家用户ID={yonghuid},昵称={shop.nicheng}'
f'操作人={kefu.user.Phone}'
),
)
except Exception as e:
logger.exception('后台添加商家失败')
return Response({'code': 500, 'msg': f'添加失败:{e}'})
return Response({
'code': 0,
'msg': '商家添加成功',
'data': {
'yonghuid': yonghuid,
'nicheng': shop.nicheng,
'zhuangtai': shop.zhuangtai,
},
})
class KefuChatPermissionsView(APIView):
"""
获取客服聊天权限及GoEasy配置
POST /houtai/kfhqltqx
Body: { "phone": "客服手机号" }
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# 1. 从前端获取传递的账号,用于防越权校验
username_from_frontend = request.data.get('phone', None)
# 2. 调用公共验证方法。
# 返回 (kefu_obj, permissions) 或 (None, Response)
kefu_obj, permissions = verify_kefu_permission(request, username_from_frontend)
# 3. 如果第一个返回值为 None说明验证失败直接返回第二个值错误Response
if kefu_obj is None:
return permissions
# ---------- 验证通过permissions 已经是权限码列表 ----------
# 4. 检查是否拥有至少一个聊天权限
has_chat_perm = any(p in CHAT_PERM_CODES for p in permissions)
# 5. 若有聊天权限,获取 GoEasy AppKey
goeasy_appkey = ''
if has_chat_perm:
goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '')
# 6. 返回数据
return Response({
'code': 0,
'msg': 'ok',
'data': {
'permissions': permissions,
'goeasy_appkey': goeasy_appkey
}
})
class DashouGiftHuiyuanSaveView(APIView):
"""
赠送会员 / 改期(免费接单资格,写入 dashou_zengsong_huiyuan
POST /houtai/dszsonghy
Body: {
phone, dashou_id, huiyuan_id,
daoqi_time: 'YYYY-MM-DD''YYYY-MM-DD HH:MM:SS',
id?: 已有记录ID改期时建议传
}
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
from jituan.services.gift_huiyuan import (
has_gift_huiyuan_permission,
parse_gift_daoqi_time,
upsert_gift_huiyuan,
update_gift_huiyuan_daoqi,
)
from products.models import Huiyuan as HuiyuanModel
phone = (request.data.get('phone') or request.data.get('username') or '').strip()
dashou_id = (request.data.get('dashou_id') or request.data.get('uid') or '').strip()
huiyuan_id = (request.data.get('huiyuan_id') or '').strip()
record_id = request.data.get('id')
raw_daoqi = request.data.get('daoqi_time')
kefu, permissions = verify_kefu_permission(request, phone or None)
if kefu is None:
return permissions
if not has_gift_huiyuan_permission(permissions):
return Response({'code': 403, 'msg': '无赠送会员权限(需打手基础管理或加积分权限)'})
if not dashou_id:
return Response({'code': 400, 'msg': '缺少打手ID'})
daoqi_time, err = parse_gift_daoqi_time(raw_daoqi)
if err:
return Response({'code': 400, 'msg': err})
try:
dashou_user = User.query.get(UserUID=dashou_id)
except User.DoesNotExist:
return Response({'code': 404, 'msg': '打手不存在'})
denied = forbid_if_user_out_of_scope(request, dashou_user)
if denied:
return denied
try:
dashou_user.DashouProfile
except ObjectDoesNotExist:
return Response({'code': 404, 'msg': '该用户不是打手'})
song_ren = getattr(kefu.user, 'Phone', None) or phone
operator = song_ren
# 有记录ID只改期
if record_id not in (None, ''):
try:
rid = int(record_id)
except (TypeError, ValueError):
return Response({'code': 400, 'msg': '无效的记录ID'})
rec, err = update_gift_huiyuan_daoqi(
record_id=rid,
yonghu_id=dashou_id,
daoqi_time=daoqi_time,
song_ren=song_ren,
)
if err:
return Response({'code': 400, 'msg': err})
try:
hy_name = HuiyuanModel.query.get(huiyuan_id=rec.huiyuan_id).jieshao
except HuiyuanModel.DoesNotExist:
hy_name = rec.huiyuan_id
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_DASHOU,
huiyuan_id=rec.huiyuan_id,
qitashuoming=(
f'【后台】修改赠送会员到期时间会员ID={rec.huiyuan_id}{hy_name}'
f'新到期={rec.daoqi_time.strftime("%Y-%m-%d %H:%M:%S")}'
f'打手ID={dashou_id},操作人={operator}'
),
)
return Response({
'code': 0,
'msg': '改期成功',
'data': {
'id': rec.id,
'huiyuan_id': rec.huiyuan_id,
'daoqi_time': rec.daoqi_time.strftime('%Y-%m-%d %H:%M:%S'),
},
})
if not huiyuan_id:
return Response({'code': 400, 'msg': '缺少会员ID'})
rec, created, jifen_info, err = upsert_gift_huiyuan(
yonghu_id=dashou_id,
huiyuan_id=huiyuan_id,
daoqi_time=daoqi_time,
song_ren=song_ren,
)
if err:
return Response({'code': 400, 'msg': err})
try:
hy_name = HuiyuanModel.query.get(huiyuan_id=huiyuan_id).jieshao
except HuiyuanModel.DoesNotExist:
hy_name = huiyuan_id
if jifen_info:
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_DASHOU,
jifen=jifen_info['after'],
yjifen=jifen_info['before'],
qitashuoming=(
f'【后台】送会员时整改积分:{jifen_info["before"]}{jifen_info["after"]}'
f'原积分={jifen_info["before"]},整改后={jifen_info["after"]}'
f'打手ID={dashou_id},操作人={operator}(送会员免费接单)'
),
)
action_label = '赠送' if created else '更新到期时间'
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_DASHOU,
huiyuan_id=huiyuan_id,
qitashuoming=(
f'【后台】{action_label}赠送会员免费接单会员ID={huiyuan_id}{hy_name}'
f'到期={rec.daoqi_time.strftime("%Y-%m-%d %H:%M:%S")}'
f'打手ID={dashou_id},操作人={operator}(写入赠送会员表,非购买会员)'
),
)
return Response({
'code': 0,
'msg': '赠送成功' if created else '到期时间已更新',
'data': {
'id': rec.id,
'huiyuan_id': rec.huiyuan_id,
'daoqi_time': rec.daoqi_time.strftime('%Y-%m-%d %H:%M:%S'),
'created': created,
'jifen_adjusted': bool(jifen_info),
'jifen': jifen_info,
},
})
class DashouGiftHuiyuanRemoveView(APIView):
"""
移除赠送会员
POST /houtai/dsycsonghy
Body: { phone, dashou_id, id? , huiyuan_id? }
"""
permission_classes = [IsAuthenticated]
parser_classes = [JSONParser]
def post(self, request):
from jituan.services.gift_huiyuan import (
has_gift_huiyuan_permission,
remove_gift_huiyuan,
)
phone = (request.data.get('phone') or request.data.get('username') or '').strip()
dashou_id = (request.data.get('dashou_id') or request.data.get('uid') or '').strip()
record_id = request.data.get('id')
huiyuan_id = (request.data.get('huiyuan_id') or '').strip()
kefu, permissions = verify_kefu_permission(request, phone or None)
if kefu is None:
return permissions
if not has_gift_huiyuan_permission(permissions):
return Response({'code': 403, 'msg': '无移除赠送会员权限'})
if not dashou_id:
return Response({'code': 400, 'msg': '缺少打手ID'})
try:
dashou_user = User.query.get(UserUID=dashou_id)
except User.DoesNotExist:
return Response({'code': 404, 'msg': '打手不存在'})
denied = forbid_if_user_out_of_scope(request, dashou_user)
if denied:
return denied
rid = None
if record_id not in (None, ''):
try:
rid = int(record_id)
except (TypeError, ValueError):
return Response({'code': 400, 'msg': '无效的记录ID'})
ok, err = remove_gift_huiyuan(
record_id=rid,
yonghu_id=dashou_id if not rid else None,
huiyuan_id=huiyuan_id if not rid else None,
)
if err:
return Response({'code': 400, 'msg': err})
operator = getattr(kefu.user, 'Phone', None) or phone
write_xiugai_log(
yonghuid=dashou_id,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_DASHOU,
huiyuan_id=huiyuan_id or '',
qitashuoming=(
f'【后台】移除赠送会员免费接单记录ID={rid or "-"}'
f'会员ID={huiyuan_id or "-"}打手ID={dashou_id},操作人={operator}'
),
)
return Response({'code': 0, 'msg': '移除成功'})