修复了管理员无法进后台的问题

This commit is contained in:
2026-06-19 01:58:34 +08:00
parent 5b9436b994
commit d71a81f279
22 changed files with 4396 additions and 4295 deletions

View File

@@ -1,478 +1,481 @@
# houtai/utils.py
import logging
import json
from functools import lru_cache
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from rest_framework.response import Response
from .models import AbnormalUserLog, PlayerDailyStats, MerchantDailyStats, \
ManagerRenewalDailyStats, ManagerDailyStats, LeaderDailyStats, WithdrawalDailyStats
from gvsdsdk.models import UserRole as SdkUserRole, RolePermission as SdkRolePermission, Permission as SdkPermission
from django.db import transaction
from django.db.models import F
from datetime import date
from decimal import Decimal
from gvsdsdk.fluent import db, func, FQ
logger = logging.getLogger(__name__)
def _get_real_ip(request):
"""获取真实IP考虑代理"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0].strip()
return ip, x_forwarded_for
else:
ip = request.META.get('REMOTE_ADDR')
return ip, ''
def _record_abnormal(account_id, request, attempt_type, detail=''):
"""记录异常操作(仅用于越权等攻击行为)"""
try:
real_ip, xff = _get_real_ip(request)
headers = {k: v for k, v in request.META.items() if k.startswith('HTTP_')}
headers_json = json.dumps(headers, ensure_ascii=False)[:2000]
request_params = json.dumps(request.query_params.dict(), ensure_ascii=False) if request.query_params else ''
request_body = request.body.decode('utf-8', errors='ignore')[:5000]
AbnormalUserLog.query.create(
account_id=account_id,
ip_address=real_ip,
real_ip=real_ip,
x_forwarded_for=xff,
user_agent=request.META.get('HTTP_USER_AGENT', ''),
request_method=request.method,
request_path=request.path,
request_params=request_params,
request_body=request_body,
request_headers=headers_json,
attempt_type=attempt_type,
detail=detail,
)
logger.warning(f"记录异常用户: {account_id} IP:{real_ip} 类型:{attempt_type}")
except Exception as e:
logger.error(f"记录异常日志失败: {e}")
def verify_kefu_permission(request, username_from_frontend=None):
"""
验证客服身份并返回该用户的所有权限列表基于新RBAC表
只有前端传递的username与当前登录用户不匹配时才记录异常并警告前端。
参数:
request: DRF request 对象
username_from_frontend: 前端传递的账号(用于防越权)
返回:
(kefu_obj, permissions_list) # 验证通过
(None, Response) # 验证失败直接返回Response
"""
current_user = request.user
# ---------- 1. 必须是客服 ----------
if current_user.user_type != 'kefu':
return None, Response({'code': 403, 'msg': '身份错误,非客服账号'}, status=403)
# ---------- 2. 客服扩展表存在且状态正常 ----------
try:
kefu = current_user.kefu_profile
if kefu.zhuangtai != 1:
return None, Response({'code': 403, 'msg': '客服账号已被禁用'}, status=403)
except ObjectDoesNotExist:
return None, Response({'code': 403, 'msg': '客服账号不存在'}, status=403)
# ---------- 3. 越权检测只记录前端篡改username的情况 ----------
if username_from_frontend and current_user.phone != username_from_frontend:
detail = f'前端username: {username_from_frontend},实际登录账号: {current_user.phone}'
_record_abnormal(current_user.phone, request, '越权尝试篡改username', detail=detail)
return None, Response({
'code': 403,
'msg': '身份错误,无权操作其他账号,相关操作已记录'
}, status=403)
# ---------- 4. 获取用户的所有权限 ----------
# 使用 gvsdsdk RBAC 表UserRole → RolePermission → Permission
try:
user_uuid = current_user.UserUUID
role_uuids = list(SdkUserRole.objects.filter(
UserUUID=user_uuid
).values_list('RoleUUID', flat=True))
if role_uuids:
perm_uuids = list(SdkRolePermission.objects.filter(
RoleUUID__in=role_uuids
).values_list('PermUUID', flat=True))
permissions = list(SdkPermission.objects.filter(
PermUUID__in=perm_uuids,
PermStatus=1
).values_list('PermCode', flat=True).distinct())
else:
permissions = []
except Exception:
permissions = []
# 超级用户自动拥有所有权限
if current_user.IsSuperuser and '000001' not in permissions:
permissions.insert(0, '000001')
if not permissions:
logger.warning(f"用户 {current_user.phone} 没有任何权限,请检查角色分配")
return kefu, permissions
def update_dashou_daily_by_action(yonghuid, amount, action):
"""
根据行为类型更新打手每日统计(接单、成交、退款)
参数:
yonghuid: 打手用户ID
amount: 本单涉及的金额Decimal
action: 1 = 接单, 2 = 成交(结算), 3 = 退款
"""
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return # 金额非正不统计
today = date.today()
with transaction.atomic():
stat, created = PlayerDailyStats.objects.select_for_update().get_or_create(
PlayerID=yonghuid,
Date=today,
defaults={
'PlayerID': yonghuid,
'Date': today,
'AcceptedOrderTotal': 0,
'AcceptedAmount': Decimal('0.00'),
'CompletedOrderTotal': 0,
'CompletedAmount': Decimal('0.00'),
'RefundCount': 0,
'RefundAmount': Decimal('0.00'),
}
)
update_fields = {}
if action == 1: # 接单
update_fields['AcceptedOrderTotal'] = F('AcceptedOrderTotal') + 1
update_fields['AcceptedAmount'] = F('AcceptedAmount') + amount
elif action == 2: # 成交
update_fields['CompletedOrderTotal'] = F('CompletedOrderTotal') + 1
update_fields['CompletedAmount'] = F('CompletedAmount') + amount
elif action == 3: # 退款
update_fields['RefundCount'] = F('RefundCount') + 1
update_fields['RefundAmount'] = F('RefundAmount') + amount
else:
return # 无效行为
if update_fields:
PlayerDailyStats.query.filter(id=stat.id).update(**update_fields)
def update_shangjia_daily(yonghuid, amount, action):
"""
根据操作行为更新商家每日统计(派发、结算、退款)
参数:
yonghuid: 商家用户ID
amount: 当前订单涉及的金额Decimal
action: 操作类型1 = 派发2 = 结算成交3 = 退款
说明:
- 金额必须大于0否则不执行任何操作
- 使用 select_for_update + F() 表达式保证原子性和并发安全
- 每天每个商家只会有一条统计记录,不存在则自动创建
"""
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return # 金额非正,不统计
today = date.today()
with transaction.atomic():
# 获取或创建当天统计记录,同时加锁防止并发
stat, created = MerchantDailyStats.objects.select_for_update().get_or_create(
MerchantID=yonghuid,
Date=today,
defaults={
'MerchantID': yonghuid,
'Date': today,
'AssignedOrderCount': 0,
'AssignedAmount': Decimal('0.00'),
'SettledOrderCount': 0,
'SettledAmount': Decimal('0.00'),
'RefundOrderCount': 0,
'RefundAmount': Decimal('0.00'),
}
)
# 构建本次更新的字段,使用 F 表达式进行原子累加
update_fields = {}
if action == 1: # 派发
update_fields['AssignedOrderCount'] = F('AssignedOrderCount') + 1
update_fields['AssignedAmount'] = F('AssignedAmount') + amount
elif action == 2: # 结算(成交)
update_fields['SettledOrderCount'] = F('SettledOrderCount') + 1
update_fields['SettledAmount'] = F('SettledAmount') + amount
elif action == 3: # 退款
update_fields['RefundOrderCount'] = F('RefundOrderCount') + 1
update_fields['RefundAmount'] = F('RefundAmount') + amount
else:
# 无效的行为类型,直接返回
return
# 执行原子更新
MerchantDailyStats.query.filter(id=stat.id).update(**update_fields)
def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
"""
根据操作行为更新管事每日统计(邀请打手、充值会员、订单/押金分红)
action: 1 = 邀请打手, 2 = 充值会员, 3 = 商家订单分红, 4 = 押金分红
"""
today = date.today()
with transaction.atomic():
stat, created = ManagerDailyStats.objects.select_for_update().get_or_create(
ManagerID=yonghuid,
Date=today,
defaults={
'ManagerID': yonghuid,
'Date': today,
'InvitedPlayerCount': 0,
'RechargedPlayerCount': 0,
'TotalIncome': Decimal('0.00'),
}
)
if action == 1:
# 邀请打手:无论新创建还是已存在,直接 +1
if created:
stat.InvitedPlayerCount = 1
stat.save()
else:
ManagerDailyStats.query.filter(id=stat.id).update(
InvitedPlayerCount=F('InvitedPlayerCount') + 1
)
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return
if created:
stat.RechargedPlayerCount = 1
stat.TotalIncome = amount
stat.save()
else:
ManagerDailyStats.query.filter(id=stat.id).update(
RechargedPlayerCount=F('RechargedPlayerCount') + 1,
TotalIncome=F('TotalIncome') + amount
)
elif action in (3, 4):
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return
if created:
stat.TotalIncome = amount
stat.save()
else:
ManagerDailyStats.query.filter(id=stat.id).update(
TotalIncome=F('TotalIncome') + amount
)
def update_guanshi_xufei_daily(yonghuid, xufei_jine=Decimal('0.00')):
"""
管事续费统计(单独表)
用法: update_guanshi_xufei_daily('300001', Decimal('30'))
"""
today = date.today()
with transaction.atomic():
stat, created = ManagerRenewalDailyStats.objects.select_for_update().get_or_create(
ManagerID=yonghuid,
Date=today,
defaults={
'ManagerID': yonghuid,
'Date': today,
'RenewalTotal': 1,
'RenewalRevenue': xufei_jine,
}
)
if not created:
ManagerRenewalDailyStats.query.filter(id=stat.id).update(
RenewalTotal=F('RenewalTotal') + 1,
RenewalRevenue=F('RenewalRevenue') + xufei_jine
)
def update_zuzhang_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
"""
根据操作行为更新组长每日统计(邀请管事、分佣收入)
参数:
yonghuid: 组长用户ID
action: 操作类型
1 = 邀请管事(次数+1无金额
2 = 分佣收入(累加金额)
amount: 分佣金额Decimal仅在 action=2 时有效
"""
today = date.today()
with transaction.atomic():
stat, created = LeaderDailyStats.objects.select_for_update().get_or_create(
LeaderID=yonghuid,
Date=today,
defaults={
'LeaderID': yonghuid,
'Date': today,
'InvitedManagerCount': 0,
'TotalIncome': Decimal('0.00'),
'CommissionAmount': Decimal('0.00'),
}
)
if created:
# 新记录直接设置初始值
if action == 1:
stat.InvitedManagerCount = 1
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
stat.TotalIncome = amount
stat.CommissionAmount = amount
stat.save()
else:
# 已存在记录,使用 F 表达式原子累加
if action == 1:
LeaderDailyStats.query.filter(id=stat.id).update(
InvitedManagerCount=F('InvitedManagerCount') + 1
)
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount > 0:
LeaderDailyStats.query.filter(id=stat.id).update(
TotalIncome=F('TotalIncome') + amount,
CommissionAmount=F('CommissionAmount') + amount
)
def update_tixian_daily_stat(leixing, amount):
"""
更新每日提现统计(平台维度)
leixing: 1=打手, 2=管事, 3=组长
amount: 本次提现金额(申请金额,含手续费)
"""
today = date.today()
with transaction.atomic():
stat, created = WithdrawalDailyStats.objects.select_for_update().get_or_create(
Date=today,
WithdrawalType=leixing,
defaults={'total_amount': amount}
)
if not created:
WithdrawalDailyStats.query.filter(id=stat.id).update(
total_amount=F('total_amount') + amount
)
# 罚款权限(与 permission 表一致)
# 66693a/b/c + 66694c按身份管理罚款99933abs罚款申请通用可查看列表并操作各身份
_DEFAULT_FADAN_PERM_TO_SHENFEN = {
'66693a': 1, # 罚款管理-打手
'66693c': 2, # 罚款管理-管事
'66693b': 3, # 罚款管理-商家
'66694c': 4, # 罚款管理-组长
}
_FADAN_GENERIC_PERMS = (
'99933abs', # 罚款申请权限
)
_SHENFEN_KEYWORDS = {
'打手': 1,
'管事': 2,
'商家': 3,
'组长': 4,
}
SHENFEN_PROFILE_MAP = {
1: 'dashou_profile',
2: 'guanshi_profile',
3: 'shop_profile',
4: 'zuzhang_profile',
}
@lru_cache(maxsize=1)
def _get_fadan_permission_config():
"""
加载罚款相关权限码(硬编码 + permission 表按 perm_name 补充)。
仅匹配 perm_name 含「罚款/罚单」,避免 qtczcaiwu 等描述里带「罚款」的财务权限误入。
"""
perm_map = dict(_DEFAULT_FADAN_PERM_TO_SHENFEN)
generic_codes = list(_FADAN_GENERIC_PERMS)
try:
rows = Permission.query.filter(
Q(perm_name__icontains='罚款') | Q(perm_name__icontains='罚单')
).values('perm_code', 'perm_name')
for row in rows:
code = row['perm_code']
name = row['perm_name'] or ''
matched_sf = None
for kw, sf in _SHENFEN_KEYWORDS.items():
if kw in name:
matched_sf = sf
break
if matched_sf is not None:
perm_map[code] = matched_sf
elif code not in generic_codes:
generic_codes.append(code)
except Exception as e:
logger.warning(f"从 permission 表加载罚款权限失败,使用硬编码映射: {e}")
return perm_map, tuple(generic_codes)
# 兼容旧引用
PERMISSION_TO_SHENFEN = _DEFAULT_FADAN_PERM_TO_SHENFEN
def has_fadan_view_permission(permissions, account_id=''):
"""列表/统计:拥有任一罚款权限或超级管理员权限即可查看。"""
if '000001' in permissions:
return True
perm_map, generic_codes = _get_fadan_permission_config()
all_codes = set(perm_map.keys()) | set(generic_codes)
if any(p in permissions for p in all_codes):
return True
logger.warning(
f"罚款查看权限不足 account={account_id} "
f"user_perms={permissions} need_any={sorted(all_codes)}"
)
return False
def check_fadan_permission(permissions, shenfen):
"""创建/处理:需拥有对应身份的罚款权限,或通用罚款权限,或超级管理员权限。"""
if '000001' in permissions:
return True
if any(p in permissions for p in _get_fadan_permission_config()[1]):
return True
shenfen = int(shenfen)
perm_map, _ = _get_fadan_permission_config()
for perm, sf in perm_map.items():
if sf == shenfen and perm in permissions:
return True
# houtai/utils.py
import logging
import json
from functools import lru_cache
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q
from rest_framework.response import Response
from .models import AbnormalUserLog, PlayerDailyStats, MerchantDailyStats, \
ManagerRenewalDailyStats, ManagerDailyStats, LeaderDailyStats, WithdrawalDailyStats
from gvsdsdk.models import UserRole as SdkUserRole, RolePermission as SdkRolePermission, Permission as SdkPermission
from django.db import transaction
from django.db.models import F
from datetime import date
from decimal import Decimal
from gvsdsdk.fluent import db, func, FQ
logger = logging.getLogger(__name__)
def _get_real_ip(request):
"""获取真实IP考虑代理"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0].strip()
return ip, x_forwarded_for
else:
ip = request.META.get('REMOTE_ADDR')
return ip, ''
def _record_abnormal(account_id, request, attempt_type, detail=''):
"""记录异常操作(仅用于越权等攻击行为)"""
try:
real_ip, xff = _get_real_ip(request)
headers = {k: v for k, v in request.META.items() if k.startswith('HTTP_')}
headers_json = json.dumps(headers, ensure_ascii=False)[:2000]
request_params = json.dumps(request.query_params.dict(), ensure_ascii=False) if request.query_params else ''
request_body = request.body.decode('utf-8', errors='ignore')[:5000]
AbnormalUserLog.query.create(
account_id=account_id,
ip_address=real_ip,
real_ip=real_ip,
x_forwarded_for=xff,
user_agent=request.META.get('HTTP_USER_AGENT', ''),
request_method=request.method,
request_path=request.path,
request_params=request_params,
request_body=request_body,
request_headers=headers_json,
attempt_type=attempt_type,
detail=detail,
)
logger.warning(f"记录异常用户: {account_id} IP:{real_ip} 类型:{attempt_type}")
except Exception as e:
logger.error(f"记录异常日志失败: {e}")
def verify_kefu_permission(request, username_from_frontend=None):
"""
验证客服/管理员身份并返回该用户的所有权限列表基于新RBAC表
只有前端传递的username与当前登录用户不匹配时才记录异常并警告前端。
参数:
request: DRF request 对象
username_from_frontend: 前端传递的账号(用于防越权)
返回:
(kefu_obj, permissions_list) # 验证通过
(None, Response) # 验证失败直接返回Response
"""
current_user = request.user
# ---------- 1. 必须是客服或管理员 ----------
is_admin = current_user.UserType == 'admin' or current_user.IsSuperuser
if current_user.UserType != 'kefu' and not is_admin:
return None, Response({'code': 403, 'msg': '身份错误,非客服账号'}, status=403)
# ---------- 2. 客服扩展表存在且状态正常(管理员跳过) ----------
kefu = None
if not is_admin:
try:
kefu = current_user.KefuProfile
if kefu.zhuangtai != 1:
return None, Response({'code': 403, 'msg': '客服账号已被禁用'}, status=403)
except ObjectDoesNotExist:
return None, Response({'code': 403, 'msg': '客服账号不存在'}, status=403)
# ---------- 3. 越权检测只记录前端篡改username的情况 ----------
if username_from_frontend and current_user.Phone != username_from_frontend:
detail = f'前端username: {username_from_frontend},实际登录账号: {current_user.Phone}'
_record_abnormal(current_user.Phone, request, '越权尝试篡改username', detail=detail)
return None, Response({
'code': 403,
'msg': '身份错误,无权操作其他账号,相关操作已记录'
}, status=403)
# ---------- 4. 获取用户的所有权限 ----------
# 使用 gvsdsdk RBAC 表UserRole → RolePermission → Permission
try:
user_uuid = current_user.UserUUID
role_uuids = list(SdkUserRole.objects.filter(
UserUUID=user_uuid
).values_list('RoleUUID', flat=True))
if role_uuids:
perm_uuids = list(SdkRolePermission.objects.filter(
RoleUUID__in=role_uuids
).values_list('PermUUID', flat=True))
permissions = list(SdkPermission.objects.filter(
PermUUID__in=perm_uuids,
PermStatus=1
).values_list('PermCode', flat=True).distinct())
else:
permissions = []
except Exception:
permissions = []
# 超级用户自动拥有所有权限
if current_user.IsSuperuser and '000001' not in permissions:
permissions.insert(0, '000001')
if not permissions:
logger.warning(f"用户 {current_user.Phone} 没有任何权限,请检查角色分配")
return kefu, permissions
def update_dashou_daily_by_action(yonghuid, amount, action):
"""
根据行为类型更新打手每日统计(接单、成交、退款)
参数:
yonghuid: 打手用户ID
amount: 本单涉及的金额Decimal
action: 1 = 接单, 2 = 成交(结算), 3 = 退款
"""
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return # 金额非正不统计
today = date.today()
with transaction.atomic():
stat, created = PlayerDailyStats.objects.select_for_update().get_or_create(
PlayerID=yonghuid,
Date=today,
defaults={
'PlayerID': yonghuid,
'Date': today,
'AcceptedOrderTotal': 0,
'AcceptedAmount': Decimal('0.00'),
'CompletedOrderTotal': 0,
'CompletedAmount': Decimal('0.00'),
'RefundCount': 0,
'RefundAmount': Decimal('0.00'),
}
)
update_fields = {}
if action == 1: # 接单
update_fields['AcceptedOrderTotal'] = F('AcceptedOrderTotal') + 1
update_fields['AcceptedAmount'] = F('AcceptedAmount') + amount
elif action == 2: # 成交
update_fields['CompletedOrderTotal'] = F('CompletedOrderTotal') + 1
update_fields['CompletedAmount'] = F('CompletedAmount') + amount
elif action == 3: # 退款
update_fields['RefundCount'] = F('RefundCount') + 1
update_fields['RefundAmount'] = F('RefundAmount') + amount
else:
return # 无效行为
if update_fields:
PlayerDailyStats.query.filter(id=stat.id).update(**update_fields)
def update_shangjia_daily(yonghuid, amount, action):
"""
根据操作行为更新商家每日统计(派发、结算、退款)
参数:
yonghuid: 商家用户ID
amount: 当前订单涉及的金额Decimal
action: 操作类型1 = 派发2 = 结算成交3 = 退款
说明:
- 金额必须大于0否则不执行任何操作
- 使用 select_for_update + F() 表达式保证原子性和并发安全
- 每天每个商家只会有一条统计记录,不存在则自动创建
"""
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return # 金额非正,不统计
today = date.today()
with transaction.atomic():
# 获取或创建当天统计记录,同时加锁防止并发
stat, created = MerchantDailyStats.objects.select_for_update().get_or_create(
MerchantID=yonghuid,
Date=today,
defaults={
'MerchantID': yonghuid,
'Date': today,
'AssignedOrderCount': 0,
'AssignedAmount': Decimal('0.00'),
'SettledOrderCount': 0,
'SettledAmount': Decimal('0.00'),
'RefundOrderCount': 0,
'RefundAmount': Decimal('0.00'),
}
)
# 构建本次更新的字段,使用 F 表达式进行原子累加
update_fields = {}
if action == 1: # 派发
update_fields['AssignedOrderCount'] = F('AssignedOrderCount') + 1
update_fields['AssignedAmount'] = F('AssignedAmount') + amount
elif action == 2: # 结算(成交)
update_fields['SettledOrderCount'] = F('SettledOrderCount') + 1
update_fields['SettledAmount'] = F('SettledAmount') + amount
elif action == 3: # 退款
update_fields['RefundOrderCount'] = F('RefundOrderCount') + 1
update_fields['RefundAmount'] = F('RefundAmount') + amount
else:
# 无效的行为类型,直接返回
return
# 执行原子更新
MerchantDailyStats.query.filter(id=stat.id).update(**update_fields)
def update_guanshi_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
"""
根据操作行为更新管事每日统计(邀请打手、充值会员、订单/押金分红)
action: 1 = 邀请打手, 2 = 充值会员, 3 = 商家订单分红, 4 = 押金分红
"""
today = date.today()
with transaction.atomic():
stat, created = ManagerDailyStats.objects.select_for_update().get_or_create(
ManagerID=yonghuid,
Date=today,
defaults={
'ManagerID': yonghuid,
'Date': today,
'InvitedPlayerCount': 0,
'RechargedPlayerCount': 0,
'TotalIncome': Decimal('0.00'),
}
)
if action == 1:
# 邀请打手:无论新创建还是已存在,直接 +1
if created:
stat.InvitedPlayerCount = 1
stat.save()
else:
ManagerDailyStats.query.filter(id=stat.id).update(
InvitedPlayerCount=F('InvitedPlayerCount') + 1
)
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return
if created:
stat.RechargedPlayerCount = 1
stat.TotalIncome = amount
stat.save()
else:
ManagerDailyStats.query.filter(id=stat.id).update(
RechargedPlayerCount=F('RechargedPlayerCount') + 1,
TotalIncome=F('TotalIncome') + amount
)
elif action in (3, 4):
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount <= 0:
return
if created:
stat.TotalIncome = amount
stat.save()
else:
ManagerDailyStats.query.filter(id=stat.id).update(
TotalIncome=F('TotalIncome') + amount
)
def update_guanshi_xufei_daily(yonghuid, xufei_jine=Decimal('0.00')):
"""
管事续费统计(单独表)
用法: update_guanshi_xufei_daily('300001', Decimal('30'))
"""
today = date.today()
with transaction.atomic():
stat, created = ManagerRenewalDailyStats.objects.select_for_update().get_or_create(
ManagerID=yonghuid,
Date=today,
defaults={
'ManagerID': yonghuid,
'Date': today,
'RenewalTotal': 1,
'RenewalRevenue': xufei_jine,
}
)
if not created:
ManagerRenewalDailyStats.query.filter(id=stat.id).update(
RenewalTotal=F('RenewalTotal') + 1,
RenewalRevenue=F('RenewalRevenue') + xufei_jine
)
def update_zuzhang_daily_by_action(yonghuid, action, amount=Decimal('0.00')):
"""
根据操作行为更新组长每日统计(邀请管事、分佣收入)
参数:
yonghuid: 组长用户ID
action: 操作类型
1 = 邀请管事(次数+1无金额
2 = 分佣收入(累加金额)
amount: 分佣金额Decimal仅在 action=2 时有效
"""
today = date.today()
with transaction.atomic():
stat, created = LeaderDailyStats.objects.select_for_update().get_or_create(
LeaderID=yonghuid,
Date=today,
defaults={
'LeaderID': yonghuid,
'Date': today,
'InvitedManagerCount': 0,
'TotalIncome': Decimal('0.00'),
'CommissionAmount': Decimal('0.00'),
}
)
if created:
# 新记录直接设置初始值
if action == 1:
stat.InvitedManagerCount = 1
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
stat.TotalIncome = amount
stat.CommissionAmount = amount
stat.save()
else:
# 已存在记录,使用 F 表达式原子累加
if action == 1:
LeaderDailyStats.query.filter(id=stat.id).update(
InvitedManagerCount=F('InvitedManagerCount') + 1
)
elif action == 2:
amount = Decimal(str(amount)) if amount else Decimal('0.00')
if amount > 0:
LeaderDailyStats.query.filter(id=stat.id).update(
TotalIncome=F('TotalIncome') + amount,
CommissionAmount=F('CommissionAmount') + amount
)
def update_tixian_daily_stat(leixing, amount):
"""
更新每日提现统计(平台维度)
leixing: 1=打手, 2=管事, 3=组长
amount: 本次提现金额(申请金额,含手续费)
"""
today = date.today()
with transaction.atomic():
stat, created = WithdrawalDailyStats.objects.select_for_update().get_or_create(
Date=today,
WithdrawalType=leixing,
defaults={'total_amount': amount}
)
if not created:
WithdrawalDailyStats.query.filter(id=stat.id).update(
total_amount=F('total_amount') + amount
)
# 罚款权限(与 permission 表一致)
# 66693a/b/c + 66694c按身份管理罚款99933abs罚款申请通用可查看列表并操作各身份
_DEFAULT_FADAN_PERM_TO_SHENFEN = {
'66693a': 1, # 罚款管理-打手
'66693c': 2, # 罚款管理-管事
'66693b': 3, # 罚款管理-商家
'66694c': 4, # 罚款管理-组长
}
_FADAN_GENERIC_PERMS = (
'99933abs', # 罚款申请权限
)
_SHENFEN_KEYWORDS = {
'打手': 1,
'管事': 2,
'商家': 3,
'组长': 4,
}
SHENFEN_PROFILE_MAP = {
1: 'DashouProfile',
2: 'GuanshiProfile',
3: 'ShopProfile',
4: 'ZuzhangProfile',
}
@lru_cache(maxsize=1)
def _get_fadan_permission_config():
"""
加载罚款相关权限码(硬编码 + permission 表按 perm_name 补充)。
仅匹配 perm_name 含「罚款/罚单」,避免 qtczcaiwu 等描述里带「罚款」的财务权限误入。
"""
perm_map = dict(_DEFAULT_FADAN_PERM_TO_SHENFEN)
generic_codes = list(_FADAN_GENERIC_PERMS)
try:
rows = Permission.query.filter(
Q(perm_name__icontains='罚款') | Q(perm_name__icontains='罚单')
).values('perm_code', 'perm_name')
for row in rows:
code = row['perm_code']
name = row['perm_name'] or ''
matched_sf = None
for kw, sf in _SHENFEN_KEYWORDS.items():
if kw in name:
matched_sf = sf
break
if matched_sf is not None:
perm_map[code] = matched_sf
elif code not in generic_codes:
generic_codes.append(code)
except Exception as e:
logger.warning(f"从 permission 表加载罚款权限失败,使用硬编码映射: {e}")
return perm_map, tuple(generic_codes)
# 兼容旧引用
PERMISSION_TO_SHENFEN = _DEFAULT_FADAN_PERM_TO_SHENFEN
def has_fadan_view_permission(permissions, account_id=''):
"""列表/统计:拥有任一罚款权限或超级管理员权限即可查看。"""
if '000001' in permissions:
return True
perm_map, generic_codes = _get_fadan_permission_config()
all_codes = set(perm_map.keys()) | set(generic_codes)
if any(p in permissions for p in all_codes):
return True
logger.warning(
f"罚款查看权限不足 account={account_id} "
f"user_perms={permissions} need_any={sorted(all_codes)}"
)
return False
def check_fadan_permission(permissions, shenfen):
"""创建/处理:需拥有对应身份的罚款权限,或通用罚款权限,或超级管理员权限。"""
if '000001' in permissions:
return True
if any(p in permissions for p in _get_fadan_permission_config()[1]):
return True
shenfen = int(shenfen)
perm_map, _ = _get_fadan_permission_config()
for perm, sf in perm_map.items():
if sf == shenfen and perm in permissions:
return True
return False