Files
Django/backend/views/zuzhang.py

792 lines
34 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 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']
class GetZuzhangListView(APIView):
"""
组长列表接口(分页 + 多条件筛选)
路由POST /houtai/hthqzzlb
权限:需要登录,且拥有 6600a~6600e 任一权限
"""
permission_classes = [IsAuthenticated]
@transaction.atomic # 保证查询的一致性(可选,对只读查询影响不大但无坏处)
def post(self, request):
# 1. 获取前端传递的 username用于防越权
frontend_username = request.data.get('username', '')
# 2. 调用公共校验方法,验证身份并获取权限列表
result = verify_kefu_permission(request, username_from_frontend=frontend_username)
# 重要verify_kefu_permission 返回格式为 (user_obj, permissions_list) 或 (None, Response)
# 如果第二个元素是 Response 对象,说明校验失败,直接返回该 Response
if isinstance(result, tuple) and len(result) == 2:
user_obj, second = result
if isinstance(second, Response):
return second # 校验失败,直接返回错误响应
else:
permissions = second # 正常情况permissions 是列表
else:
# 防止意外返回格式,视为失败
return Response({'code': 500, 'msg': '权限校验模块异常'})
# 3. 检查是否拥有组长管理所需权限6600a ~ 6600e 任一)
required_perms = ['6600a', '6600b', '6600c', '6600d', '6600e']
if not any(perm in permissions for perm in required_perms):
logger.warning(f"用户 {request.user.Phone} 尝试访问组长列表但缺少必要权限,已有权限: {permissions}")
return Response({'code': 403, 'msg': '您无权访问组长管理页面'})
# 4. 提取请求参数(分页 + 筛选)
try:
page = int(request.data.get('page', 1))
page_size = int(request.data.get('page_size', 20))
except ValueError:
page, page_size = 1, 20
keyword = request.data.get('keyword', '').strip()
status = request.data.get('status') # 1=正常, 0=禁用
invite_count_op = request.data.get('invite_count_op') # gte / lte
invite_count_value = request.data.get('invite_count_value')
amount_op = request.data.get('amount_op')
amount_value = request.data.get('amount_value')
commission_op = request.data.get('commission_op')
commission_value = request.data.get('commission_value')
# 5. 构建基础 QuerySet预加载关联表
queryset = filter_user_related_by_club(
UserZuzhang.query.select_related('user', 'user__BossProfile').all(),
request,
)
# 6. 应用筛选条件(使用 ORM 安全过滤)
if keyword:
queryset = queryset.filter(
Q(user__UserUID__icontains=keyword) |
Q(user__BossProfile__nickname__icontains=keyword)
)
if status is not None and status != '':
try:
queryset = queryset.filter(zhuangtai=int(status))
except ValueError:
pass
# 邀请总数筛选
if invite_count_value not in (None, ''):
try:
val = float(invite_count_value)
op = invite_count_op if invite_count_op in ('gte', 'lte') else 'gte'
queryset = queryset.filter(**{f'yaoqing_zongshu__{op}': val})
except (ValueError, TypeError):
pass
# 可提现金额筛选
if amount_value not in (None, ''):
try:
val = float(amount_value)
op = amount_op if amount_op in ('gte', 'lte') else 'gte'
queryset = queryset.filter(**{f'ketixian_jine__{op}': val})
except (ValueError, TypeError):
pass
# 分佣总额筛选
if commission_value not in (None, ''):
try:
val = float(commission_value)
op = commission_op if commission_op in ('gte', 'lte') else 'gte'
queryset = queryset.filter(**{f'fenyong_zonge__{op}': val})
except (ValueError, TypeError):
pass
# 7. 总数统计
total = queryset.count()
# 8. 分页
offset = (page - 1) * page_size
paged_queryset = queryset[offset:offset + page_size]
# 9. 构造返回数据
data_list = []
for zu in paged_queryset:
user = zu.user
nickname = ''
if hasattr(user, 'BossProfile') and user.BossProfile:
nickname = user.BossProfile.nickname or ''
data_list.append({
'yonghuid': user.UserUID,
'avatar': user.Avatar or '',
'nickname': nickname,
'fenyong_zonge': float(zu.fenyong_zonge),
'ketixian_jine': float(zu.ketixian_jine),
'yaoqing_zongshu': zu.yaoqing_zongshu,
'zhuangtai': zu.zhuangtai,
})
return Response({
'code': 0,
'msg': 'success',
**list_response_meta(request),
'data': {
'list': data_list,
'total': total,
}
})
class GetZuzhangDetailView(APIView):
"""
获取组长详情
权限6600a,6600b,6600c,6600d,6600e 任一
POST /houtai/hthqzzxq
参数username, yonghuid
返回:
code:0, data: {
base: {...},
balance: {...},
permanent_enabled, permanent_amount,
custom_first_dividend: {},
extra_dividend_map: {},
all_members: []
}
"""
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 = {'6600a', '6600b', '6600c', '6600d', '6600e'}
if not any(p in permissions for p in required_perms):
return Response({'code': 403, 'msg': '您没有权限查看组长详情'})
# 查询组长主表、扩展表、老板扩展表(昵称)
try:
user = User.query.select_related('ZuzhangProfile', 'BossProfile').get(UserUID=yonghuid)
zuzhang = user.ZuzhangProfile
boss = user.BossProfile if hasattr(user, 'BossProfile') else None
except User.DoesNotExist:
return Response({'code': 404, 'msg': '组长不存在'})
except UserZuzhang.DoesNotExist:
return Response({'code': 404, 'msg': '用户不是组长'})
denied = forbid_if_user_out_of_scope(request, user)
if denied:
return denied
# 基础信息
base_data = {
'yonghuid': user.UserUID,
'nickname': boss.nickname if boss else '',
'avatar': user.Avatar or '',
'dianhua': '', # 组长表无电话字段?组长扩展表没有电话/微信但前端可能需要可从User关联实际上组长表无电话字段这里留空或从其他表获取
'wechat': '',
'zhuangtai': zuzhang.zhuangtai,
'yaoqingma': zuzhang.yaoqingma,
'yaoqingren': '', # 组长表没有邀请人字段
'haibao_url': zuzhang.haibao_url or '',
'yaoqing_zongshu': zuzhang.yaoqing_zongshu,
'jinri_fenyong': str(zuzhang.jinri_fenyong),
'jinyue_fenyong': str(zuzhang.jinyue_fenyong),
'fenyong_zonge': str(zuzhang.fenyong_zonge),
'CreateTime': zuzhang.CreateTime.isoformat() if zuzhang.CreateTime else None,
'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if getattr(user, 'UserCreateTime', None) else '',
}
# 余额提现相关
balance_data = {
'ketixian_jine': str(zuzhang.ketixian_jine),
'jinri_tixian': str(zuzhang.jinri_tixian),
'last_tixian_time': zuzhang.last_tixian_time.isoformat() if zuzhang.last_tixian_time else None,
'kaioi_ewai_tixian': zuzhang.kaioi_ewai_tixian,
'ewai_tixian_xiane': str(zuzhang.ewai_tixian_xiane),
}
# 永久分红(额外分红开关和金额)
permanent_enabled = zuzhang.kaioi_ewai_fenhong
permanent_amount = str(zuzhang.ewai_fenhong_jine) if zuzhang.ewai_fenhong_jine else '0'
# 获取所有会员(用于前端选择)
all_members = list(Huiyuan.query.all().values('huiyuan_id', 'jieshao', 'jiage', 'zuzhangfc'))
for m in all_members:
m['jiage'] = str(m['jiage'])
m['zuzhangfc'] = str(m['zuzhangfc'])
# 获取多次分红配置(按次数和会员组织,按俱乐部隔离)
from jituan.services.club_penalty import resolve_gsfenhong_club_id
zuzhang_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
duoci_records = DuociFenhong.query.filter(
yonghuid=yonghuid, club_id=zuzhang_club,
).order_by('cishu')
custom_first = {} # 首次分红定制cishu=1
extra_map = {} # 其他次数 { cishu: { huiyuan_id: { zuzhang_fenhong, jieshao, jiage } } }
for record in duoci_records:
# 获取会员信息
member = Huiyuan.query.filter(huiyuan_id=record.huiyuan).first()
if not member:
continue
member_info = {
'jieshao': member.jieshao,
'jiage': str(member.jiage),
}
if record.cishu == 1:
custom_first[record.huiyuan] = float(record.zuzhang_fenhong)
else:
times = record.cishu
if times not in extra_map:
extra_map[times] = {}
extra_map[times][record.huiyuan] = {
'zuzhang_fenhong': float(record.zuzhang_fenhong),
**member_info
}
return Response({
'code': 0,
'data': {
'base': base_data,
'balance': balance_data,
'permanent_enabled': permanent_enabled,
'permanent_amount': permanent_amount,
'custom_first_dividend': custom_first,
'extra_dividend_map': extra_map,
'all_members': all_members,
}
})
class UpdateZuzhangView(APIView):
"""
修改组长信息(基础信息、余额、提现限额、分红策略)
权限细分:
- 增加可提现金额6600a
- 减少可提现金额6600b
- 修改状态(封禁/解封6600c
- 修改提现限额kaioi_ewai_tixian/ewai_tixian_xiane6600d
- 修改分红相关永久分红、定制首次、额外次数6600e
POST /houtai/htxgzzxx
参数:
username, yonghuid,
nickname, avatar, dianhua, wechat, zhuangtai, # 基础
ketixian_jine, kaioi_ewai_tixian, ewai_tixian_xiane, # 余额提现
permanent_enabled, permanent_amount, # 永久分红
custom_first_dividend: {}, # 首次定制分红 {会员ID: 金额}
extra_dividend_map: {} # 额外次数分红 {次数: {会员ID: {zuzhang_fenhong}}}
"""
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
# 查询组长对象
try:
user = User.query.get(UserUID=yonghuid)
zuzhang = user.ZuzhangProfile
boss = user.BossProfile if hasattr(user, 'BossProfile') else None
except User.DoesNotExist:
return Response({'code': 404, 'msg': '用户不存在'})
except UserZuzhang.DoesNotExist:
return Response({'code': 404, 'msg': '用户不是组长'})
# 获取原始数据(用于对比金额变化)
old_ketixian_jine = zuzhang.ketixian_jine
# 开始事务
with transaction.atomic():
operator = kefu.user.Phone
old_nickname = boss.nickname if boss else ''
old_zhuangtai = zuzhang.zhuangtai
# ---------- 1. 基础信息修改需要6600c按需求基础信息修改通常用6600c但为了安全暂不校验如有需要可加----------
nickname = request.data.get('nickname', '').strip()
avatar = request.data.get('avatar', '').strip()
dianhua = request.data.get('dianhua', '').strip()
wechat = request.data.get('wechat', '').strip()
zhuangtai = request.data.get('zhuangtai')
base_changes = []
# 状态修改需要6600c
if zhuangtai is not None:
if '6600c' not in permissions:
return Response({'code': 403, 'msg': '无权限修改组长状态需要6600c'})
try:
zt = int(zhuangtai)
if zt in (0, 1):
zuzhang.zhuangtai = zt
if zt != old_zhuangtai:
zt_map = {0: '禁用', 1: '正常'}
base_changes.append(f'状态:{zt_map.get(old_zhuangtai, old_zhuangtai)}{zt_map.get(zt, zt)}')
except:
pass
if nickname:
if not boss:
boss = UserBoss.query.create(user=user, nickname=nickname)
else:
boss.nickname = nickname
boss.save()
if nickname != old_nickname:
base_changes.append(f'昵称:{old_nickname or ""}{nickname}')
if avatar:
user.Avatar = avatar
user.save()
base_changes.append('更新了头像')
if base_changes:
zuzhang.save()
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_ZUZHANG,
qitashuoming=(
f'【后台】修改组长基础信息:{"".join(base_changes)}'
f'组长ID={yonghuid},操作人={operator}'
),
)
# 组长表没有电话/微信字段如需存储可暂存到User或忽略
# 这里不处理
# ---------- 2. 可提现金额修改6600a6600b----------
new_ketixian_jine = request.data.get('ketixian_jine')
if new_ketixian_jine is not None:
try:
new_val = Decimal(str(new_ketixian_jine))
if new_val < 0:
return Response({'code': 400, 'msg': '可提现金额不能为负数'})
except:
return Response({'code': 400, 'msg': '金额格式错误'})
if new_val > old_ketixian_jine:
if '6600a' not in permissions:
return Response({'code': 403, 'msg': '无权限增加可提现金额需要6600a'})
from jituan.services.group_finance_gate import deny_group_finance_exec
deny = deny_group_finance_exec(request.user, request)
if deny:
return deny
action_desc = f'增加{new_val - old_ketixian_jine}'
elif new_val < old_ketixian_jine:
if '6600b' not in permissions:
return Response({'code': 403, 'msg': '无权限减少可提现金额需要6600b'})
action_desc = f'减少{old_ketixian_jine - new_val}'
else:
action_desc = '无变动'
if new_val != old_ketixian_jine:
zuzhang.ketixian_jine = new_val
zuzhang.save(update_fields=['ketixian_jine'])
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_ZUZHANG,
xiugaitijiao=new_val,
xiugaitijiaoq=old_ketixian_jine,
qitashuoming=(
f'【后台】修改组长可提现金额:{old_ketixian_jine}元 → {new_val}元({action_desc}'
f'组长ID={yonghuid},操作人={operator}'
),
)
# ---------- 3. 提现限额修改需要6600d----------
kaioi_ewai = request.data.get('kaioi_ewai_tixian')
ewai_val = request.data.get('ewai_tixian_xiane')
if kaioi_ewai is not None or ewai_val is not None:
if '6600d' not in permissions:
return Response({'code': 403, 'msg': '无权限修改提现限额需要6600d'})
limit_changes = []
old_kaioi = zuzhang.kaioi_ewai_tixian
old_ewai = zuzhang.ewai_tixian_xiane
if kaioi_ewai is not None:
zuzhang.kaioi_ewai_tixian = bool(kaioi_ewai)
if bool(kaioi_ewai) != old_kaioi:
limit_changes.append(f'额外限额开关:{"" if old_kaioi else ""}{"" if kaioi_ewai else ""}')
if ewai_val is not None:
try:
zuzhang.ewai_tixian_xiane = Decimal(str(ewai_val))
if zuzhang.ewai_tixian_xiane != old_ewai:
limit_changes.append(f'额外限额金额:{old_ewai}{zuzhang.ewai_tixian_xiane}')
except:
return Response({'code': 400, 'msg': '额外限额金额格式错误'})
zuzhang.save()
if limit_changes:
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_ZUZHANG,
qitashuoming=(
f'【后台】修改组长提现限额:{"".join(limit_changes)}'
f'组长ID={yonghuid},操作人={operator}'
),
)
# ---------- 4. 分红相关修改需要6600e----------
perm_enabled = request.data.get('permanent_enabled')
perm_amount = request.data.get('permanent_amount')
custom_first = request.data.get('custom_first_dividend')
extra_map = request.data.get('extra_dividend_map')
if any(v is not None for v in [perm_enabled, perm_amount, custom_first, extra_map]):
if '6600e' not in permissions:
return Response({'code': 403, 'msg': '无权限修改分红策略需要6600e'})
# 4.1 永久分红(额外分红开关和金额)
if perm_enabled is not None:
zuzhang.kaioi_ewai_fenhong = bool(perm_enabled)
if perm_amount is not None:
try:
zuzhang.ewai_fenhong_jine = Decimal(str(perm_amount))
except:
return Response({'code': 400, 'msg': '永久分红金额格式错误'})
zuzhang.save()
# 4.2 首次定制分红cishu=1
if custom_first is not None and isinstance(custom_first, dict):
from jituan.services.club_penalty import resolve_gsfenhong_club_id
zuzhang_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
existing_first = DuociFenhong.query.filter(
club_id=zuzhang_club, yonghuid=yonghuid, cishu=1,
)
for huiyuan_id, amount in custom_first.items():
try:
amount = Decimal(str(amount))
except:
continue
if amount <= 0:
# 删除定制(恢复默认)
DuociFenhong.query.filter(
club_id=zuzhang_club, yonghuid=yonghuid, huiyuan=huiyuan_id, cishu=1,
).delete()
else:
DuociFenhong.query.update_or_create(
club_id=zuzhang_club,
yonghuid=yonghuid,
huiyuan=huiyuan_id,
cishu=1,
defaults={
'zuzhang_fenhong': amount,
'guanshi_fenhong': Decimal('0') # 组长表只关心组长分红
}
)
# 删除那些在前端字典中不存在的首次定制记录(即已取消定制)
for record in existing_first:
if record.huiyuan not in custom_first:
record.delete()
# 4.3 额外次数分红cishu >= 2
if extra_map is not None and isinstance(extra_map, dict):
from jituan.services.club_penalty import resolve_gsfenhong_club_id
zuzhang_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
keep_set = set()
for times_str, members in extra_map.items():
try:
cishu = int(times_str)
if cishu < 2:
continue
except:
continue
if not isinstance(members, dict):
continue
for huiyuan_id, data in members.items():
if not isinstance(data, dict):
continue
amount = data.get('zuzhang_fenhong')
if amount is None:
continue
try:
amount = Decimal(str(amount))
except:
continue
keep_set.add((cishu, huiyuan_id))
DuociFenhong.query.update_or_create(
club_id=zuzhang_club,
yonghuid=yonghuid,
huiyuan=huiyuan_id,
cishu=cishu,
defaults={
'zuzhang_fenhong': amount,
'guanshi_fenhong': Decimal('0')
}
)
# 删除不在 keep_set 中的额外次数记录
existing_extra = DuociFenhong.query.filter(
club_id=zuzhang_club, yonghuid=yonghuid, cishu__gte=2,
)
for record in existing_extra:
if (record.cishu, record.huiyuan) not in keep_set:
record.delete()
dividend_parts = []
if perm_enabled is not None:
dividend_parts.append(f'永久分红开关→{"" if perm_enabled else ""}')
if perm_amount is not None:
dividend_parts.append(f'永久分红金额→{perm_amount}')
if custom_first is not None:
dividend_parts.append(f'更新首次定制分红({len(custom_first)}项)')
if extra_map is not None:
dividend_parts.append(f'更新额外次数分红({len(extra_map)}组)')
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_ZUZHANG,
qitashuoming=(
f'【后台】修改组长分红策略:{"".join(dividend_parts) or "配置已更新"}'
f'组长ID={yonghuid},操作人={operator}'
),
)
# 保存组长其他可能修改的字段(例如电话、微信没有,忽略)
return Response({'code': 0, 'msg': '修改成功'})
class ChangeInviterView(APIView):
"""
后台更换邀请人/上级
POST /houtai/htghyqr
参数:
username: 操作人账号
yonghuid: 被修改用户ID
target_type: dashou打手| guanshi管事
new_yaoqingren: 新邀请人用户ID须在 User 主表存在)
权限:
dashou → 001ee
guanshi → 4400a
校验:
打手新上级须为管事;管事新上级须为组长;不可设为自己
"""
permission_classes = []
parser_classes = [JSONParser]
def post(self, request):
username = request.data.get('username', '').strip()
yonghuid = request.data.get('yonghuid', '').strip()
target_type = request.data.get('target_type', '').strip().lower()
new_yaoqingren = request.data.get('new_yaoqingren', '').strip()
if not username:
return Response({'code': 401, 'msg': '缺少username'})
if not yonghuid:
return Response({'code': 400, 'msg': '缺少用户ID'})
if target_type not in ('dashou', 'guanshi'):
return Response({'code': 400, 'msg': 'target_type 须为 dashou 或 guanshi'})
if not new_yaoqingren:
return Response({'code': 400, 'msg': '缺少新邀请人ID'})
if new_yaoqingren == yonghuid:
return Response({'code': 400, 'msg': '不能将自己设为邀请人'})
kefu, permissions = verify_kefu_permission(request, username)
if kefu is None:
return permissions
if target_type == 'dashou':
if '001ee' not in permissions:
return Response({'code': 403, 'msg': '无权限更换打手邀请人需要001ee'})
else:
if '4400a' not in permissions:
return Response({'code': 403, 'msg': '无权限更换管事邀请人需要4400a'})
try:
target_user = User.query.get(UserUID=yonghuid)
except User.DoesNotExist:
return Response({'code': 404, 'msg': '被修改用户不存在'})
try:
inviter_user = User.query.get(UserUID=new_yaoqingren)
except User.DoesNotExist:
return Response({'code': 404, 'msg': '新邀请人用户ID不存在'})
operator = kefu.user.Phone
with transaction.atomic():
if target_type == 'dashou':
try:
dashou = target_user.DashouProfile
except UserDashou.DoesNotExist:
return Response({'code': 404, 'msg': '用户不是打手'})
try:
guanshi_profile = inviter_user.GuanshiProfile
except UserGuanshi.DoesNotExist:
return Response({'code': 400, 'msg': '新邀请人不是管事,无法作为打手上级'})
if guanshi_profile.zhuangtai != 1:
return Response({'code': 400, 'msg': '新邀请人管事账号已禁用'})
old_inviter = dashou.yaoqingren or ''
dashou.yaoqingren = new_yaoqingren
dashou.save(update_fields=['yaoqingren'])
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_DASHOU,
qitashuoming=(
f'【后台】更换打手邀请人:{old_inviter}{new_yaoqingren}'
f'打手ID={yonghuid},新上级须为管事(已校验),操作人={operator}'
),
)
else:
try:
guanshi = target_user.GuanshiProfile
except UserGuanshi.DoesNotExist:
return Response({'code': 404, 'msg': '用户不是管事'})
try:
zuzhang_profile = inviter_user.ZuzhangProfile
except UserZuzhang.DoesNotExist:
return Response({'code': 400, 'msg': '新邀请人不是组长,无法作为管事上级'})
if zuzhang_profile.zhuangtai != 1:
return Response({'code': 400, 'msg': '新邀请人组长账号已禁用'})
old_inviter = guanshi.yaoqingren or ''
guanshi.yaoqingren = new_yaoqingren
guanshi.save(update_fields=['yaoqingren'])
write_xiugai_log(
yonghuid=yonghuid,
xiugaiid=operator,
leixing=XIUGAI_LEIXING_GUANSHI,
qitashuoming=(
f'【后台】更换管事邀请人:{old_inviter}{new_yaoqingren}'
f'管事ID={yonghuid},新上级须为组长(已校验),操作人={operator}'
),
)
return Response({
'code': 0,
'msg': '邀请人更换成功',
'data': {'yonghuid': yonghuid, 'new_yaoqingren': new_yaoqingren},
})