diff --git a/backend/utils.py b/backend/utils.py index ea55e34..41c51ca 100644 --- a/backend/utils.py +++ b/backend/utils.py @@ -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 \ No newline at end of file diff --git a/backend/view.py b/backend/view.py index fe10864..ce7a7a2 100644 --- a/backend/view.py +++ b/backend/view.py @@ -322,13 +322,13 @@ class GetAdminUserListView(APIView): users = User.query.filter( user_type='kefu', - kefu_profile__isnull=False - ).select_related('kefu_profile') + KefuProfile__isnull=False + ).select_related('KefuProfile') if phone: users = users.filter(phone__icontains=phone) if nicheng: - users = users.filter(kefu_profile__nicheng__icontains=nicheng) + users = users.filter(KefuProfile__nicheng__icontains=nicheng) # 关键修正:先求值为列表,再传入 phone__in if role_code: @@ -365,11 +365,11 @@ class GetAdminUserListView(APIView): for r in user_roles ] data_list.append({ - 'phone': user.phone, - 'nicheng': user.kefu_profile.nicheng, + 'phone': user.Phone, + 'nicheng': user.KefuProfile.nicheng, 'roles': roles_data, - 'status': user.kefu_profile.zhuangtai, - 'create_time': user.create_time.strftime('%Y-%m-%d %H:%M:%S') if hasattr(user, 'create_time') else '', + 'status': user.KefuProfile.zhuangtai, + 'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S') if hasattr(user, 'UserCreateTime') else '', 'update_time': user.update_time.strftime('%Y-%m-%d %H:%M:%S') if hasattr(user, 'update_time') else '', }) @@ -440,11 +440,11 @@ class GetAdminRolesView(APIView): page_size = int(request.data.get('pageSize', 20)) # 基础查询:存在客服扩展表 - users = User.query.filter(kefu_profile__isnull=False).select_related('kefu_profile') + users = User.query.filter(KefuProfile__isnull=False).select_related('KefuProfile') if phone: users = users.filter(Phone__icontains=phone) if nicheng: - users = users.filter(kefu_profile__nicheng__icontains=nicheng) + users = users.filter(KefuProfile__nicheng__icontains=nicheng) if role_code: # 筛选拥有该角色的用户 user_ids_with_role = UserRole.query.filter(role__role_code=role_code).values_list('account_id', flat=True) @@ -461,14 +461,14 @@ class GetAdminRolesView(APIView): data_list = [] for user in page_obj: # 获取用户的所有角色 - user_roles = UserRole.query.filter(account_id=user.phone).select_related('role') + user_roles = UserRole.query.filter(account_id=user.Phone).select_related('role') roles_data = [{'role_code': ur.role.role_code, 'role_name': ur.role.role_name} for ur in user_roles] data_list.append({ - 'phone': user.phone, - 'nicheng': user.kefu_profile.nicheng, + 'phone': user.Phone, + 'nicheng': user.KefuProfile.nicheng, 'roles': roles_data, - 'status': user.kefu_profile.zhuangtai, - 'create_time': user.create_time.strftime('%Y-%m-%d %H:%M:%S'), + 'status': user.KefuProfile.zhuangtai, + 'create_time': user.UserCreateTime.strftime('%Y-%m-%d %H:%M:%S'), 'update_time': user.update_time.strftime('%Y-%m-%d %H:%M:%S'), }) @@ -508,7 +508,7 @@ class ModifyAdminUserView(APIView): try: target_user = User.query.get(Phone=target_phone) - target_kefu = target_user.kefu_profile + target_kefu = target_user.KefuProfile except User.DoesNotExist: return Response({'code': 404, 'msg': '用户不存在'}) except UserKefu.DoesNotExist: @@ -600,7 +600,7 @@ class AddAdminUserView(APIView): return Response({'code': 400, 'msg': '密码和二级密码至少6位'}) # 校验手机号是否已被客服占用 - if User.query.filter(Phone=phone, kefu_profile__isnull=False).exists(): + if User.query.filter(Phone=phone, KefuProfile__isnull=False).exists(): return Response({'code': 400, 'msg': '该账号已存在'}) # 生成唯一的 yonghuid(7位数字) @@ -743,8 +743,8 @@ class KefuGetDashouListView(APIView): for dashou in dashou_list: user = dashou.user result.append({ - 'yonghuid': user.yonghuid, - 'avatar': user.avatar or '', + 'yonghuid': user.UserUID, + 'avatar': user.Avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'nicheng': dashou.nicheng or '', @@ -799,19 +799,19 @@ class KefuGetDashouDetailView(APIView): # 4. 查询打手主表和扩展表 try: - user_main = User.query.select_related('dashou_profile').get( + user_main = User.query.select_related('DashouProfile').get( UserUID=uid ) except User.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}) try: - dashou = user_main.dashou_profile + dashou = user_main.DashouProfile except ObjectDoesNotExist: # 用户存在但未注册打手身份,返回基本信息 user_info = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': 0, 'zhanghaozhuangtai': 0, 'zhuangtai': 0, @@ -819,9 +819,9 @@ class KefuGetDashouDetailView(APIView): 'chenghao': '', 'dianhua': '', 'wechat': '', - 'phone': user_main.phone or '', - 'create_time': user_main.create_time, - 'ip': user_main.ip or '', + 'phone': user_main.Phone or '', + 'create_time': user_main.UserCreateTime, + 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, 'tuikuanliang': 0, @@ -850,8 +850,8 @@ class KefuGetDashouDetailView(APIView): # 5. 构建打手信息 user_info = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'zhuangtai': dashou.zhuangtai, @@ -859,9 +859,9 @@ class KefuGetDashouDetailView(APIView): 'chenghao': dashou.chenghao or '', 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', - 'phone': user_main.phone or '', - 'create_time': user_main.create_time, - 'ip': user_main.ip or '', + 'phone': user_main.Phone or '', + 'create_time': user_main.UserCreateTime, + 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, 'tuikuanliang': dashou.tuikuanliang, @@ -956,7 +956,7 @@ class KefuGetDashouDetailView(APIView): # 3. 查询打手主表和扩展表 try: # 使用 select_related 预加载 dashou_profile,减少数据库查询 - user_main = User.query.select_related('dashou_profile').get( + user_main = User.query.select_related('DashouProfile').get( UserUID=uid ) except User.DoesNotExist: @@ -965,12 +965,12 @@ class KefuGetDashouDetailView(APIView): # 验证用户类型是否为打手(可选,但建议验证) # 如果该用户没有打手扩展表,则返回空信息(但前端会显示未注册) try: - dashou = user_main.dashou_profile + dashou = user_main.DashouProfile except ObjectDoesNotExist: # 用户存在但未注册打手身份,返回基本信息 user_info = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': 0, 'zhanghaozhuangtai': 0, 'zhuangtai': 0, @@ -978,9 +978,9 @@ class KefuGetDashouDetailView(APIView): 'chenghao': '', 'dianhua': '', 'wechat': '', - 'phone': user_main.phone or '', - 'create_time': user_main.create_time, - 'ip': user_main.ip or '', + 'phone': user_main.Phone or '', + 'create_time': user_main.UserCreateTime, + 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, 'tuikuanliang': 0, @@ -1005,8 +1005,8 @@ class KefuGetDashouDetailView(APIView): # 4. 构建打手信息 user_info = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'zhuangtai': dashou.zhuangtai, @@ -1014,9 +1014,9 @@ class KefuGetDashouDetailView(APIView): 'chenghao': dashou.chenghao or '', 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', - 'phone': user_main.phone or '', - 'create_time': user_main.create_time, - 'ip': user_main.ip or '', + 'phone': user_main.Phone or '', + 'create_time': user_main.UserCreateTime, + 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, 'tuikuanliang': dashou.tuikuanliang, @@ -1107,7 +1107,7 @@ class KefuUpdateDashouView(APIView): # 3. 查询打手 try: dashou_user = User.query.get(UserUID=dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile except User.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}) except UserDashou.DoesNotExist: @@ -1137,7 +1137,7 @@ class KefuUpdateDashouView(APIView): after = dashou_profile.yue Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, xiugaitijiao=after, xiugaitijiaoq=before['yue'], @@ -1153,7 +1153,7 @@ class KefuUpdateDashouView(APIView): after = dashou_profile.yue Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, xiugaitijiao=after, xiugaitijiaoq=before['yue'], @@ -1172,7 +1172,7 @@ class KefuUpdateDashouView(APIView): after = dashou_profile.yajin Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, yajin=after, yyajin=before['yajin'], @@ -1188,7 +1188,7 @@ class KefuUpdateDashouView(APIView): after = dashou_profile.yajin Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, yajin=after, yyajin=before['yajin'], @@ -1207,7 +1207,7 @@ class KefuUpdateDashouView(APIView): after = dashou_profile.jifen Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, jifen=after, yjifen=before['jifen'], @@ -1223,7 +1223,7 @@ class KefuUpdateDashouView(APIView): after = dashou_profile.jifen Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, jifen=after, yjifen=before['jifen'], @@ -1241,7 +1241,7 @@ class KefuUpdateDashouView(APIView): dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改账号状态为{new_status}', ) @@ -1257,7 +1257,7 @@ class KefuUpdateDashouView(APIView): dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改在线状态为{new_status}', ) @@ -1273,7 +1273,7 @@ class KefuUpdateDashouView(APIView): dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改接单状态为{new_status}', ) @@ -1306,7 +1306,7 @@ class KefuUpdateDashouView(APIView): record.save() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, huiyuants=days, huiyuan_id=huiyuan_id, @@ -1326,7 +1326,7 @@ class KefuUpdateDashouView(APIView): record.delete() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, huiyuan_id=huiyuan_id, qitashuoming=f'移除会员 {huiyuan_id}', @@ -1345,7 +1345,7 @@ class KefuUpdateDashouView(APIView): dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改称号:{before["chenghao"] or "无"} → {new_chenghao}', ) @@ -1479,8 +1479,8 @@ class KefuGetShangjiaListView(APIView): for shangjia in shangjia_list: user = shangjia.user result.append({ - 'yonghuid': user.yonghuid, - 'avatar': user.avatar or '', + 'yonghuid': user.UserUID, + 'avatar': user.Avatar or '', 'nicheng': shangjia.nicheng or '', 'yue': str(shangjia.yue), # 转换为字符串,避免前端精度问题 'fabu': shangjia.fabu, @@ -1545,8 +1545,8 @@ class KefuGetShangjiaDetailView(APIView): user = shangjia.user data = { - 'yonghuid': user.yonghuid, - 'avatar': user.avatar or '', + 'yonghuid': user.UserUID, + 'avatar': user.Avatar or '', 'nicheng': shangjia.nicheng, 'zhuangtai': shangjia.zhuangtai, # 1正常,2禁用 'dianhua': shangjia.dianhua, @@ -2882,14 +2882,14 @@ class GetGuanliListView(APIView): # 构建查询:从 User 开始,关联 UserGuanshi 和 UserBoss(左连接获取昵称) qs = User.query.filter( - guanshi_profile__isnull=False # 确保是管事 - ).select_related('guanshi_profile').select_related('boss_profile') + GuanshiProfile__isnull=False # 确保是管事 + ).select_related('GuanshiProfile').select_related('BossProfile') # 关键词搜索(用户ID 或 昵称来自 boss_profile.nickname) if keyword: qs = qs.filter( Q(UserUID__icontains=keyword) | - Q(boss_profile__nickname__icontains=keyword) + Q(BossProfile__nickname__icontains=keyword) ) # 余额筛选(使用 guanshi_profile.yue) @@ -2897,9 +2897,9 @@ class GetGuanliListView(APIView): try: balance_val = Decimal(str(balance_value)) if balance_op == 'gte': - qs = qs.filter(guanshi_profile__yue__gte=balance_val) + qs = qs.filter(GuanshiProfile__yue__gte=balance_val) elif balance_op == 'lte': - qs = qs.filter(guanshi_profile__yue__lte=balance_val) + qs = qs.filter(GuanshiProfile__yue__lte=balance_val) else: return Response({'code': 400, 'msg': '余额比较符无效'}) except: @@ -2910,9 +2910,9 @@ class GetGuanliListView(APIView): try: commission_val = Decimal(str(commission_value)) if commission_op == 'gte': - qs = qs.filter(guanshi_profile__chongzhifenrun__gte=commission_val) + qs = qs.filter(GuanshiProfile__chongzhifenrun__gte=commission_val) elif commission_op == 'lte': - qs = qs.filter(guanshi_profile__chongzhifenrun__lte=commission_val) + qs = qs.filter(GuanshiProfile__chongzhifenrun__lte=commission_val) else: return Response({'code': 400, 'msg': '分佣比较符无效'}) except: @@ -2923,9 +2923,9 @@ class GetGuanliListView(APIView): try: invite_val = int(invite_count_value) if invite_count_op == 'gte': - qs = qs.filter(guanshi_profile__yaogingshuliang__gte=invite_val) + qs = qs.filter(GuanshiProfile__yaogingshuliang__gte=invite_val) elif invite_count_op == 'lte': - qs = qs.filter(guanshi_profile__yaogingshuliang__lte=invite_val) + qs = qs.filter(GuanshiProfile__yaogingshuliang__lte=invite_val) else: return Response({'code': 400, 'msg': '邀请数量比较符无效'}) except: @@ -2937,7 +2937,7 @@ class GetGuanliListView(APIView): status_val = int(status) if status_val not in (0, 1): raise ValueError - qs = qs.filter(guanshi_profile__zhuangtai=status_val) + qs = qs.filter(GuanshiProfile__zhuangtai=status_val) except: return Response({'code': 400, 'msg': '状态值无效'}) @@ -2951,11 +2951,11 @@ class GetGuanliListView(APIView): # 构建返回数据 result = [] for user in users: - guanshi = user.guanshi_profile - boss = user.boss_profile if hasattr(user, 'boss_profile') else None + guanshi = user.GuanshiProfile + boss = user.BossProfile if hasattr(user, 'BossProfile') else None result.append({ - 'yonghuid': user.yonghuid, - 'avatar': user.avatar or '', + 'yonghuid': user.UserUID, + 'avatar': user.Avatar or '', 'nickname': boss.nickname if boss else '', 'zhuangtai': guanshi.zhuangtai, 'yaogingshuliang': guanshi.yaogingshuliang, @@ -3010,9 +3010,9 @@ class GetGuanliDetailView(APIView): # 查询用户主表和管事扩展表 try: - user = User.query.select_related('guanshi_profile', 'boss_profile').get(UserUID=yonghuid) - guanshi = user.guanshi_profile - boss = user.boss_profile if hasattr(user, 'boss_profile') else None + user = User.query.select_related('GuanshiProfile', 'BossProfile').get(UserUID=yonghuid) + guanshi = user.GuanshiProfile + boss = user.BossProfile if hasattr(user, 'BossProfile') else None except User.DoesNotExist: return Response({'code': 404, 'msg': '管事不存在'}) except UserGuanshi.DoesNotExist: @@ -3020,9 +3020,9 @@ class GetGuanliDetailView(APIView): # 基础信息 base_data = { - 'yonghuid': user.yonghuid, + 'yonghuid': user.UserUID, 'nickname': boss.nickname if boss else '', - 'avatar': user.avatar or '', + 'avatar': user.Avatar or '', 'dianhua': guanshi.dianhua or '', 'wechat': guanshi.wechat or '', 'zhuangtai': guanshi.zhuangtai, @@ -3132,8 +3132,8 @@ class UpdateGuanliView(APIView): # 查询管事对象(不在事务外加锁,事务内加锁) try: user = User.query.get(UserUID=yonghuid) - guanshi = user.guanshi_profile - boss = user.boss_profile if hasattr(user, 'boss_profile') else None + guanshi = user.GuanshiProfile + boss = user.BossProfile if hasattr(user, 'BossProfile') else None except User.DoesNotExist: return Response({'code': 404, 'msg': '用户不存在'}) except UserGuanshi.DoesNotExist: @@ -3143,8 +3143,8 @@ class UpdateGuanliView(APIView): with transaction.atomic(): # 重新获取加锁对象 user = User.objects.select_for_update().get(UserUID=yonghuid) - guanshi = user.guanshi_profile - boss = user.boss_profile if hasattr(user, 'boss_profile') else None + guanshi = user.GuanshiProfile + boss = user.BossProfile if hasattr(user, 'BossProfile') else None # ---------- 1. 基础信息修改(需要4400a)---------- nickname = request.data.get('nickname') @@ -3176,7 +3176,7 @@ class UpdateGuanliView(APIView): except: pass if avatar is not None: - user.avatar = avatar + user.Avatar = avatar user.save() guanshi.save() @@ -3559,7 +3559,7 @@ class GetZuzhangListView(APIView): # 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}") + logger.warning(f"用户 {request.user.Phone} 尝试访问组长列表但缺少必要权限,已有权限: {permissions}") return Response({'code': 403, 'msg': '您无权访问组长管理页面'}) # 4. 提取请求参数(分页 + 筛选) @@ -3579,7 +3579,7 @@ class GetZuzhangListView(APIView): commission_value = request.data.get('commission_value') # 5. 构建基础 QuerySet(预加载关联表) - queryset = UserZuzhang.query.select_related('user', 'user__boss_profile').all() + queryset = UserZuzhang.query.select_related('user', 'user__BossProfile').all() # 6. 应用筛选条件(使用 ORM 安全过滤) if keyword: @@ -3633,12 +3633,12 @@ class GetZuzhangListView(APIView): for zu in paged_queryset: user = zu.user nickname = '' - if hasattr(user, 'boss_profile') and user.boss_profile: - nickname = user.boss_profile.nickname or '' + if hasattr(user, 'BossProfile') and user.BossProfile: + nickname = user.BossProfile.nickname or '' data_list.append({ - 'yonghuid': user.yonghuid, - 'avatar': user.avatar or '', + 'yonghuid': user.UserUID, + 'avatar': user.Avatar or '', 'nickname': nickname, 'fenyong_zonge': float(zu.fenyong_zonge), 'ketixian_jine': float(zu.ketixian_jine), @@ -3702,9 +3702,9 @@ class GetZuzhangDetailView(APIView): # 查询组长主表、扩展表、老板扩展表(昵称) try: - user = User.query.select_related('zuzhang_profile', 'boss_profile').get(UserUID=yonghuid) - zuzhang = user.zuzhang_profile - boss = user.boss_profile if hasattr(user, 'boss_profile') else None + 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: @@ -3712,9 +3712,9 @@ class GetZuzhangDetailView(APIView): # 基础信息 base_data = { - 'yonghuid': user.yonghuid, + 'yonghuid': user.UserUID, 'nickname': boss.nickname if boss else '', - 'avatar': user.avatar or '', + 'avatar': user.Avatar or '', 'dianhua': '', # 组长表无电话字段?组长扩展表没有电话/微信,但前端可能需要,可从User关联?实际上组长表无电话字段,这里留空或从其他表获取 'wechat': '', 'zhuangtai': zuzhang.zhuangtai, @@ -3826,8 +3826,8 @@ class UpdateZuzhangView(APIView): # 查询组长对象 try: user = User.query.get(UserUID=yonghuid) - zuzhang = user.zuzhang_profile - boss = user.boss_profile if hasattr(user, 'boss_profile') else None + zuzhang = user.ZuzhangProfile + boss = user.BossProfile if hasattr(user, 'BossProfile') else None except User.DoesNotExist: return Response({'code': 404, 'msg': '用户不存在'}) except UserZuzhang.DoesNotExist: @@ -3861,7 +3861,7 @@ class UpdateZuzhangView(APIView): boss.nickname = nickname boss.save() if avatar: - user.avatar = avatar + user.Avatar = avatar user.save() # 组长表没有电话/微信字段,如需存储可暂存到User或忽略 # 这里不处理 @@ -5944,7 +5944,7 @@ class FaKuanChuLiView(APIView): return Response({'code': 400, 'msg': '无效的操作类型'}) # 记录处理者(使用 request.user 的 phone) - penalty.ProcessorID = request.user.phone + penalty.ProcessorID = request.user.Phone penalty.ProcessorIdentity = 2 # 这里假设处理者是售后身份,可根据实际业务获取 penalty.save() @@ -6033,7 +6033,7 @@ class FaKuanChuangJianView(APIView): penalty = Penalty.query.create( PenalizedUserID=PenalizedUserID, Identity=shenfen, - ApplicantID=request.user.phone, # 申请人(客服)手机号 + ApplicantID=request.user.Phone, # 申请人(客服)手机号 ApplicantIdentity=2, # 售后身份 Reason=Reason, FineAmount=FineAmount, @@ -6134,7 +6134,7 @@ class FineApplyView(APIView): with transaction.atomic(): penalty = Penalty.query.create( PenalizedUserID=dashou_id, - ApplicantID=request.user.phone, + ApplicantID=request.user.Phone, Identity=1, # 被处罚人身份:1 打手 Reason=Reason, FineAmount=FineAmount, @@ -6204,7 +6204,7 @@ class PunishDashouView(APIView): # 查询打手 try: dashou_user = User.query.get(UserUID=dashou_id) - dashou = dashou_user.dashou_profile + dashou = dashou_user.DashouProfile except User.DoesNotExist: return Response({'code': 404, 'msg': '打手用户不存在'}) except ObjectDoesNotExist: @@ -7984,7 +7984,7 @@ class KhgglView(APIView): filter_bankuai_name = request.data.get('bankuai_name', '').strip() # 基础查询:所有审核官,预加载用户及打手扩展表 - queryset = UserShenheguan.query.select_related('user__dashou_profile') + queryset = UserShenheguan.query.select_related('user__DashouProfile') if filter_yonghuid: queryset = queryset.filter(user__UserUID=filter_yonghuid) @@ -8015,7 +8015,7 @@ class KhgglView(APIView): tag_ids = Chenghao.query.filter(bankuai=bk, leixing='dashou').values_list('id', flat=True) count = YonghuChenghao.query.filter( chenghao_id__in=tag_ids, - yonghu__dashou_profile__isnull=False + yonghu__DashouProfile__isnull=False ).values('yonghu').distinct().count() bankuai_dashou_count_map[bk.bankuai_id] = count @@ -8042,7 +8042,7 @@ class KhgglView(APIView): for sg in shenheguan_list: user = sg.user # 获取该审核官关联的板块 - bk_qs = KaoheguanBankuai.query.filter(kaoheguan_id=user.yonghuid).select_related('bankuai') + bk_qs = KaoheguanBankuai.query.filter(kaoheguan_id=user.UserUID).select_related('bankuai') bankuai_info = [] for bk in bk_qs: bid = bk.bankuai.bankuai_id @@ -8052,12 +8052,12 @@ class KhgglView(APIView): 'dashou_count': bankuai_dashou_count_map.get(bid, 0) }) - dashou_nick = user.dashou_profile.nicheng if hasattr(user, 'dashou_profile') and user.dashou_profile else '' + dashou_nick = user.DashouProfile.nicheng if hasattr(user, 'DashouProfile') and user.DashouProfile else '' data_list.append({ - 'yonghuid': user.yonghuid, - 'avatar': user.avatar or '', - 'phone': user.phone or '', + 'yonghuid': user.UserUID, + 'avatar': user.Avatar or '', + 'phone': user.Phone or '', 'zhuangtai': sg.zhuangtai, 'shenhe_zhuangtai': sg.shenhe_zhuangtai, 'shenhe_zongshu': sg.shenhe_zongshu, @@ -8231,7 +8231,7 @@ class ZxkfghdsView(APIView): shangjia_id = shangjia_ext.MerchantID # 获取商家昵称用于新订单扩展表 shangjia_user = User.query.get(UserUID=shangjia_id) - shangjia_nicheng = shangjia_user.shop_profile.nicheng or f"商家{shangjia_id}" + shangjia_nicheng = shangjia_user.ShopProfile.nicheng or f"商家{shangjia_id}" except (ObjectDoesNotExist, UserShangjia.DoesNotExist): logger.error(f"商家信息缺失,订单: {OrderID}") return Response({'code': 500, 'msg': '商家数据异常'}) @@ -8240,7 +8240,7 @@ class ZxkfghdsView(APIView): with transaction.atomic(): # 5.1 订单状态改为已退款 old_order.Status = 5 - old_order.AssignedCS = current_user.phone + old_order.AssignedCS = current_user.Phone old_order.save() # 5.2 处理接单打手(若有) @@ -8248,7 +8248,7 @@ class ZxkfghdsView(APIView): if dashou_id: try: dashou_user = User.query.get(UserUID=dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.tuikuanliang += 1 # 退款次数+1 dashou_profile.zhuangtai = 1 # 设为空闲 dashou_profile.save() @@ -8261,7 +8261,7 @@ class ZxkfghdsView(APIView): defaults={ 'PlayerID': dashou_id or '', 'ApplicantID': '', - 'ProcessorID': current_user.phone, + 'ProcessorID': current_user.Phone, 'Reason': '客服更换打手退款', 'ApplyStatus': 1, 'Amount': old_order.Amount or 0, diff --git a/config/views.py b/config/views.py index 95e12af..3a031ed 100644 --- a/config/views.py +++ b/config/views.py @@ -185,7 +185,7 @@ class AdminConfigQueryView(APIView): user_main = request.user # 验证账号:比较user_main的phone与前端传递的zhanghao - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response({ 'code': 1, 'msg': '账号不匹配', @@ -193,7 +193,7 @@ class AdminConfigQueryView(APIView): }, status=status.HTTP_401_UNAUTHORIZED) # 验证用户类型是否为管理员 - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response({ 'code': 1, 'msg': '无权限', @@ -202,7 +202,7 @@ class AdminConfigQueryView(APIView): # 验证管理员扩展表是否存在 try: - admin_profile = user_main.admin_profile + admin_profile = user_main.AdminProfile except AdminProfile.DoesNotExist: return Response({ 'code': 1, @@ -307,14 +307,14 @@ class AdminUploadImageView(APIView): # 验证用户权限 user_main = request.user - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response({ 'code': 1, 'msg': '账号不匹配', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response({ 'code': 1, 'msg': '无权限', @@ -322,7 +322,7 @@ class AdminUploadImageView(APIView): }, status=status.HTTP_401_UNAUTHORIZED) try: - admin_profile = user_main.admin_profile + admin_profile = user_main.AdminProfile except AdminProfile.DoesNotExist: return Response({ 'code': 1, @@ -419,14 +419,14 @@ class AdminUpdateConfigView(APIView): # 验证用户权限 user_main = request.user - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response({ 'code': 1, 'msg': '账号不匹配', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response({ 'code': 1, 'msg': '无权限', @@ -434,7 +434,7 @@ class AdminUpdateConfigView(APIView): }, status=status.HTTP_401_UNAUTHORIZED) try: - admin_profile = user_main.admin_profile + admin_profile = user_main.AdminProfile except AdminProfile.DoesNotExist: return Response({ 'code': 1, @@ -817,7 +817,7 @@ class DashouPaihangView(APIView): dashou_nicheng = dashou.nicheng or '未设置昵称' # 获取头像(相对URL) - touxiang_url = user_main.avatar or '' + touxiang_url = user_main.Avatar or '' # 获取收入金额 if leixing == 1: @@ -826,7 +826,7 @@ class DashouPaihangView(APIView): shouyi_jine = dashou.jinyueshouyi paihang_list.append({ - 'uid': user_main.yonghuid, + 'uid': user_main.UserUID, 'touxiang': touxiang_url, 'nicheng': dashou_nicheng, 'jine': float(shouyi_jine), # 转换为float方便前端处理 @@ -884,7 +884,7 @@ class DashouPaihangView(APIView): guanshi_nicheng = boss_profile.nickname if boss_profile else '未设置昵称' # 获取头像(相对URL) - touxiang_url = user_main.avatar or '' + touxiang_url = user_main.Avatar or '' # 获取数量 if leixing == 1: @@ -893,7 +893,7 @@ class DashouPaihangView(APIView): chongzhi_shuliang = guanshi.jinyuechongzhi paihang_list.append({ - 'uid': user_main.yonghuid, + 'uid': user_main.UserUID, 'touxiang': touxiang_url, 'nicheng': guanshi_nicheng, 'jine': float(chongzhi_shuliang), # 这里jine字段实际存储的是数量 @@ -951,7 +951,7 @@ class DashouPaihangView(APIView): shangjia_nicheng = boss_profile.nickname if boss_profile else '未设置昵称' # 获取头像(相对URL) - touxiang_url = user_main.avatar or '' + touxiang_url = user_main.Avatar or '' # 获取流水金额 if leixing == 1: @@ -960,7 +960,7 @@ class DashouPaihangView(APIView): liushui_jine = shangjia.jinyueliushui paihang_list.append({ - 'uid': user_main.yonghuid, + 'uid': user_main.UserUID, 'touxiang': touxiang_url, 'nicheng': shangjia_nicheng, 'jine': float(liushui_jine), @@ -1016,7 +1016,7 @@ class GetAdminZhanghaoView(APIView): user_type = getattr(user, 'user_type', '') if user_type != 'admin': # 检查是否有管理员扩展表 - if not hasattr(user, 'admin_profile'): + if not hasattr(user, 'AdminProfile'): return Response({ 'code': 403, 'message': '无管理员权限', @@ -1032,12 +1032,12 @@ class GetAdminZhanghaoView(APIView): um.id, um.phone, um.yonghuid, - um.user_type, + um.UserType, COALESCE(COUNT(xjl.id), 0) as jilushuliang FROM user_main um LEFT JOIN xiugaijilu xjl ON um.phone = xjl.xiugaiid - WHERE um.user_type = 'admin' - GROUP BY um.id, um.phone, um.yonghuid, um.user_type + WHERE um.UserType = 'admin' + GROUP BY um.id, um.phone, um.yonghuid, um.UserType ORDER BY um.create_time DESC """ @@ -1116,7 +1116,7 @@ class GetXiugaiJiluView(APIView): # 🔥 2. 验证是否为管理员(user_type='admin'字符串) user_type = getattr(user, 'user_type', '') if user_type != 'admin': - if not hasattr(user, 'admin_profile'): + if not hasattr(user, 'AdminProfile'): return Response({ 'code': 403, 'message': '无管理员权限', @@ -1330,7 +1330,7 @@ class ShangjiaMobanListView(APIView): def post(self, request): try: user = request.user - yonghu_id = user.yonghuid + yonghu_id = user.UserUID leixing_id = request.data.get('shangpinTypeId') if not leixing_id: @@ -1463,7 +1463,7 @@ class ShangjiaTianjiaMobanView(APIView): try: # 1. 获取当前商家用户ID user = request.user - yonghu_id = user.yonghuid + yonghu_id = user.UserUID # 2. 接收并清洗参数,避免类型错误 leixing_id = request.data.get('shangpinTypeId') @@ -1590,7 +1590,7 @@ class ShangjiaShanchuMobanView(APIView): def post(self, request): try: user = request.user - yonghu_id = user.yonghuid + yonghu_id = user.UserUID moban_id = request.data.get('mobanId') leixing_id = request.data.get('shangpinTypeId') @@ -1627,7 +1627,7 @@ class ShangjiaGengxinMobanView(APIView): def post(self, request): try: user = request.user - yonghu_id = user.yonghuid + yonghu_id = user.UserUID moban_id = request.data.get('mobanId') leixing_id = request.data.get('shangpinTypeId') @@ -1764,7 +1764,7 @@ class ShangjiaGenerateLinkView(APIView): # 商家信息 try: - shangjia = user.shop_profile + shangjia = user.ShopProfile except UserShangjia.DoesNotExist: return Response({'code': 403, 'msg': '商家信息未完善', 'data': {}}, status=403) if shangjia.zhuangtai != 1: @@ -1778,7 +1778,7 @@ class ShangjiaGenerateLinkView(APIView): # 查询模板 try: - moban = ShangjiaMoban.query.get(id=moban_id, yonghu_id=user.yonghuid, leixing_id=shangpin_type_id) + moban = ShangjiaMoban.query.get(id=moban_id, yonghu_id=user.UserUID, leixing_id=shangpin_type_id) except ShangjiaMoban.DoesNotExist: return Response({'code': 400, 'msg': '模板不存在', 'data': {}}, status=400) @@ -1835,7 +1835,7 @@ class ShangjiaGenerateLinkView(APIView): with transaction.atomic(): dingdan_id = generate_dingdan_id() - secure_token = generate_secure_token(dingdan_id, user.yonghuid) + secure_token = generate_secure_token(dingdan_id, user.UserUID) # 创建订单主表 dingdan = Order.query.create( @@ -1849,15 +1849,15 @@ class ShangjiaGenerateLinkView(APIView): huiyuan_id=final_huiyuan_id, yongjin=final_yongjin, Description=moban.moban_jieshao[:2000], - user1_id=f"Sj{user.yonghuid}", - ImageURL=user.avatar or '', + user1_id=f"Sj{user.UserUID}", + ImageURL=user.Avatar or '', ) # 商家扩展表 MerchantOrderExt.query.create( Order=dingdan, - MerchantID=user.yonghuid, - MerchantNickname=shangjia.nicheng or f"商家{user.yonghuid}", + MerchantID=user.UserUID, + MerchantNickname=shangjia.nicheng or f"商家{user.UserUID}", ) # 关联标签 @@ -1881,7 +1881,7 @@ class ShangjiaGenerateLinkView(APIView): # 生成链接并保存 link_url = generate_link_url(secure_token) lianjie_obj = ShangjiaLianjie.query.create( - yonghu_id=user.yonghuid, + yonghu_id=user.UserUID, leixing_id=shangpin_type_id, moban_id=moban_id, lianjie=link_url, @@ -1926,7 +1926,7 @@ class ShangjiaLianjieListView(APIView): def post(self, request): try: user = request.user - yonghu_id = user.yonghuid + yonghu_id = user.UserUID moban_id = request.data.get('mobanId') used = int(request.data.get('used', 0)) # 0未使用,1已使用 @@ -2192,7 +2192,7 @@ class KehuGetDingdanLianjieView(APIView): shangjia_nicheng = '' try: user_main = User.query.get(UserUID=lianjie_obj.yonghu_id) - shangjia_obj = user_main.shop_profile + shangjia_obj = user_main.ShopProfile shangjia_nicheng = shangjia_obj.nicheng except (User.DoesNotExist, UserShangjia.DoesNotExist): logger.warning(f"商家信息不存在 - 用户ID: {lianjie_obj.yonghu_id}") @@ -2478,7 +2478,7 @@ class KehuTianxieDingdanView(APIView): # 通过反向关系获取打手扩展信息 try: - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile # 检查打手状态(zhuangtai) if dashou_profile.zhuangtai != 1: @@ -2572,7 +2572,7 @@ class KehuTianxieDingdanView(APIView): # 获取商家信息用于返回 try: user_main = User.query.get(UserUID=lianjie_obj.yonghu_id) - shangjia_obj = user_main.shop_profile + shangjia_obj = user_main.ShopProfile shangjia_mingcheng = shangjia_obj.nicheng except: shangjia_mingcheng = f"商家{lianjie_obj.yonghu_id}" @@ -2753,21 +2753,21 @@ class GuanshiQRCodeView(APIView): def post(self, request): logger.info("===== 开始生成管事二维码 =====") current_user = request.user - logger.info(f"当前用户ID: {current_user.yonghuid}") + logger.info(f"当前用户ID: {current_user.UserUID}") try: try: - guanshi = current_user.guanshi_profile + guanshi = current_user.GuanshiProfile logger.info(f"管事扩展表查询成功,状态: {guanshi.zhuangtai}") except UserGuanshi.DoesNotExist: - logger.warning(f"用户 {current_user.yonghuid} 不是管事身份") + logger.warning(f"用户 {current_user.UserUID} 不是管事身份") return Response( {'code': 403, 'message': '当前用户不是管事身份', 'data': None}, status=status.HTTP_403_FORBIDDEN ) if guanshi.zhuangtai != 1: - logger.warning(f"用户 {current_user.yonghuid} 管事状态异常: {guanshi.zhuangtai}") + logger.warning(f"用户 {current_user.UserUID} 管事状态异常: {guanshi.zhuangtai}") return Response( {'code': 403, 'message': '管事账号状态异常', 'data': None}, status=status.HTTP_403_FORBIDDEN @@ -2775,10 +2775,10 @@ class GuanshiQRCodeView(APIView): invite_code = guanshi.yaoqingma if not invite_code: - logger.info(f"用户 {current_user.yonghuid} 无邀请码,开始生成") - invite_code = CreateInvitationCode(str(current_user.yonghuid)) + logger.info(f"用户 {current_user.UserUID} 无邀请码,开始生成") + invite_code = CreateInvitationCode(str(current_user.UserUID)) if not VerifyInvitationCode(invite_code): - logger.error(f"邀请码唯一性校验失败,用户: {current_user.yonghuid}") + logger.error(f"邀请码唯一性校验失败,用户: {current_user.UserUID}") return Response( {'code': 500, 'message': '邀请码生成失败', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR @@ -2854,7 +2854,7 @@ class GuanshiQRCodeView(APIView): ) timestamp = int(time.time()) - filename = f"{current_user.yonghuid}_{timestamp}.png" + filename = f"{current_user.UserUID}_{timestamp}.png" oss_path = f"erweima/guanshi/{filename}" file_obj = io.BytesIO(wx_resp.content) logger.info(f"开始上传图片到COS: {oss_path}") @@ -2971,7 +2971,7 @@ class ZuzhangHaibaoView(APIView): # 1. 验证组长身份 try: - zuzhang = current_user.zuzhang_profile + zuzhang = current_user.ZuzhangProfile except UserZuzhang.DoesNotExist: return Response({ 'code': 403, @@ -2990,9 +2990,9 @@ class ZuzhangHaibaoView(APIView): # 3. 获取或生成邀请码 invite_code = zuzhang.yaoqingma if not invite_code: - invite_code = CreateInvitationCode(str(current_user.yonghuid)) + invite_code = CreateInvitationCode(str(current_user.UserUID)) if not VerifyInvitationCode(invite_code): - logger.error(f"组长邀请码生成失败,用户: {current_user.yonghuid}") + logger.error(f"组长邀请码生成失败,用户: {current_user.UserUID}") return Response({ 'code': 500, 'message': '邀请码生成失败', @@ -3066,7 +3066,7 @@ class ZuzhangHaibaoView(APIView): # 5. 上传到腾讯云 COS timestamp = int(time.time()) - filename = f"{current_user.yonghuid}_{timestamp}.png" + filename = f"{current_user.UserUID}_{timestamp}.png" oss_path = f"erweima/zuzhang/{filename}" file_obj = io.BytesIO(wx_resp.content) @@ -3247,7 +3247,7 @@ class CheckPhoneAuthView(APIView): user = request.user # User 实例 # 已经认证过手机号,直接放行 - if user.shoujihao_renzheng: + if user.PhoneVerified: return Response({'need_auth': False}) # 检查各角色是否满足阈值条件 @@ -3277,7 +3277,7 @@ class CheckPhoneAuthView(APIView): except UserDashou.DoesNotExist: return False # 检查是否有任意会员购买记录 - return Huiyuangoumai.query.filter(yonghu_id=user.yonghuid).exists() + return Huiyuangoumai.query.filter(yonghu_id=user.UserUID).exists() def _shangjia_meets_threshold(self, user): """商家条件:发布订单总数 fabu >= 5""" diff --git a/gvsdsdk/migrations/0002_alter_user_useruuid.py b/gvsdsdk/migrations/0002_alter_user_useruuid.py new file mode 100644 index 0000000..58f2003 --- /dev/null +++ b/gvsdsdk/migrations/0002_alter_user_useruuid.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.27 on 2026-06-19 01:54 + +from django.db import migrations +import gvsdsdk.models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('gvsdsdk', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='UserUUID', + field=gvsdsdk.models.UUIDField(binary_length=16, db_column='UserUUID', default=uuid.uuid4, primary_key=True, serialize=False, verbose_name='用户UUID'), + ), + ] diff --git a/gvsdsdk/models.py b/gvsdsdk/models.py index 85782c3..d79d374 100644 --- a/gvsdsdk/models.py +++ b/gvsdsdk/models.py @@ -1,1044 +1,944 @@ -"""GVS-DSDK 数据模型模块 - -将 SAAS 主服务器的用户表(User)、权限表(Permission)、角色表(Role)、 -用户角色关联表(UserRole)、角色权限关联表(RolePermission)、 -岗位模板表(PositionTemplate)、岗位模板权限关联表(PositionTemplatePermission)、 -人事职务标签表(Position)、员工部门关联表(UserDept)封装为独立的 Django ORM 模型, -供子服务器直接进行跨服务器 ORM 查询。 - -权限体系四大分层: - 1. 组织人事层:Tenant → Company → Department,员工通过 UserDept 归属部门 - 2. 岗位模板层:PositionTemplate + PositionTemplatePermission,权限初始化快照 - 3. 角色权限载体层:Role + RolePermission,运行时鉴权实体 - 4. 鉴权规则层:操作权限校验(角色汇总)+ 数据权限校验(组织属性) - -使用方式(子服务器): - from gvsdsdk.models import User, Permission, Role, UserRole, RolePermission - - # 查询所有启用的用户 - users = User.objects.using('saas').filter(UserAccountLicense=1) - - # 或配置 DatabaseRouter 后直接使用 - users = User.objects.filter(UserAccountLicense=1) - -设计原则: - 1. 独立:不依赖 gvsds 内部模块,可在任意 Django 项目中独立使用 - 2. 非托管:managed=False,不创建/迁移表,仅映射已有表结构 - 3. PascalCase:字段名、方法名遵循帕斯卡命名法 - 4. 兼容:db_table、db_column 与 SAAS 主库完全一致 - 5. 租户隔离:所有权限相关数据依附于独立租户空间,不同租户互相不可见 -""" - -import uuid as _uuid -import datetime as _datetime -from django.db import models -from django.contrib.auth.models import BaseUserManager -from gvsdsdk.model_base import QModel -from gvsdsdk.model_utils import UUIDObj - - -# === UUID 字段 === - -class FixedBinaryField(models.BinaryField): - """定长二进制字段:映射 MySQL BINARY(N),可用于主键""" - - description = "Fixed-length binary field" - - def __init__(self, *args, **kwargs): - self.binary_length = kwargs.pop('binary_length', 16) - kwargs.setdefault('max_length', self.binary_length) - kwargs.setdefault('editable', False) - super().__init__(*args, **kwargs) - - def db_type(self, connection): - if connection.vendor == 'mysql': - return f'BINARY({self.binary_length})' - elif connection.vendor == 'sqlite': - return 'BLOB' - elif connection.vendor == 'postgresql': - return 'BYTEA' - return f'BINARY({self.binary_length})' - - def rel_db_type(self, connection): - return self.db_type(connection) - - def deconstruct(self): - name, path, args, kwargs = super().deconstruct() - kwargs['binary_length'] = self.binary_length - return name, path, args, kwargs - - -class UUIDField(FixedBinaryField): - """UUID 字段:数据库存储为 BINARY(16),Python 侧自动转换 str/bytes/uuid.UUID""" - - description = "UUID stored as BINARY(16)" - - def __init__(self, *args, **kwargs): - kwargs.setdefault('max_length', 16) - kwargs.setdefault('editable', False) - super().__init__(*args, **kwargs) - - def db_type(self, connection): - vendor = connection.vendor - if vendor == 'mysql': - return 'BINARY(16)' - elif vendor == 'sqlite': - return 'BLOB' - elif vendor == 'postgresql': - return 'BYTEA' - return 'BINARY(16)' - - def from_db_value(self, value, expression, connection): - if value is None: - return None - if isinstance(value, UUIDObj): - return value - return UUIDObj(value) - - def get_prep_value(self, value): - if value is None: - return None - if isinstance(value, UUIDObj): - return value.bytes - if isinstance(value, str): - try: - return _uuid.UUID(value).bytes - except ValueError: - return None - if isinstance(value, _uuid.UUID): - return value.bytes - if isinstance(value, bytes): - return value - return None - - def get_prep_lookup(self, lookup_type, value): - if lookup_type in ('exact', 'iexact', 'in'): - if value is None: - return None - if lookup_type == 'in' and isinstance(value, (list, tuple)): - return [self.get_prep_value(v) for v in value] - return self.get_prep_value(value) - return super().get_prep_lookup(lookup_type, value) - - def value_to_string(self, obj): - value = self.value_from_object(obj) - if isinstance(value, UUIDObj): - return value.str or '' - if isinstance(value, str): - return value - if isinstance(value, bytes) and len(value) == 16: - return str(_uuid.UUID(bytes=value)) - if isinstance(value, _uuid.UUID): - return str(value) - return '' - - def deconstruct(self): - name, path, args, kwargs = super().deconstruct() - del kwargs['max_length'] - return name, path, args, kwargs - - -# === QModel 基础模型 === - -# === 用户管理器 === - -class UserManager(BaseUserManager): - """用户管理器——提供用户创建与超级用户创建。 - 方法名保留蛇形命名以兼容 Django 内部约定。""" - - def create_user(self, username, password=None, email='', **extra_fields): - """创建普通用户""" - if not username: - raise ValueError('The Username field must be set') - user = self.model( - UserUUID=_uuid.uuid4().bytes, - UserName=username, - UserEmail=email or '', - **extra_fields - ) - if password: - user.SetPassword(password) - user.save(using=self._db) - return user - - def create_superuser(self, username, password=None, **extra_fields): - """创建超级用户""" - extra_fields.setdefault('UserAccountLicense', 1) - extra_fields.setdefault('UserPositionStatus', 0) - extra_fields.setdefault('IsStaff', True) - extra_fields.setdefault('IsSuperuser', True) - return self.create_user(username, password, **extra_fields) - - -# === 用户表 === -class User(QModel): - """用户表——系统核心身份模型(映射 SAAS 主库 User 表)""" - - UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID', default=_uuid.uuid4) - UserName = models.CharField(max_length=256, unique=True, db_column='UserName', verbose_name='用户名') - UserPassword = models.CharField(max_length=60, default='', db_column='UserPassword', verbose_name='密码') - UserPositionStatus = models.SmallIntegerField(default=0, db_column='UserPositionStatus', verbose_name='职位状态') - UserPersonalSignature = models.CharField(max_length=1024, default='', db_column='UserPersonalSignature', verbose_name='个人签名') - UserEmail = models.CharField(max_length=256, default='', db_column='UserEmail', verbose_name='邮箱') - UserEmailVerify = models.SmallIntegerField(default=0, db_column='UserEmailVerify', verbose_name='邮箱验证') - UserAccountLicense = models.SmallIntegerField(default=1, db_column='UserAccountLicense', verbose_name='账户许可') - UserGender = models.SmallIntegerField(default=-1, db_column='UserGender', verbose_name='性别') - UserTele = models.CharField(max_length=16, null=True, blank=True, db_column='UserTele', verbose_name='电话') - UserCreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UserCreateTime', verbose_name='创建时间') - UserLastLoginDate = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UserLastLoginDate', verbose_name='最后登录') - # === 本地业务字段(去远程化后新增) === - UserUID = models.CharField(max_length=7, unique=True, db_column='UserUID', verbose_name='用户业务ID', null=True, blank=True) - OpenID = models.CharField(max_length=64, unique=True, db_column='OpenID', verbose_name='微信OpenID', null=True, blank=True) - UnionID = models.CharField(max_length=64, db_column='UnionID', verbose_name='微信UnionID', null=True, blank=True) - Avatar = models.CharField(max_length=500, default='a_long/morentouxiang.jpg', db_column='Avatar', verbose_name='头像') - Phone = models.CharField(max_length=11, db_column='Phone', verbose_name='手机号', null=True, blank=True) - PhoneVerified = models.BooleanField(default=False, db_column='PhoneVerified', verbose_name='手机号是否已认证') - IP = models.CharField(max_length=50, db_column='IP', verbose_name='来访用户IP', null=True, blank=True) - IsStaff = models.BooleanField(default=False, db_column='IsStaff', verbose_name='是否员工') - IsSuperuser = models.BooleanField(default=False, db_column='IsSuperuser', verbose_name='是否超级用户') - - objects = UserManager() - - # Django auth 框架必需 - USERNAME_FIELD = 'UserName' - REQUIRED_FIELDS = ['UserUID'] - - class Meta: - managed = True # 将由 QModelBase.__new__ 根据 GVSDSDK_MANAGE_TABLES 动态设置 - app_label = 'gvsdsdk' - db_table = 'User' - verbose_name = '用户' - verbose_name_plural = '用户' - indexes = [ - models.Index(fields=['UserUID']), - models.Index(fields=['OpenID']), - ] - - def __str__(self): - return self.UserName - - @property - def UUIDStr(self): - """返回 UUID 字符串形式""" - return UUIDObj(self.UserUUID).str - - @property - def IsActive(self): - """账户是否启用(UserAccountLicense == 1)""" - return self.UserAccountLicense == 1 - - @property - def IsOnJob(self): - """是否在职(UserPositionStatus == 0)""" - return self.UserPositionStatus == 0 - - @property - def IsAnonymous(self): - return False - - @property - def IsAuthenticated(self): - return True - - @property - def PK(self): - return UUIDObj(self.UserUUID) - - @property - def ID(self): - return UUIDObj(self.UserUUID) - - def SetPassword(self, RawPassword): - """设置密码——bcrypt 哈希""" - import bcrypt - salt = bcrypt.gensalt(rounds=12) - hashed = bcrypt.hashpw(RawPassword.encode('utf-8'), salt) - self.UserPassword = hashed.decode('utf-8') - - def CheckPassword(self, RawPassword): - """验证密码——兼容 bcrypt 哈希和明文存储""" - import bcrypt - stored = self.UserPassword or '' - # bcrypt 哈希以 $2b$ 或 $2a$ 开头,长度固定 60 - if stored.startswith(('$2b$', '$2a$')) and len(stored) == 60: - try: - return bcrypt.checkpw( - RawPassword.encode('utf-8'), - stored.encode('utf-8') - ) - except Exception: - return False - else: - # 明文密码(旧数据兼容) - return stored == RawPassword - - def GetTele(self): - """获取电话,空则返回默认文本""" - return self.UserTele or '未填写' - - def GetCompanies(self): - """获取用户所属公司列表""" - # Company 模型不在 gvsdsdk 中,此处返回空 QuerySet - # 如需使用,请在子服务器中自行实现 - return [] - - def GetRoles(self): - """获取用户角色列表(需在同一 database 连接下使用)""" - user_role_uuids = UserRole.objects.filter( - UserUUID=self.UserUUID - ).values_list('RoleUUID', flat=True) - return Role.objects.filter(RoleUUID__in=user_role_uuids) - - def GetDepartments(self): - """获取用户部门列表""" - # Department 模型不在 gvsdsdk 中,此处返回空列表 - # 如需使用,请在子服务器中自行实现 - return [] - - def HasPermission(self, PermCode): - """检查用户是否拥有指定权限(需在同一 database 连接下使用) - - 通过 UserRole → RolePermission → Permission 链路查询, - 汇总所有角色的操作权限后判断。 - """ - user_role_uuids = UserRole.objects.filter( - UserUUID=self.UserUUID - ).values_list('RoleUUID', flat=True) - perm_uuids = RolePermission.objects.filter( - RoleUUID__in=user_role_uuids - ).values_list('PermUUID', flat=True) - return Permission.objects.filter( - PermUUID__in=perm_uuids, - PermCode=PermCode, - PermStatus=1, - ).exists() - - # Django auth 框架兼容(内部使用蛇形命名) - @property - def is_active(self): - return self.IsActive - - @property - def is_anonymous(self): - return self.IsAnonymous - - @property - def is_authenticated(self): - return self.IsAuthenticated - - @property - def id(self): - """兼容旧代码的 id 属性,返回 UserUUID""" - return self.UserUUID - - @property - def yonghuid(self): - """兼容旧代码的 yonghuid 属性""" - return self.UserUID - - @property - def openid(self): - """兼容旧代码的 openid 属性""" - return self.OpenID - - @property - def user_type(self): - """兼容旧代码的 user_type 属性 — 通过 UserRole 推断(带实例缓存)""" - # 实例缓存:同一请求内只查一次 - if hasattr(self, '_cached_user_type'): - return self._cached_user_type - try: - # 单次查询获取所有角色名 - role_uuids = UserRole.objects.filter( - UserUUID=self.UserUUID - ).values_list('RoleUUID', flat=True) - role_names = set( - Role.objects.filter(RoleUUID__in=role_uuids) - .values_list('RoleName', flat=True) - ) - # 优先级:管理员 > 客服 > 负责人 > 组长 > 审核官 > 商家 > 打手 - for rn in role_names: - if '管理员' in rn or 'admin' in rn.lower(): - self._cached_user_type = 'admin' - return 'admin' - for rn in role_names: - if '客服' in rn or 'kefu' in rn.lower(): - self._cached_user_type = 'kefu' - return 'kefu' - for rn in role_names: - if '负责人' in rn or '管事' in rn or 'guanshi' in rn.lower(): - self._cached_user_type = 'guanshi' - return 'guanshi' - for rn in role_names: - if '组长' in rn or 'zuzhang' in rn.lower(): - self._cached_user_type = 'zuzhang' - return 'zuzhang' - for rn in role_names: - if '审核' in rn or 'shenheguan' in rn.lower(): - self._cached_user_type = 'shenheguan' - return 'shenheguan' - for rn in role_names: - if '商家' in rn or 'shangjia' in rn.lower(): - self._cached_user_type = 'shop' - return 'shop' - for rn in role_names: - if '打手' in rn or 'dashou' in rn.lower(): - self._cached_user_type = 'dashou' - return 'dashou' - self._cached_user_type = 'normal' - return 'normal' - except Exception: - self._cached_user_type = 'normal' - return 'normal' - - @property - def pk(self): - return self.PK - - @property - def id(self): - return self.ID - - @property - def shoujihao_renzheng(self): - """兼容旧代码 — 手机号是否已认证""" - return self.PhoneVerified - - @shoujihao_renzheng.setter - def shoujihao_renzheng(self, value): - self.PhoneVerified = bool(value) - - # ── 旧字段兼容属性(UserMain → gvsdsdk.User 映射)── - - @property - def avatar(self): - return self.Avatar - - @avatar.setter - def avatar(self, value): - self.Avatar = value - - @property - def phone(self): - return self.Phone - - @phone.setter - def phone(self, value): - self.Phone = value - - @property - def ip(self): - return self.IP - - @ip.setter - def ip(self, value): - self.IP = value - - @property - def last_login_time(self): - return self.UserLastLoginDate - - @last_login_time.setter - def last_login_time(self, value): - self.UserLastLoginDate = value - - @property - def zhifu(self): - """兼容旧代码 — 收款码图片(存于 UserSensitiveData)""" - try: - return self.sensitive_data.zhifu - except Exception: - return '' - - @zhifu.setter - def zhifu(self, value): - try: - sd = self.sensitive_data - sd.zhifu = value - sd.save(update_fields=['zhifu']) - except Exception: - pass - - @property - def skzhanghao(self): - """兼容旧代码 — 收款账号(存于 UserSensitiveData)""" - try: - return self.sensitive_data.skzhanghao - except Exception: - return '' - - @skzhanghao.setter - def skzhanghao(self, value): - try: - sd = self.sensitive_data - sd.skzhanghao = value - sd.save(update_fields=['skzhanghao']) - except Exception: - pass - - def set_password(self, raw_password): - self.SetPassword(raw_password) - - def check_password(self, raw_password): - return self.CheckPassword(raw_password) - - @property - def dashou_profile(self): - """兼容旧代码""" - from users.models import UserDashou - return UserDashou.objects.filter(user=self).first() - - @property - def shop_profile(self): - """兼容旧代码""" - from users.models import UserShangjia - return UserShangjia.objects.filter(user=self).first() - - @property - def guanshi_profile(self): - """兼容旧代码""" - from users.models import UserGuanshi - return UserGuanshi.objects.filter(user=self).first() - - @property - def boss_profile(self): - """兼容旧代码""" - from users.models import UserBoss - return UserBoss.objects.filter(user=self).first() - - @property - def admin_profile(self): - """兼容旧代码""" - from users.models import AdminProfile - return AdminProfile.objects.filter(user=self).first() - - @property - def kefu_profile(self): - """兼容旧代码""" - from users.models import UserKefu - return UserKefu.objects.filter(user=self).first() - - @property - def zuzhang_profile(self): - """兼容旧代码""" - from users.models import UserZuzhang - return UserZuzhang.objects.filter(user=self).first() - - @property - def shenheguan_profile(self): - """兼容旧代码""" - from users.models import UserShenheguan - return UserShenheguan.objects.filter(user=self).first() - - def has_role(self, role_code): - """检查用户是否拥有指定角色(支持角色名或旧 user_type 值)""" - # 角色名映射:旧 user_type → 角色名 - ROLE_CODE_MAP = { - 'admin': '管理员', 'kefu': '客服', 'guanshi': '负责人', - 'zuzhang': '组长', 'shenheguan': '审核官', 'shop': '商家', - 'dashou': '打手', 'normal': '消费者', - } - target = ROLE_CODE_MAP.get(role_code, role_code) - try: - role_uuids = UserRole.objects.filter( - UserUUID=self.UserUUID - ).values_list('RoleUUID', flat=True) - return Role.objects.filter( - RoleUUID__in=role_uuids, RoleName=target - ).exists() - except Exception: - return False - - -# === 用户头像表 === - -class UserAvatar(QModel): - """用户头像表(映射 SAAS 主库 UserAvatar 表)""" - - UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID') - UserAvatar = models.BinaryField(editable=True, db_column='UserAvatar', verbose_name='头像数据') - AvatarUploadTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='AvatarUploadTime', verbose_name='上传时间') - AvatarFileType = models.CharField(max_length=16, default='png', db_column='AvatarFileType', verbose_name='文件类型') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'UserAvatar' - verbose_name = '用户头像' - verbose_name_plural = '用户头像' - - -# === 用户双因素认证表 === - -class User2FA(QModel): - """用户双因素认证表(映射 SAAS 主库 User2FA 表)""" - - User2FAUUID = UUIDField(primary_key=True, db_column='User2FAUUID', verbose_name='2FA UUID') - UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID') - SecretKey = models.CharField(max_length=64, db_column='SecretKey', verbose_name='密钥') - IsEnabled = models.SmallIntegerField(default=0, db_column='IsEnabled', verbose_name='是否启用') - Verified = models.SmallIntegerField(default=0, db_column='Verified', verbose_name='已验证') - LastUsedAt = models.DateTimeField(null=True, blank=True, db_column='LastUsedAt', verbose_name='最后使用') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'User2FA' - verbose_name = '双因素认证' - verbose_name_plural = '双因素认证' - - -# === 双因素认证待验证表 === - -class UserPending2FA(QModel): - """双因素认证待验证表(映射 SAAS 主库 UserPending2FA 表)""" - - PendingUUID = UUIDField(primary_key=True, db_column='PendingUUID', verbose_name='待确认UUID') - UserUUID = UUIDField(unique=True, db_column='UserUUID', verbose_name='用户UUID') - SecretKey = models.CharField(max_length=64, db_column='SecretKey', verbose_name='密钥') - ExpireAt = models.DateTimeField(db_column='ExpireAt', verbose_name='过期时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'UserPending2FA' - verbose_name = '2FA待确认' - verbose_name_plural = '2FA待确认' - - -# === 临时令牌表 === - -def _EltEndTimeDefault(): - from django.conf import settings - days = getattr(settings, 'ELT_TOKEN_LIFETIME_DAYS', 30) - return _datetime.datetime.utcnow() + _datetime.timedelta(days=days) - - -class EphemeralToken(QModel): - """临时令牌表——用于 API 认证与会话管理(映射 SAAS 主库 EphemeralToken 表)""" - - ELTUUID = UUIDField(primary_key=True, db_column='ELTUUID', verbose_name='令牌UUID') - ELTUserUUID = UUIDField(db_index=True, db_column='ELTUserUUID', verbose_name='用户UUID') - ELTCreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='ELTCreateTime', verbose_name='创建时间') - ELTEndTime = models.DateTimeField(default=_EltEndTimeDefault, db_column='ELTEndTime', verbose_name='过期时间') - ELTType = models.SmallIntegerField(default=0, db_column='ELTType', verbose_name='令牌类型') - ELTAllowIP = models.JSONField(default=list, db_column='ELTAllowIP', verbose_name='允许IP') - ELTStatus = models.SmallIntegerField(default=1, db_index=True, db_column='ELTStatus', verbose_name='状态') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'EphemeralToken' - verbose_name = '临时令牌' - verbose_name_plural = '临时令牌' - - def IsExpired(self): - """检查令牌是否过期""" - return _datetime.datetime.utcnow() > self.ELTEndTime.replace(tzinfo=None) - - def IsValid(self): - """检查令牌是否有效""" - return self.ELTStatus == 1 and not self.IsExpired() - - -# === 用户登录日志表 === - -class UserLoginLog(QModel): - """用户登录日志表(映射 SAAS 主库 UserLoginLog 表)""" - - LogUUID = UUIDField(primary_key=True, db_column='LogUUID', verbose_name='日志UUID') - UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID') - LoginIP = models.CharField(max_length=39, db_column='LoginIP', verbose_name='登录IP') - LoginPlatform = models.CharField(max_length=32, db_column='LoginPlatform', verbose_name='登录平台') - LoginStatus = models.SmallIntegerField(db_column='LoginStatus', verbose_name='登录状态') - LoginTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='LoginTime', verbose_name='登录时间') - UserAgent = models.CharField(max_length=512, default='', db_column='UserAgent', verbose_name='用户代理') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'UserLoginLog' - verbose_name = '登录日志' - verbose_name_plural = '登录日志' - - -# === 账户激活码表 === - -class AccountActivation(QModel): - """账户激活码表(映射 SAAS 主库 AccountActivation 表)""" - - AccountActivationCode = FixedBinaryField(primary_key=True, binary_length=6, db_column='AccountActivationCode', verbose_name='激活码') - UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'AccountActivation' - verbose_name = '账户激活' - verbose_name_plural = '账户激活' - - -# === 角色表 === - -class Role(QModel): - """角色表——权限分配单元(映射 SAAS 主库 Role 表) - - 角色是真正承载操作权限的实体,在单个租户内部独立存在, - 不依附任何公司或部门。通过 AssignScope 控制角色的分配可见范围: - - 0:全租户管理员可分发 - - 1:仅指定公司管理员可分发(AssignScopeUUID = CompanyUUID) - - 2:仅指定部门管理员可分发(AssignScopeUUID = DeptUUID) - """ - - RoleUUID = UUIDField(primary_key=True, db_column='RoleUUID', verbose_name='角色UUID') - TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') - RoleName = models.CharField(max_length=128, db_column='RoleName', verbose_name='角色名称') - RoleType = models.SmallIntegerField(default=2, db_column='RoleType', verbose_name='角色类型') - RoleStatus = models.SmallIntegerField(default=1, db_column='RoleStatus', verbose_name='角色状态') - AssignScope = models.SmallIntegerField(default=0, db_column='AssignScope', verbose_name='分配可见范围') - AssignScopeUUID = UUIDField(null=True, blank=True, db_column='AssignScopeUUID', verbose_name='分配范围UUID') - RoleDesc = models.CharField(max_length=512, default='', db_column='RoleDesc', verbose_name='角色描述') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'Role' - verbose_name = '角色' - verbose_name_plural = '角色' - - def __str__(self): - return self.RoleName - - def GetPermissions(self): - """获取角色拥有的权限列表(通过 RolePermission 中间表)""" - perm_uuids = RolePermission.objects.filter( - RoleUUID=self.RoleUUID - ).values_list('PermUUID', flat=True) - return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1) - - def GetUsers(self): - """获取拥有该角色的用户列表""" - user_uuids = UserRole.objects.filter( - RoleUUID=self.RoleUUID - ).values_list('UserUUID', flat=True) - return User.objects.filter(UserUUID__in=user_uuids) - - -# === 权限表 === - -class Permission(QModel): - """权限表——操作权限定义(映射 SAAS 主库 Permission 表) - - 独立的权限定义表,租户内全局可用,不再挂在角色下。 - 通过 RolePermission 中间表与角色建立多对多关系, - 通过 PositionTemplatePermission 中间表与岗位模板建立多对多关系。 - """ - - PermUUID = UUIDField(primary_key=True, db_column='PermUUID', verbose_name='权限UUID') - TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') - PermName = models.CharField(max_length=128, db_column='PermName', verbose_name='权限名称') - PermCode = models.CharField(max_length=128, db_column='PermCode', verbose_name='权限键') - PermCategory = models.CharField(max_length=64, default='', db_column='PermCategory', verbose_name='权限分类') - PermStatus = models.SmallIntegerField(default=1, db_column='PermStatus', verbose_name='是否启用') - PermDesc = models.CharField(max_length=512, default='', db_column='PermDesc', verbose_name='描述') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'Permission' - verbose_name = '权限' - verbose_name_plural = '权限' - - def __str__(self): - return f"{self.PermName} ({self.PermCode})" - - def GetRoles(self): - """获取拥有该权限的角色列表""" - role_uuids = RolePermission.objects.filter( - PermUUID=self.PermUUID - ).values_list('RoleUUID', flat=True) - return Role.objects.filter(RoleUUID__in=role_uuids) - - -# === 用户角色关联表 === - -class UserRole(QModel): - """用户角色关联表——用户与角色的多对多映射(映射 SAAS 主库 UserRole 表) - - 通过 CompanyUUID 标识该用户-角色关联所属的公司, - 用于菜单按公司差异化显示、数据权限按公司过滤等场景。 - 同一用户在不同公司下可绑定不同角色。 - - 注意:UserUUID 和 RoleUUID 在 gvsds 中是 ForeignKey, - 但此处改为 UUIDField 以保持独立性。 - """ - - UserRoleUUID = UUIDField(primary_key=True, db_column='UserRoleUUID', verbose_name='关联UUID') - # 原 gvsds 中为 ForeignKey(User),此处改为 UUIDField - UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') - # 原 gvsds 中为 ForeignKey('Role'),此处改为 UUIDField - RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') - CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'UserRole' - verbose_name = '用户角色关联' - verbose_name_plural = '用户角色关联' - - -# === 角色权限关联表 === - -class RolePermission(QModel): - """角色权限关联表——角色与操作权限的多对多映射(映射 SAAS 主库 RolePermission 表) - - 一个角色可以拥有多个权限,一个权限也可以被多个角色引用。 - 角色和具体操作权限为多对多绑定关系,一套权限集合可以打包在一个角色内统一维护。 - """ - - RolePermUUID = UUIDField(primary_key=True, db_column='RolePermUUID', verbose_name='关联UUID') - RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') - PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'RolePermission' - verbose_name = '角色权限关联' - verbose_name_plural = '角色权限关联' - - -# === 用户 ABAC 表 === - -class UserABAC(QModel): - """用户 ABAC 表——基于属性的访问控制(映射 SAAS 主库 UserABAC 表)""" - - UserABACUUID = UUIDField(primary_key=True, db_column='UserABACUUID', verbose_name='ABAC UUID') - UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') - HierarchicalPermissionIdentifiers = models.CharField(max_length=256, db_column='HierarchicalPermissionIdentifiers', verbose_name='层级权限标识') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'UserABAC' - verbose_name = '用户ABAC' - verbose_name_plural = '用户ABAC' - - -# === 密码表 === - -class Password(QModel): - """密码表——独立密码存储(映射 SAAS 主库 Password 表)""" - - UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID') - Password = models.CharField(max_length=1024, db_column='Password', verbose_name='密码哈希') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'Password' - verbose_name = '密码' - verbose_name_plural = '密码' - - -# === 商铺表 === - -class Shop(QModel): - """商铺表——SAAS 多商铺体系中的商铺实体 - - 每个商铺属于一个公司(CompanyUUID),可关联多个角色(通过 RoleShop)。 - """ - - ShopUUID = UUIDField(primary_key=True, db_column='ShopUUID', verbose_name='商铺UUID') - CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID') - ShopName = models.CharField(max_length=128, db_column='ShopName', verbose_name='商铺名称') - ShopCode = models.CharField(max_length=64, default='', db_column='ShopCode', verbose_name='商铺编码') - ShopType = models.SmallIntegerField(default=0, db_column='ShopType', verbose_name='商铺类型') - ShopStatus = models.SmallIntegerField(default=1, db_column='ShopStatus', verbose_name='商铺状态') - ShopAddress = models.CharField(max_length=512, default='', db_column='ShopAddress', verbose_name='商铺地址') - ShopContact = models.CharField(max_length=128, default='', db_column='ShopContact', verbose_name='联系人') - ShopPhone = models.CharField(max_length=32, default='', db_column='ShopPhone', verbose_name='联系电话') - ShopDesc = models.CharField(max_length=512, default='', db_column='ShopDesc', verbose_name='描述') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'Shop' - verbose_name = '商铺' - verbose_name_plural = '商铺' - - def __str__(self): - return self.ShopName - - def GetRoles(self): - """获取商铺关联的所有角色""" - role_uuids = RoleShop.objects.filter( - ShopUUID=self.ShopUUID - ).values_list('RoleUUID', flat=True) - return Role.objects.filter(RoleUUID__in=role_uuids) - - -# === 角色商铺关联表 === - -class RoleShop(QModel): - """角色商铺关联表——角色与商铺的多对多映射 - - 一个角色可以管理多个商铺,一个商铺也可以被多个角色管理。 - """ - - RoleShopUUID = UUIDField(primary_key=True, db_column='RoleShopUUID', verbose_name='关联UUID') - RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') - ShopUUID = UUIDField(db_column='ShopUUID', verbose_name='商铺UUID') - IsMainShop = models.SmallIntegerField(default=0, db_column='IsMainShop', verbose_name='主商铺') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'RoleShop' - verbose_name = '角色商铺关联' - verbose_name_plural = '角色商铺关联' - - -# === 岗位模板表 === - -class PositionTemplate(QModel): - """岗位模板表——权限初始化快照模板(映射 SAAS 主库 PositionTemplate 表) - - 租户内全局可重复使用,定位是权限初始化快照模板, - 不直接分配给员工、不参与运行时鉴权。 - 新建角色时可一键复制模板内的全部权限内容生成新角色。 - 模板后续调整只会作用于之后新创建的角色, - 已有角色不会同步变更,实现模板复用和存量权限独立管控互不干扰。 - """ - - PositionTemplateUUID = UUIDField(primary_key=True, db_column='PositionTemplateUUID', verbose_name='岗位模板UUID') - TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') - TemplateName = models.CharField(max_length=128, db_column='TemplateName', verbose_name='模板名称') - TemplateCode = models.CharField(max_length=64, default='', db_column='TemplateCode', verbose_name='模板编码') - TemplateStatus = models.SmallIntegerField(default=1, db_column='TemplateStatus', verbose_name='模板状态') - TemplateDesc = models.CharField(max_length=512, default='', db_column='TemplateDesc', verbose_name='模板描述') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'PositionTemplate' - verbose_name = '岗位模板' - verbose_name_plural = '岗位模板' - - def __str__(self): - return self.TemplateName - - def GetPermissions(self): - """获取模板关联的权限列表""" - perm_uuids = PositionTemplatePermission.objects.filter( - PositionTemplateUUID=self.PositionTemplateUUID - ).values_list('PermUUID', flat=True) - return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1) - - def CopyToRole(self, RoleName, TenantUUID): - """从模板复制权限到新角色(一次性复制,后续模板变更不影响已有角色)""" - role = Role.objects.create( - RoleUUID=_uuid.uuid4().bytes, - TenantUUID=TenantUUID, - RoleName=RoleName, - ) - template_perms = PositionTemplatePermission.objects.filter( - PositionTemplateUUID=self.PositionTemplateUUID - ) - for tp in template_perms: - RolePermission.objects.create( - RolePermUUID=_uuid.uuid4().bytes, - RoleUUID=role.RoleUUID, - PermUUID=tp.PermUUID, - ) - return role - - -# === 岗位模板权限关联表 === - -class PositionTemplatePermission(QModel): - """岗位模板权限关联表——岗位模板与操作权限的多对多映射 - - 仅作为复制源头,不参与运行时鉴权。 - 新建角色时一键复制模板权限到角色,复制后模板与角色无运行时绑定。 - """ - - PTPermUUID = UUIDField(primary_key=True, db_column='PTPermUUID', verbose_name='关联UUID') - PositionTemplateUUID = UUIDField(db_column='PositionTemplateUUID', verbose_name='岗位模板UUID') - PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'PositionTemplatePermission' - verbose_name = '岗位模板权限关联' - verbose_name_plural = '岗位模板权限关联' - - -# === 人事职务标签表 === - -class Position(QModel): - """人事职务标签表——仅用于人事管理标注(映射 SAAS 主库 Position 表) - - 员工会标注对应的职务标签,该标签仅为人事标注信息, - 不直接绑定权限,和角色、权限体系完全解耦。 - 人员调岗、升职只修改组织归属和人事职务标签,权限只需增删绑定角色即可,互不干扰。 - """ - - PositionUUID = UUIDField(primary_key=True, db_column='PositionUUID', verbose_name='职务UUID') - TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') - PositionName = models.CharField(max_length=128, db_column='PositionName', verbose_name='职务名称') - PositionCode = models.CharField(max_length=64, default='', db_column='PositionCode', verbose_name='职务编码') - PositionLevel = models.SmallIntegerField(default=0, db_column='PositionLevel', verbose_name='职务级别') - PositionStatus = models.SmallIntegerField(default=1, db_column='PositionStatus', verbose_name='状态') - PositionDesc = models.CharField(max_length=512, default='', db_column='PositionDesc', verbose_name='描述') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'Position' - verbose_name = '人事职务标签' - verbose_name_plural = '人事职务标签' - - def __str__(self): - return self.PositionName - - -# === 员工部门关联表 === - -class UserDept(QModel): - """员工部门关联表——员工与部门的多对多映射(映射 SAAS 主库 UserDept 表) - - 支持一人多部门兼职,同时标注该员工在对应部门的职务标签。 - 该层级仅用于组织架构展示、审批流转、人员统计, - 同时为数据权限判断提供人员所属组织属性依据,不参与操作权限校验。 - """ - - UserDeptUUID = UUIDField(primary_key=True, db_column='UserDeptUUID', verbose_name='关联UUID') - UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') - DeptUUID = UUIDField(db_column='DeptUUID', verbose_name='部门UUID') - PositionUUID = UUIDField(null=True, blank=True, db_column='PositionUUID', verbose_name='职务标签UUID') - IsMainDept = models.SmallIntegerField(default=1, db_column='IsMainDept', verbose_name='是否主部门') - CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') - - class Meta: - managed = True - app_label = 'gvsdsdk' - db_table = 'UserDept' - verbose_name = '员工部门关联' - verbose_name_plural = '员工部门关联' +"""GVS-DSDK 数据模型模块 + +将 SAAS 主服务器的用户表(User)、权限表(Permission)、角色表(Role)、 +用户角色关联表(UserRole)、角色权限关联表(RolePermission)、 +岗位模板表(PositionTemplate)、岗位模板权限关联表(PositionTemplatePermission)、 +人事职务标签表(Position)、员工部门关联表(UserDept)封装为独立的 Django ORM 模型, +供子服务器直接进行跨服务器 ORM 查询。 + +权限体系四大分层: + 1. 组织人事层:Tenant → Company → Department,员工通过 UserDept 归属部门 + 2. 岗位模板层:PositionTemplate + PositionTemplatePermission,权限初始化快照 + 3. 角色权限载体层:Role + RolePermission,运行时鉴权实体 + 4. 鉴权规则层:操作权限校验(角色汇总)+ 数据权限校验(组织属性) + +使用方式(子服务器): + from gvsdsdk.models import User, Permission, Role, UserRole, RolePermission + + # 查询所有启用的用户 + users = User.objects.using('saas').filter(UserAccountLicense=1) + + # 或配置 DatabaseRouter 后直接使用 + users = User.objects.filter(UserAccountLicense=1) + +设计原则: + 1. 独立:不依赖 gvsds 内部模块,可在任意 Django 项目中独立使用 + 2. 非托管:managed=False,不创建/迁移表,仅映射已有表结构 + 3. PascalCase:字段名、方法名遵循帕斯卡命名法 + 4. 兼容:db_table、db_column 与 SAAS 主库完全一致 + 5. 租户隔离:所有权限相关数据依附于独立租户空间,不同租户互相不可见 +""" + +import uuid as _uuid +import datetime as _datetime +from django.db import models +from django.contrib.auth.models import BaseUserManager +from gvsdsdk.model_base import QModel +from gvsdsdk.model_utils import UUIDObj + + +# === UUID 字段 === + +class FixedBinaryField(models.BinaryField): + """定长二进制字段:映射 MySQL BINARY(N),可用于主键""" + + description = "Fixed-length binary field" + + def __init__(self, *args, **kwargs): + self.binary_length = kwargs.pop('binary_length', 16) + kwargs.setdefault('max_length', self.binary_length) + kwargs.setdefault('editable', False) + super().__init__(*args, **kwargs) + + def db_type(self, connection): + if connection.vendor == 'mysql': + return f'BINARY({self.binary_length})' + elif connection.vendor == 'sqlite': + return 'BLOB' + elif connection.vendor == 'postgresql': + return 'BYTEA' + return f'BINARY({self.binary_length})' + + def rel_db_type(self, connection): + return self.db_type(connection) + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + kwargs['binary_length'] = self.binary_length + return name, path, args, kwargs + + +class UUIDField(FixedBinaryField): + """UUID 字段:数据库存储为 BINARY(16),Python 侧自动转换 str/bytes/uuid.UUID""" + + description = "UUID stored as BINARY(16)" + + def __init__(self, *args, **kwargs): + kwargs.setdefault('max_length', 16) + kwargs.setdefault('editable', False) + super().__init__(*args, **kwargs) + + def db_type(self, connection): + vendor = connection.vendor + if vendor == 'mysql': + return 'BINARY(16)' + elif vendor == 'sqlite': + return 'BLOB' + elif vendor == 'postgresql': + return 'BYTEA' + return 'BINARY(16)' + + def from_db_value(self, value, expression, connection): + if value is None: + return None + if isinstance(value, UUIDObj): + return value + return UUIDObj(value) + + def get_prep_value(self, value): + if value is None: + return None + if isinstance(value, UUIDObj): + return value.bytes + if isinstance(value, str): + try: + return _uuid.UUID(value).bytes + except ValueError: + return None + if isinstance(value, _uuid.UUID): + return value.bytes + if isinstance(value, bytes): + return value + return None + + def get_prep_lookup(self, lookup_type, value): + if lookup_type in ('exact', 'iexact', 'in'): + if value is None: + return None + if lookup_type == 'in' and isinstance(value, (list, tuple)): + return [self.get_prep_value(v) for v in value] + return self.get_prep_value(value) + return super().get_prep_lookup(lookup_type, value) + + def value_to_string(self, obj): + value = self.value_from_object(obj) + if isinstance(value, UUIDObj): + return value.str or '' + if isinstance(value, str): + return value + if isinstance(value, bytes) and len(value) == 16: + return str(_uuid.UUID(bytes=value)) + if isinstance(value, _uuid.UUID): + return str(value) + return '' + + def deconstruct(self): + name, path, args, kwargs = super().deconstruct() + del kwargs['max_length'] + return name, path, args, kwargs + + +# === QModel 基础模型 === + +# === 用户管理器 === + +class UserManager(BaseUserManager): + """用户管理器——提供用户创建与超级用户创建。 + 方法名保留蛇形命名以兼容 Django 内部约定。""" + + def create_user(self, username, password=None, email='', **extra_fields): + """创建普通用户""" + if not username: + raise ValueError('The Username field must be set') + user = self.model( + UserUUID=_uuid.uuid4().bytes, + UserName=username, + UserEmail=email or '', + **extra_fields + ) + if password: + user.SetPassword(password) + user.save(using=self._db) + return user + + def create_superuser(self, username, password=None, **extra_fields): + """创建超级用户""" + extra_fields.setdefault('UserAccountLicense', 1) + extra_fields.setdefault('UserPositionStatus', 0) + extra_fields.setdefault('IsStaff', True) + extra_fields.setdefault('IsSuperuser', True) + return self.create_user(username, password, **extra_fields) + + +# === 用户表 === +class User(QModel): + """用户表——系统核心身份模型(映射 SAAS 主库 User 表)""" + + UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID', default=_uuid.uuid4) + UserName = models.CharField(max_length=256, unique=True, db_column='UserName', verbose_name='用户名') + UserPassword = models.CharField(max_length=60, default='', db_column='UserPassword', verbose_name='密码') + UserPositionStatus = models.SmallIntegerField(default=0, db_column='UserPositionStatus', verbose_name='职位状态') + UserPersonalSignature = models.CharField(max_length=1024, default='', db_column='UserPersonalSignature', verbose_name='个人签名') + UserEmail = models.CharField(max_length=256, default='', db_column='UserEmail', verbose_name='邮箱') + UserEmailVerify = models.SmallIntegerField(default=0, db_column='UserEmailVerify', verbose_name='邮箱验证') + UserAccountLicense = models.SmallIntegerField(default=1, db_column='UserAccountLicense', verbose_name='账户许可') + UserGender = models.SmallIntegerField(default=-1, db_column='UserGender', verbose_name='性别') + UserTele = models.CharField(max_length=16, null=True, blank=True, db_column='UserTele', verbose_name='电话') + UserCreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UserCreateTime', verbose_name='创建时间') + UserLastLoginDate = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UserLastLoginDate', verbose_name='最后登录') + # === 本地业务字段(去远程化后新增) === + UserUID = models.CharField(max_length=7, unique=True, db_column='UserUID', verbose_name='用户业务ID', null=True, blank=True) + OpenID = models.CharField(max_length=64, unique=True, db_column='OpenID', verbose_name='微信OpenID', null=True, blank=True) + UnionID = models.CharField(max_length=64, db_column='UnionID', verbose_name='微信UnionID', null=True, blank=True) + Avatar = models.CharField(max_length=500, default='a_long/morentouxiang.jpg', db_column='Avatar', verbose_name='头像') + Phone = models.CharField(max_length=11, db_column='Phone', verbose_name='手机号', null=True, blank=True) + PhoneVerified = models.BooleanField(default=False, db_column='PhoneVerified', verbose_name='手机号是否已认证') + IP = models.CharField(max_length=50, db_column='IP', verbose_name='来访用户IP', null=True, blank=True) + IsStaff = models.BooleanField(default=False, db_column='IsStaff', verbose_name='是否员工') + IsSuperuser = models.BooleanField(default=False, db_column='IsSuperuser', verbose_name='是否超级用户') + + objects = UserManager() + + # Django auth 框架必需 + USERNAME_FIELD = 'UserName' + REQUIRED_FIELDS = ['UserUID'] + + class Meta: + managed = True # 将由 QModelBase.__new__ 根据 GVSDSDK_MANAGE_TABLES 动态设置 + app_label = 'gvsdsdk' + db_table = 'User' + verbose_name = '用户' + verbose_name_plural = '用户' + indexes = [ + models.Index(fields=['UserUID']), + models.Index(fields=['OpenID']), + ] + + def __str__(self): + return self.UserName + + @property + def UUIDStr(self): + """返回 UUID 字符串形式""" + return UUIDObj(self.UserUUID).str + + @property + def IsActive(self): + """账户是否启用(UserAccountLicense == 1)""" + return self.UserAccountLicense == 1 + + @property + def IsOnJob(self): + """是否在职(UserPositionStatus == 0)""" + return self.UserPositionStatus == 0 + + @property + def IsAnonymous(self): + return False + + @property + def IsAuthenticated(self): + return True + + @property + def PK(self): + return UUIDObj(self.UserUUID) + + @property + def ID(self): + return UUIDObj(self.UserUUID) + + def SetPassword(self, RawPassword): + """设置密码——bcrypt 哈希""" + import bcrypt + salt = bcrypt.gensalt(rounds=12) + hashed = bcrypt.hashpw(RawPassword.encode('utf-8'), salt) + self.UserPassword = hashed.decode('utf-8') + + def CheckPassword(self, RawPassword): + """验证密码——兼容 bcrypt 哈希和明文存储""" + import bcrypt + stored = self.UserPassword or '' + # bcrypt 哈希以 $2b$ 或 $2a$ 开头,长度固定 60 + if stored.startswith(('$2b$', '$2a$')) and len(stored) == 60: + try: + return bcrypt.checkpw( + RawPassword.encode('utf-8'), + stored.encode('utf-8') + ) + except Exception: + return False + else: + # 明文密码(旧数据兼容) + return stored == RawPassword + + def GetTele(self): + """获取电话,空则返回默认文本""" + return self.UserTele or '未填写' + + def GetCompanies(self): + """获取用户所属公司列表""" + # Company 模型不在 gvsdsdk 中,此处返回空 QuerySet + # 如需使用,请在子服务器中自行实现 + return [] + + def GetRoles(self): + """获取用户角色列表(需在同一 database 连接下使用)""" + user_role_uuids = UserRole.objects.filter( + UserUUID=self.UserUUID + ).values_list('RoleUUID', flat=True) + return Role.objects.filter(RoleUUID__in=user_role_uuids) + + def GetDepartments(self): + """获取用户部门列表""" + # Department 模型不在 gvsdsdk 中,此处返回空列表 + # 如需使用,请在子服务器中自行实现 + return [] + + def HasPermission(self, PermCode): + """检查用户是否拥有指定权限(需在同一 database 连接下使用) + + 通过 UserRole → RolePermission → Permission 链路查询, + 汇总所有角色的操作权限后判断。 + """ + user_role_uuids = UserRole.objects.filter( + UserUUID=self.UserUUID + ).values_list('RoleUUID', flat=True) + perm_uuids = RolePermission.objects.filter( + RoleUUID__in=user_role_uuids + ).values_list('PermUUID', flat=True) + return Permission.objects.filter( + PermUUID__in=perm_uuids, + PermCode=PermCode, + PermStatus=1, + ).exists() + + # ── Django auth 框架必需属性 ── + @property + def is_active(self): + return self.IsActive + + @property + def is_anonymous(self): + return self.IsAnonymous + + @property + def is_authenticated(self): + return self.IsAuthenticated + + @property + def pk(self): + return self.PK + + @property + def id(self): + return self.ID + + def set_password(self, raw_password): + self.SetPassword(raw_password) + + def check_password(self, raw_password): + return self.CheckPassword(raw_password) + + # ── 用户类型推断(通过 UserRole → Role 推断,带实例缓存)── + + @property + def UserType(self): + """用户类型 — 通过 UserRole 推断(带实例缓存)""" + if hasattr(self, '_cached_user_type'): + return self._cached_user_type + try: + role_uuids = UserRole.objects.filter( + UserUUID=self.UserUUID + ).values_list('RoleUUID', flat=True) + role_names = set( + Role.objects.filter(RoleUUID__in=role_uuids) + .values_list('RoleName', flat=True) + ) + # 优先级:管理员 > 客服 > 负责人 > 组长 > 审核官 > 商家 > 打手 + for rn in role_names: + if '管理员' in rn or 'admin' in rn.lower(): + self._cached_user_type = 'admin' + return 'admin' + for rn in role_names: + if '客服' in rn or 'kefu' in rn.lower(): + self._cached_user_type = 'kefu' + return 'kefu' + for rn in role_names: + if '负责人' in rn or '管事' in rn or 'guanshi' in rn.lower(): + self._cached_user_type = 'guanshi' + return 'guanshi' + for rn in role_names: + if '组长' in rn or 'zuzhang' in rn.lower(): + self._cached_user_type = 'zuzhang' + return 'zuzhang' + for rn in role_names: + if '审核' in rn or 'shenheguan' in rn.lower(): + self._cached_user_type = 'shenheguan' + return 'shenheguan' + for rn in role_names: + if '商家' in rn or 'shangjia' in rn.lower(): + self._cached_user_type = 'shop' + return 'shop' + for rn in role_names: + if '打手' in rn or 'dashou' in rn.lower(): + self._cached_user_type = 'dashou' + return 'dashou' + self._cached_user_type = 'normal' + return 'normal' + except Exception: + self._cached_user_type = 'normal' + return 'normal' + + # ── 收款信息(存于 UserSensitiveData)── + + @property + def PaymentQRCode(self): + """收款码图片(存于 UserSensitiveData.zhifu)""" + try: + return self.sensitive_data.zhifu + except Exception: + return '' + + @PaymentQRCode.setter + def PaymentQRCode(self, value): + try: + sd = self.sensitive_data + sd.zhifu = value + sd.save(update_fields=['zhifu']) + except Exception: + pass + + @property + def PaymentAccount(self): + """收款账号(存于 UserSensitiveData.skzhanghao)""" + try: + return self.sensitive_data.skzhanghao + except Exception: + return '' + + @PaymentAccount.setter + def PaymentAccount(self, value): + try: + sd = self.sensitive_data + sd.skzhanghao = value + sd.save(update_fields=['skzhanghao']) + except Exception: + pass + + # ── 关联扩展表快捷访问 ── + # DashouProfile / ShopProfile / GuanshiProfile / BossProfile / + # AdminProfile / KefuProfile / ZuzhangProfile / ShenheguanProfile + # 均由 OneToOneField related_name 直接提供反向关联访问 + + def HasRole(self, role_code): + """检查用户是否拥有指定角色(支持角色名或 UserType 值)""" + ROLE_CODE_MAP = { + 'admin': '管理员', 'kefu': '客服', 'guanshi': '负责人', + 'zuzhang': '组长', 'shenheguan': '审核官', 'shop': '商家', + 'dashou': '打手', 'normal': '消费者', + } + target = ROLE_CODE_MAP.get(role_code, role_code) + try: + role_uuids = UserRole.objects.filter( + UserUUID=self.UserUUID + ).values_list('RoleUUID', flat=True) + return Role.objects.filter( + RoleUUID__in=role_uuids, RoleName=target + ).exists() + except Exception: + return False + + +# === 用户头像表 === + +class UserAvatar(QModel): + """用户头像表(映射 SAAS 主库 UserAvatar 表)""" + + UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID') + UserAvatar = models.BinaryField(editable=True, db_column='UserAvatar', verbose_name='头像数据') + AvatarUploadTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='AvatarUploadTime', verbose_name='上传时间') + AvatarFileType = models.CharField(max_length=16, default='png', db_column='AvatarFileType', verbose_name='文件类型') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'UserAvatar' + verbose_name = '用户头像' + verbose_name_plural = '用户头像' + + +# === 用户双因素认证表 === + +class User2FA(QModel): + """用户双因素认证表(映射 SAAS 主库 User2FA 表)""" + + User2FAUUID = UUIDField(primary_key=True, db_column='User2FAUUID', verbose_name='2FA UUID') + UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID') + SecretKey = models.CharField(max_length=64, db_column='SecretKey', verbose_name='密钥') + IsEnabled = models.SmallIntegerField(default=0, db_column='IsEnabled', verbose_name='是否启用') + Verified = models.SmallIntegerField(default=0, db_column='Verified', verbose_name='已验证') + LastUsedAt = models.DateTimeField(null=True, blank=True, db_column='LastUsedAt', verbose_name='最后使用') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'User2FA' + verbose_name = '双因素认证' + verbose_name_plural = '双因素认证' + + +# === 双因素认证待验证表 === + +class UserPending2FA(QModel): + """双因素认证待验证表(映射 SAAS 主库 UserPending2FA 表)""" + + PendingUUID = UUIDField(primary_key=True, db_column='PendingUUID', verbose_name='待确认UUID') + UserUUID = UUIDField(unique=True, db_column='UserUUID', verbose_name='用户UUID') + SecretKey = models.CharField(max_length=64, db_column='SecretKey', verbose_name='密钥') + ExpireAt = models.DateTimeField(db_column='ExpireAt', verbose_name='过期时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'UserPending2FA' + verbose_name = '2FA待确认' + verbose_name_plural = '2FA待确认' + + +# === 临时令牌表 === + +def _EltEndTimeDefault(): + from django.conf import settings + days = getattr(settings, 'ELT_TOKEN_LIFETIME_DAYS', 30) + return _datetime.datetime.utcnow() + _datetime.timedelta(days=days) + + +class EphemeralToken(QModel): + """临时令牌表——用于 API 认证与会话管理(映射 SAAS 主库 EphemeralToken 表)""" + + ELTUUID = UUIDField(primary_key=True, db_column='ELTUUID', verbose_name='令牌UUID') + ELTUserUUID = UUIDField(db_index=True, db_column='ELTUserUUID', verbose_name='用户UUID') + ELTCreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='ELTCreateTime', verbose_name='创建时间') + ELTEndTime = models.DateTimeField(default=_EltEndTimeDefault, db_column='ELTEndTime', verbose_name='过期时间') + ELTType = models.SmallIntegerField(default=0, db_column='ELTType', verbose_name='令牌类型') + ELTAllowIP = models.JSONField(default=list, db_column='ELTAllowIP', verbose_name='允许IP') + ELTStatus = models.SmallIntegerField(default=1, db_index=True, db_column='ELTStatus', verbose_name='状态') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'EphemeralToken' + verbose_name = '临时令牌' + verbose_name_plural = '临时令牌' + + def IsExpired(self): + """检查令牌是否过期""" + return _datetime.datetime.utcnow() > self.ELTEndTime.replace(tzinfo=None) + + def IsValid(self): + """检查令牌是否有效""" + return self.ELTStatus == 1 and not self.IsExpired() + + +# === 用户登录日志表 === + +class UserLoginLog(QModel): + """用户登录日志表(映射 SAAS 主库 UserLoginLog 表)""" + + LogUUID = UUIDField(primary_key=True, db_column='LogUUID', verbose_name='日志UUID') + UserUUID = UUIDField(db_index=True, db_column='UserUUID', verbose_name='用户UUID') + LoginIP = models.CharField(max_length=39, db_column='LoginIP', verbose_name='登录IP') + LoginPlatform = models.CharField(max_length=32, db_column='LoginPlatform', verbose_name='登录平台') + LoginStatus = models.SmallIntegerField(db_column='LoginStatus', verbose_name='登录状态') + LoginTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='LoginTime', verbose_name='登录时间') + UserAgent = models.CharField(max_length=512, default='', db_column='UserAgent', verbose_name='用户代理') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'UserLoginLog' + verbose_name = '登录日志' + verbose_name_plural = '登录日志' + + +# === 账户激活码表 === + +class AccountActivation(QModel): + """账户激活码表(映射 SAAS 主库 AccountActivation 表)""" + + AccountActivationCode = FixedBinaryField(primary_key=True, binary_length=6, db_column='AccountActivationCode', verbose_name='激活码') + UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'AccountActivation' + verbose_name = '账户激活' + verbose_name_plural = '账户激活' + + +# === 角色表 === + +class Role(QModel): + """角色表——权限分配单元(映射 SAAS 主库 Role 表) + + 角色是真正承载操作权限的实体,在单个租户内部独立存在, + 不依附任何公司或部门。通过 AssignScope 控制角色的分配可见范围: + - 0:全租户管理员可分发 + - 1:仅指定公司管理员可分发(AssignScopeUUID = CompanyUUID) + - 2:仅指定部门管理员可分发(AssignScopeUUID = DeptUUID) + """ + + RoleUUID = UUIDField(primary_key=True, db_column='RoleUUID', verbose_name='角色UUID') + TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') + RoleName = models.CharField(max_length=128, db_column='RoleName', verbose_name='角色名称') + RoleType = models.SmallIntegerField(default=2, db_column='RoleType', verbose_name='角色类型') + RoleStatus = models.SmallIntegerField(default=1, db_column='RoleStatus', verbose_name='角色状态') + AssignScope = models.SmallIntegerField(default=0, db_column='AssignScope', verbose_name='分配可见范围') + AssignScopeUUID = UUIDField(null=True, blank=True, db_column='AssignScopeUUID', verbose_name='分配范围UUID') + RoleDesc = models.CharField(max_length=512, default='', db_column='RoleDesc', verbose_name='角色描述') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'Role' + verbose_name = '角色' + verbose_name_plural = '角色' + + def __str__(self): + return self.RoleName + + def GetPermissions(self): + """获取角色拥有的权限列表(通过 RolePermission 中间表)""" + perm_uuids = RolePermission.objects.filter( + RoleUUID=self.RoleUUID + ).values_list('PermUUID', flat=True) + return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1) + + def GetUsers(self): + """获取拥有该角色的用户列表""" + user_uuids = UserRole.objects.filter( + RoleUUID=self.RoleUUID + ).values_list('UserUUID', flat=True) + return User.objects.filter(UserUUID__in=user_uuids) + + +# === 权限表 === + +class Permission(QModel): + """权限表——操作权限定义(映射 SAAS 主库 Permission 表) + + 独立的权限定义表,租户内全局可用,不再挂在角色下。 + 通过 RolePermission 中间表与角色建立多对多关系, + 通过 PositionTemplatePermission 中间表与岗位模板建立多对多关系。 + """ + + PermUUID = UUIDField(primary_key=True, db_column='PermUUID', verbose_name='权限UUID') + TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') + PermName = models.CharField(max_length=128, db_column='PermName', verbose_name='权限名称') + PermCode = models.CharField(max_length=128, db_column='PermCode', verbose_name='权限键') + PermCategory = models.CharField(max_length=64, default='', db_column='PermCategory', verbose_name='权限分类') + PermStatus = models.SmallIntegerField(default=1, db_column='PermStatus', verbose_name='是否启用') + PermDesc = models.CharField(max_length=512, default='', db_column='PermDesc', verbose_name='描述') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'Permission' + verbose_name = '权限' + verbose_name_plural = '权限' + + def __str__(self): + return f"{self.PermName} ({self.PermCode})" + + def GetRoles(self): + """获取拥有该权限的角色列表""" + role_uuids = RolePermission.objects.filter( + PermUUID=self.PermUUID + ).values_list('RoleUUID', flat=True) + return Role.objects.filter(RoleUUID__in=role_uuids) + + +# === 用户角色关联表 === + +class UserRole(QModel): + """用户角色关联表——用户与角色的多对多映射(映射 SAAS 主库 UserRole 表) + + 通过 CompanyUUID 标识该用户-角色关联所属的公司, + 用于菜单按公司差异化显示、数据权限按公司过滤等场景。 + 同一用户在不同公司下可绑定不同角色。 + + 注意:UserUUID 和 RoleUUID 在 gvsds 中是 ForeignKey, + 但此处改为 UUIDField 以保持独立性。 + """ + + UserRoleUUID = UUIDField(primary_key=True, db_column='UserRoleUUID', verbose_name='关联UUID') + # 原 gvsds 中为 ForeignKey(User),此处改为 UUIDField + UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') + # 原 gvsds 中为 ForeignKey('Role'),此处改为 UUIDField + RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') + CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'UserRole' + verbose_name = '用户角色关联' + verbose_name_plural = '用户角色关联' + + +# === 角色权限关联表 === + +class RolePermission(QModel): + """角色权限关联表——角色与操作权限的多对多映射(映射 SAAS 主库 RolePermission 表) + + 一个角色可以拥有多个权限,一个权限也可以被多个角色引用。 + 角色和具体操作权限为多对多绑定关系,一套权限集合可以打包在一个角色内统一维护。 + """ + + RolePermUUID = UUIDField(primary_key=True, db_column='RolePermUUID', verbose_name='关联UUID') + RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') + PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'RolePermission' + verbose_name = '角色权限关联' + verbose_name_plural = '角色权限关联' + + +# === 用户 ABAC 表 === + +class UserABAC(QModel): + """用户 ABAC 表——基于属性的访问控制(映射 SAAS 主库 UserABAC 表)""" + + UserABACUUID = UUIDField(primary_key=True, db_column='UserABACUUID', verbose_name='ABAC UUID') + UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') + HierarchicalPermissionIdentifiers = models.CharField(max_length=256, db_column='HierarchicalPermissionIdentifiers', verbose_name='层级权限标识') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'UserABAC' + verbose_name = '用户ABAC' + verbose_name_plural = '用户ABAC' + + +# === 密码表 === + +class Password(QModel): + """密码表——独立密码存储(映射 SAAS 主库 Password 表)""" + + UserUUID = UUIDField(primary_key=True, db_column='UserUUID', verbose_name='用户UUID') + Password = models.CharField(max_length=1024, db_column='Password', verbose_name='密码哈希') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'Password' + verbose_name = '密码' + verbose_name_plural = '密码' + + +# === 商铺表 === + +class Shop(QModel): + """商铺表——SAAS 多商铺体系中的商铺实体 + + 每个商铺属于一个公司(CompanyUUID),可关联多个角色(通过 RoleShop)。 + """ + + ShopUUID = UUIDField(primary_key=True, db_column='ShopUUID', verbose_name='商铺UUID') + CompanyUUID = UUIDField(db_column='CompanyUUID', verbose_name='公司UUID') + ShopName = models.CharField(max_length=128, db_column='ShopName', verbose_name='商铺名称') + ShopCode = models.CharField(max_length=64, default='', db_column='ShopCode', verbose_name='商铺编码') + ShopType = models.SmallIntegerField(default=0, db_column='ShopType', verbose_name='商铺类型') + ShopStatus = models.SmallIntegerField(default=1, db_column='ShopStatus', verbose_name='商铺状态') + ShopAddress = models.CharField(max_length=512, default='', db_column='ShopAddress', verbose_name='商铺地址') + ShopContact = models.CharField(max_length=128, default='', db_column='ShopContact', verbose_name='联系人') + ShopPhone = models.CharField(max_length=32, default='', db_column='ShopPhone', verbose_name='联系电话') + ShopDesc = models.CharField(max_length=512, default='', db_column='ShopDesc', verbose_name='描述') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'Shop' + verbose_name = '商铺' + verbose_name_plural = '商铺' + + def __str__(self): + return self.ShopName + + def GetRoles(self): + """获取商铺关联的所有角色""" + role_uuids = RoleShop.objects.filter( + ShopUUID=self.ShopUUID + ).values_list('RoleUUID', flat=True) + return Role.objects.filter(RoleUUID__in=role_uuids) + + +# === 角色商铺关联表 === + +class RoleShop(QModel): + """角色商铺关联表——角色与商铺的多对多映射 + + 一个角色可以管理多个商铺,一个商铺也可以被多个角色管理。 + """ + + RoleShopUUID = UUIDField(primary_key=True, db_column='RoleShopUUID', verbose_name='关联UUID') + RoleUUID = UUIDField(db_column='RoleUUID', verbose_name='角色UUID') + ShopUUID = UUIDField(db_column='ShopUUID', verbose_name='商铺UUID') + IsMainShop = models.SmallIntegerField(default=0, db_column='IsMainShop', verbose_name='主商铺') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'RoleShop' + verbose_name = '角色商铺关联' + verbose_name_plural = '角色商铺关联' + + +# === 岗位模板表 === + +class PositionTemplate(QModel): + """岗位模板表——权限初始化快照模板(映射 SAAS 主库 PositionTemplate 表) + + 租户内全局可重复使用,定位是权限初始化快照模板, + 不直接分配给员工、不参与运行时鉴权。 + 新建角色时可一键复制模板内的全部权限内容生成新角色。 + 模板后续调整只会作用于之后新创建的角色, + 已有角色不会同步变更,实现模板复用和存量权限独立管控互不干扰。 + """ + + PositionTemplateUUID = UUIDField(primary_key=True, db_column='PositionTemplateUUID', verbose_name='岗位模板UUID') + TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') + TemplateName = models.CharField(max_length=128, db_column='TemplateName', verbose_name='模板名称') + TemplateCode = models.CharField(max_length=64, default='', db_column='TemplateCode', verbose_name='模板编码') + TemplateStatus = models.SmallIntegerField(default=1, db_column='TemplateStatus', verbose_name='模板状态') + TemplateDesc = models.CharField(max_length=512, default='', db_column='TemplateDesc', verbose_name='模板描述') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'PositionTemplate' + verbose_name = '岗位模板' + verbose_name_plural = '岗位模板' + + def __str__(self): + return self.TemplateName + + def GetPermissions(self): + """获取模板关联的权限列表""" + perm_uuids = PositionTemplatePermission.objects.filter( + PositionTemplateUUID=self.PositionTemplateUUID + ).values_list('PermUUID', flat=True) + return Permission.objects.filter(PermUUID__in=perm_uuids, PermStatus=1) + + def CopyToRole(self, RoleName, TenantUUID): + """从模板复制权限到新角色(一次性复制,后续模板变更不影响已有角色)""" + role = Role.objects.create( + RoleUUID=_uuid.uuid4().bytes, + TenantUUID=TenantUUID, + RoleName=RoleName, + ) + template_perms = PositionTemplatePermission.objects.filter( + PositionTemplateUUID=self.PositionTemplateUUID + ) + for tp in template_perms: + RolePermission.objects.create( + RolePermUUID=_uuid.uuid4().bytes, + RoleUUID=role.RoleUUID, + PermUUID=tp.PermUUID, + ) + return role + + +# === 岗位模板权限关联表 === + +class PositionTemplatePermission(QModel): + """岗位模板权限关联表——岗位模板与操作权限的多对多映射 + + 仅作为复制源头,不参与运行时鉴权。 + 新建角色时一键复制模板权限到角色,复制后模板与角色无运行时绑定。 + """ + + PTPermUUID = UUIDField(primary_key=True, db_column='PTPermUUID', verbose_name='关联UUID') + PositionTemplateUUID = UUIDField(db_column='PositionTemplateUUID', verbose_name='岗位模板UUID') + PermUUID = UUIDField(db_column='PermUUID', verbose_name='权限UUID') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'PositionTemplatePermission' + verbose_name = '岗位模板权限关联' + verbose_name_plural = '岗位模板权限关联' + + +# === 人事职务标签表 === + +class Position(QModel): + """人事职务标签表——仅用于人事管理标注(映射 SAAS 主库 Position 表) + + 员工会标注对应的职务标签,该标签仅为人事标注信息, + 不直接绑定权限,和角色、权限体系完全解耦。 + 人员调岗、升职只修改组织归属和人事职务标签,权限只需增删绑定角色即可,互不干扰。 + """ + + PositionUUID = UUIDField(primary_key=True, db_column='PositionUUID', verbose_name='职务UUID') + TenantUUID = UUIDField(db_column='TenantUUID', verbose_name='租户UUID') + PositionName = models.CharField(max_length=128, db_column='PositionName', verbose_name='职务名称') + PositionCode = models.CharField(max_length=64, default='', db_column='PositionCode', verbose_name='职务编码') + PositionLevel = models.SmallIntegerField(default=0, db_column='PositionLevel', verbose_name='职务级别') + PositionStatus = models.SmallIntegerField(default=1, db_column='PositionStatus', verbose_name='状态') + PositionDesc = models.CharField(max_length=512, default='', db_column='PositionDesc', verbose_name='描述') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + UpdateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='UpdateTime', verbose_name='更新时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'Position' + verbose_name = '人事职务标签' + verbose_name_plural = '人事职务标签' + + def __str__(self): + return self.PositionName + + +# === 员工部门关联表 === + +class UserDept(QModel): + """员工部门关联表——员工与部门的多对多映射(映射 SAAS 主库 UserDept 表) + + 支持一人多部门兼职,同时标注该员工在对应部门的职务标签。 + 该层级仅用于组织架构展示、审批流转、人员统计, + 同时为数据权限判断提供人员所属组织属性依据,不参与操作权限校验。 + """ + + UserDeptUUID = UUIDField(primary_key=True, db_column='UserDeptUUID', verbose_name='关联UUID') + UserUUID = UUIDField(db_column='UserUUID', verbose_name='用户UUID') + DeptUUID = UUIDField(db_column='DeptUUID', verbose_name='部门UUID') + PositionUUID = UUIDField(null=True, blank=True, db_column='PositionUUID', verbose_name='职务标签UUID') + IsMainDept = models.SmallIntegerField(default=1, db_column='IsMainDept', verbose_name='是否主部门') + CreateTime = models.DateTimeField(default=_datetime.datetime.utcnow, db_column='CreateTime', verbose_name='创建时间') + + class Meta: + managed = True + app_label = 'gvsdsdk' + db_table = 'UserDept' + verbose_name = '员工部门关联' + verbose_name_plural = '员工部门关联' diff --git a/orders/tasks.py b/orders/tasks.py index 9f3f985..572decc 100644 --- a/orders/tasks.py +++ b/orders/tasks.py @@ -68,7 +68,7 @@ def process_expired_order(self, dingdan_id): dashou_user = User.query.filter(UserUID=dashou_id).first() if dashou_user: # 获取打手扩展表 - dashou_profile = getattr(dashou_user, 'dashou_profile', None) + dashou_profile = getattr(dashou_user, 'DashouProfile', None) if dashou_profile: # 使用F表达式原子更新 UserDashou.query.filter(pk=dashou_profile.pk).update( @@ -96,7 +96,7 @@ def process_expired_order(self, dingdan_id): # 查询商家用户 shangjia_user = User.query.filter(UserUID=shangjia_id).first() if shangjia_user: - shangjia_profile = getattr(shangjia_user, 'shop_profile', None) + shangjia_profile = getattr(shangjia_user, 'ShopProfile', None) if shangjia_profile: # 更新商家成交订单数量 UserShangjia.query.filter(pk=shangjia_profile.pk).update( diff --git a/orders/utils.py b/orders/utils.py index bbb9bab..696a52f 100644 --- a/orders/utils.py +++ b/orders/utils.py @@ -179,7 +179,7 @@ def settle_shangjia_order_guanshi_fenhong(order, dashou_id): dashouid=dashou_id, shuoming='商家订单分红', fenhong=guanshi_fencheng, - avatar=user_main.avatar if user_main else None, + avatar=user_main.Avatar if user_main else None, nicheng=dashou.nicheng or '未知打手', fenhong_leixing=3, ) diff --git a/orders/views.py b/orders/views.py index 35ddf96..830ef93 100644 --- a/orders/views.py +++ b/orders/views.py @@ -91,7 +91,7 @@ class PaymentFailView(APIView): if not dingdan: return Response({'code': 2, 'msg': '订单不存在', 'data': None}) - if dingdan.User1ID != f"Boss{request.user.yonghuid}": + if dingdan.User1ID != f"Boss{request.user.UserUID}": return Response({'code': 3, 'msg': '无权操作此订单', 'data': None}) # 只允许删除未支付订单(状态9) @@ -242,8 +242,8 @@ class WechatPayNotifyView(APIView): laoban_id = dingdan.pingtai_kuozhan.BossID if laoban_id: user_main = User.query.filter(UserUID=laoban_id).first() - if user_main and hasattr(user_main, 'boss_profile'): - boss = user_main.boss_profile + if user_main and hasattr(user_main, 'BossProfile'): + boss = user_main.BossProfile boss.zonge = (boss.zonge or 0) + (dingdan.Amount or 0) boss.alldingdan = (boss.alldingdan or 0) + 1 boss.save() @@ -433,9 +433,9 @@ class PaymentVerifyView(APIView): # ---------- 3. 验证订单归属(安全) ---------- current_user = request.user - user_identifier = f"Boss{current_user.yonghuid}" + user_identifier = f"Boss{current_user.UserUID}" if dingdan.User1ID != user_identifier: - logger.warning(f"用户 {current_user.yonghuid} 尝试复查他人订单 {dingdanid}") + logger.warning(f"用户 {current_user.UserUID} 尝试复查他人订单 {dingdanid}") return Response({'code': 403, 'msg': '无权访问'}) # ---------- 4. 如果订单状态已是1/7,直接返回成功 ---------- @@ -460,7 +460,7 @@ class PaymentVerifyView(APIView): if trade_state == 'SUCCESS': # 支付成功,执行本地更新 - self._process_payment_success(dingdan, transaction_id, current_user.yonghuid) + self._process_payment_success(dingdan, transaction_id, current_user.UserUID) # 重新获取订单以获取最新状态 dingdan.refresh_from_db() return Response({ @@ -526,8 +526,8 @@ class PaymentVerifyView(APIView): laoban_id = dingdan.pingtai_kuozhan.BossID if hasattr(dingdan, 'pingtai_kuozhan') else None if laoban_id: user_main = User.query.filter(UserUID=laoban_id).first() - if user_main and hasattr(user_main, 'boss_profile'): - boss = user_main.boss_profile + if user_main and hasattr(user_main, 'BossProfile'): + boss = user_main.BossProfile boss.zonge = (boss.zonge or 0) + (dingdan.Amount or 0) boss.alldingdan = (boss.alldingdan or 0) + 1 boss.save() @@ -787,7 +787,7 @@ class CreateOrderView(APIView): 'Remark': beizhu, 'Nickname': nicheng, 'ImageURL':shangpin_tupian, - 'User1ID':f"Boss{request.user.yonghuid}", + 'User1ID':f"Boss{request.user.UserUID}", 'ProductTypeID':shangpin.leixing_id } @@ -1020,7 +1020,7 @@ class DingdanHuoquView(APIView): # 获取当前登录用户 try: current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID except Exception as e: return Response({ 'code': 1, @@ -1135,7 +1135,7 @@ class DingdanXiangqingView(APIView): # 获取当前登录用户 try: current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID except Exception as e: return Response({ 'code': 1, @@ -1243,7 +1243,7 @@ class JiedanView(APIView): # 获取当前登录用户 try: current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID except Exception as e: return Response({ 'code': 1, @@ -1475,7 +1475,7 @@ class DingdanXiangqingView2(APIView): def post(self, request): try: - yonghuid = request.user.yonghuid + yonghuid = request.user.UserUID except Exception: return Response({'code': 1, 'msg': '用户认证失败', 'data': None}) @@ -1514,9 +1514,9 @@ class DingdanXiangqingView2(APIView): if jiedan_dashou_id: dashou_user = User.query.filter(UserUID=jiedan_dashou_id).first() if dashou_user: - dashou_touxiang = dashou_user.avatar or '' + dashou_touxiang = dashou_user.Avatar or '' try: - dashou_nicheng = dashou_user.dashou_profile.nicheng or '' + dashou_nicheng = dashou_user.DashouProfile.nicheng or '' except UserDashou.DoesNotExist: pass biaoxian = DashouBiaoxian.query.filter( @@ -1575,7 +1575,7 @@ class JiedanView2(APIView): # 1. 用户认证 try: current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID except Exception: return Response({'code': 1, 'msg': '用户认证失败', 'data': None}) @@ -1664,7 +1664,7 @@ class JiedanView2(APIView): dashou_user = User.query.filter(UserUID=jiedan_dashou_id).first() if dashou_user: try: - dashou_ext = dashou_user.dashou_profile + dashou_ext = dashou_user.DashouProfile 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 @@ -1811,7 +1811,7 @@ class ShangjiaPaifaView(APIView): permission_classes = [IsAuthenticated] def post(self, request): - logger.info(f"商家派单请求: user_id={request.user.yonghuid}, data={request.data}") + logger.info(f"商家派单请求: user_id={request.user.UserUID}, data={request.data}") try: data = request.data @@ -1899,9 +1899,9 @@ class ShangjiaPaifaView(APIView): # ----- 8. 商家信息 ----- try: - shangjia = request.user.shop_profile + shangjia = request.user.ShopProfile except UserShangjia.DoesNotExist: - logger.warning(f"用户不是商家: {request.user.yonghuid}") + logger.warning(f"用户不是商家: {request.user.UserUID}") return Response({'code': 403, 'msg': '您不是商家'}) if shangjia.zhuangtai != 1: @@ -1917,7 +1917,7 @@ class ShangjiaPaifaView(APIView): if zhiding_uid: try: zhiding_user = User.query.get(UserUID=zhiding_uid) - zhiding_dashou = zhiding_user.dashou_profile + zhiding_dashou = zhiding_user.DashouProfile if zhiding_dashou.zhanghaozhuangtai != 1: return Response({'code': 400, 'msg': '指定打手状态异常'}) except User.DoesNotExist: @@ -1955,14 +1955,14 @@ class ShangjiaPaifaView(APIView): Description=dingdan_jieshao[:2000], Remark=dingdan_beizhu[:1000], Nickname=laoban_name[:50], - ImageURL=request.user.avatar, - User1ID=f"Sj{request.user.yonghuid}" + ImageURL=request.user.Avatar, + User1ID=f"Sj{request.user.UserUID}" ) logger.info(f"订单主表创建: {dingdan.OrderID}") MerchantOrderExt.query.create( Order=dingdan, - MerchantID=request.user.yonghuid, + MerchantID=request.user.UserUID, MerchantNickname=shangjia.nicheng, ) @@ -1981,7 +1981,7 @@ class ShangjiaPaifaView(APIView): ) update_shangjia_daily( - UserID=request.user.yonghuid, + UserID=request.user.UserUID, amount=Decimal(str(jiage)), action=1 ) @@ -2034,7 +2034,7 @@ class ShangjiaDingdanHuoquView(APIView): # 1. 获取当前登录用户的yonghuid try: current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID except AttributeError: return Response({ 'code': 1, @@ -2169,7 +2169,7 @@ class ShangjiaDingdanXiangqingView(APIView): # 1. 获取当前登录用户的 yonghuid try: current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID except AttributeError: return Response({ 'code': 1, @@ -2224,11 +2224,11 @@ class ShangjiaDingdanXiangqingView(APIView): if dashou_yonghuid: dashou_user = User.query.filter( UserUID=dashou_yonghuid - ).select_related('dashou_profile').first() + ).select_related('DashouProfile').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 '' + dashou_avatar = dashou_user.Avatar or '' + if hasattr(dashou_user, 'DashouProfile') and dashou_user.DashouProfile: + dashou_nicheng = dashou_user.DashouProfile.nicheng or '' # 6. 罚款数据(如果存在) fadan_obj = Penalty.query.filter( @@ -2339,10 +2339,10 @@ class ShangjiaJiesuanView(APIView): # 1. 获取当前登录用户 try: current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID try: - shangjia = current_user.shop_profile + shangjia = current_user.ShopProfile except UserShangjia.DoesNotExist: return Response({ 'code': 1, @@ -2427,7 +2427,7 @@ class ShangjiaJiesuanView(APIView): # 查询打手信息 try: dashou_user = User.query.get(UserUID=jiedan_dashou_id) - dashou = dashou_user.dashou_profile + dashou = dashou_user.DashouProfile except (User.DoesNotExist, UserDashou.DoesNotExist): return Response({ 'code': 8, @@ -2484,7 +2484,7 @@ class ShangjiaJiesuanView(APIView): # ✅ 新方法(行为驱动,更安全) update_shangjia_daily( - UserID=request.user.yonghuid, + UserID=request.user.UserUID, amount=Decimal(str(jine)), action=2 # 2 = 结算 ) @@ -2534,7 +2534,7 @@ class ShangjiaChexiaoView(APIView): try: current_user = request.user # 使用反向查询获取商家扩展信息(性能最佳) - shangjia = current_user.shop_profile + shangjia = current_user.ShopProfile except UserShangjia.DoesNotExist: return Response({ 'code': 1, @@ -2593,7 +2593,7 @@ class ShangjiaChexiaoView(APIView): # 7. 验证订单所属商家(使用反向查询) shangjia_kuozhan = MerchantOrderExt.query.filter( Order__OrderID=dingdan_id, - MerchantID=current_user.yonghuid + MerchantID=current_user.UserUID ).first() if not shangjia_kuozhan: @@ -2606,7 +2606,7 @@ class ShangjiaChexiaoView(APIView): # 8. 创建退款记录(注意:申请状态直接设为1-成功) RefundRecord.query.create( OrderID=dingdan_id, - ApplicantID=current_user.yonghuid, + ApplicantID=current_user.UserUID, Reason=chexiao_liyou, ApplyStatus=1, # 直接成功 Amount=dingdan.Amount, @@ -2635,7 +2635,7 @@ class ShangjiaChexiaoView(APIView): # 行为驱动 update_shangjia_daily( - UserID=request.user.yonghuid, + UserID=request.user.UserUID, amount=Decimal(str(dingdan.Amount)), action=3 # 3 = 退款 ) @@ -2670,7 +2670,7 @@ class ShangjiaTuikuanShenqingView(APIView): # 1. 获取当前登录用户并验证商家身份 try: current_user = request.user - shangjia = current_user.shop_profile + shangjia = current_user.ShopProfile except UserShangjia.DoesNotExist: return Response({ 'code': 1, @@ -2729,7 +2729,7 @@ class ShangjiaTuikuanShenqingView(APIView): # 7. 验证订单所属商家 shangjia_kuozhan = MerchantOrderExt.query.filter( Order__OrderID=dingdan_id, - MerchantID=current_user.yonghuid + MerchantID=current_user.UserUID ).first() if not shangjia_kuozhan: @@ -2744,7 +2744,7 @@ class ShangjiaTuikuanShenqingView(APIView): OrderID=dingdan_id, defaults={ 'PlayerID': dingdan.PlayerID, - 'ApplicantID': current_user.yonghuid, + 'ApplicantID': current_user.UserUID, 'Reason': tuikuan_liyou, 'ApplyStatus': 0, # 待处理 'Amount': dingdan.Amount, @@ -2771,8 +2771,8 @@ class ShangjiaTuikuanShenqingView(APIView): dashou_main = User.query.filter( UserUID=dingdan.PlayerID ).first() - if dashou_main and hasattr(dashou_main, 'dashou_profile'): - dashou_profile = dashou_main.dashou_profile + if dashou_main and hasattr(dashou_main, 'DashouProfile'): + dashou_profile = dashou_main.DashouProfile dashou_profile.zhuangtai = 1 dashou_profile.save() logger.info(f"退款申请成功,打手状态已更新:订单{dingdan_id},打手{dingdan.PlayerID}状态变更为1") @@ -2808,7 +2808,7 @@ class ShangjiaChufaShenqingView(APIView): # 1. 获取当前登录用户并验证商家身份 try: current_user = request.user - shangjia = current_user.shop_profile + shangjia = current_user.ShopProfile except UserShangjia.DoesNotExist: return Response({ 'code': 1, @@ -2852,7 +2852,7 @@ class ShangjiaChufaShenqingView(APIView): # 验证打手身份 try: dashou_user = User.query.get(UserUID=dashou_id) - dashou = dashou_user.dashou_profile + dashou = dashou_user.DashouProfile except User.DoesNotExist: return Response({ 'code': 5, @@ -2886,7 +2886,7 @@ class ShangjiaChufaShenqingView(APIView): # 验证订单所属商家 shangjia_kuozhan = MerchantOrderExt.query.filter( Order__OrderID=dingdan_id, - MerchantID=current_user.yonghuid + MerchantID=current_user.UserUID ).first() if not shangjia_kuozhan: @@ -2907,7 +2907,7 @@ class ShangjiaChufaShenqingView(APIView): # 创建处罚记录 chufajilu = PenaltyRecord.query.create( PlayerID=dashou_id, - ApplicantID=current_user.yonghuid, + ApplicantID=current_user.UserUID, PenaltyReason=chufa_liyou, ApplyStatus=0, # 待处理 DeductedPoints=5, # 扣除5积分 @@ -2930,7 +2930,7 @@ class ShangjiaChufaShenqingView(APIView): if tupian_url and tupian_url.strip(): PenaltyEvidenceImage.query.create( OrderID=dingdan_id, - UserID=current_user.yonghuid, + UserID=current_user.UserUID, ImageURL=tupian_url ) @@ -3031,8 +3031,8 @@ class DashouDingdanHuoquView(APIView): if zhiding_ids: dashou_query = User.query.filter( UserUID__in=zhiding_ids - ).select_related('dashou_profile').annotate( - Nickname=F('dashou_profile__nicheng') + ).select_related('DashouProfile').annotate( + Nickname=F('DashouProfile__nicheng') ).values('UserUID', 'Avatar', 'Nickname') zhiding_info = { item['UserUID']: { @@ -3418,7 +3418,7 @@ class DaiLiQunLiaoXiaoXiView(APIView): return Response({'code': 400, 'msg': 'identityType 无效'}) # ===== 2. 构造 senderId ===== - uid = request.user.yonghuid + uid = request.user.UserUID prefix_map = {'PlayerID': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'} sender_id = prefix_map[identity_type] + uid logger.info(f"[群聊代理] 登录用户 UserID={uid}, 构造 senderId={sender_id}") @@ -3557,26 +3557,26 @@ class QiangdanView(APIView): # ==================== 【新增】罚单检查 ==================== - fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(request.user.yonghuid,1) + fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(request.user.UserUID,1) if not fadan_ok: return Response({'code': 400, 'msg': fadan_msg}) # ========================================================= # ========== 标签快速校验(提前拒绝,避免锁资源) ========== - biaoqian_ok, biaoqian_msg = check_dashou_biaoqian_required(dingdan_id, request.user.yonghuid) + biaoqian_ok, biaoqian_msg = check_dashou_biaoqian_required(dingdan_id, request.user.UserUID) if not biaoqian_ok: return Response({'code': 400, 'msg': biaoqian_msg}) # =========================================================== # 2. 锁定并验证订单(行锁) - order_result = self._validate_and_lock_order(dingdan_id, request.user.yonghuid) + order_result = self._validate_and_lock_order(dingdan_id, request.user.UserUID) if isinstance(order_result, str): return Response({'code': 400, 'msg': order_result}) order = order_result - validation_result = self._validate_order_requirements(order, dashou_profile, request.user.yonghuid) + validation_result = self._validate_order_requirements(order, dashou_profile, request.user.UserUID) if not validation_result['success']: return Response({'code': 400, 'msg': validation_result['message']}) - self._execute_qiangdan(order, dashou_profile, request.user.yonghuid) + self._execute_qiangdan(order, dashou_profile, request.user.UserUID) # ========== 【唯一改动】聊天建立方式 ========== chat_success = establish_order_chat(dingdan_id) @@ -3619,7 +3619,7 @@ class QiangdanView(APIView): # ---------- 以下所有方法原封不动照抄旧版 ---------- def _validate_dashou_status(self, user): try: - dashou_profile = user.dashou_profile + dashou_profile = user.DashouProfile if (dashou_profile.zhuangtai != 1 or dashou_profile.zhanghaozhuangtai != 1 or dashou_profile.DeductedPoints < 5): @@ -3695,7 +3695,7 @@ class DashouDingdanHuoquView1(APIView): # 1. 获取当前登录用户的 yonghuid try: current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID except AttributeError: return Response({ 'code': 1, @@ -3829,13 +3829,13 @@ class DashouCOSZhengshuView(APIView): try: chufa_record = PenaltyRecord.query.filter( OrderID=dingdan_id, - PlayerID=request.user.yonghuid, + PlayerID=request.user.UserUID, ApplyStatus__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}'}, + 'error': f'No punishment record found for user {request.user.UserUID} on order {dingdan_id}'}, status=status.HTTP_403_FORBIDDEN ) except Exception as e: @@ -3852,12 +3852,12 @@ class DashouCOSZhengshuView(APIView): try: dingdan = Order.query.get( OrderID=dingdan_id, - PlayerID=request.user.yonghuid, + PlayerID=request.user.UserUID, Status__in=[2, 8] ) except Order.DoesNotExist: return Response( - {'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found or not assigned to user {request.user.yonghuid}'}, + {'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found or not assigned to user {request.user.UserUID}'}, status=status.HTTP_403_FORBIDDEN ) @@ -3968,7 +3968,7 @@ class ShangjiaCOSZhengshuView(APIView): # 2. 🔴【修改】验证商家身份 - 通过反向查询shop_profile判断是否为商家 try: # 检查是否有商家扩展表 - shangjia_profile = request.user.shop_profile + shangjia_profile = request.user.ShopProfile # 验证商家状态是否为1(正常) if shangjia_profile.zhuangtai != 1: @@ -3976,7 +3976,7 @@ class ShangjiaCOSZhengshuView(APIView): status=status.HTTP_403_FORBIDDEN) # 🔴【重要】获取商家ID(从商家扩展表中获取) - shangjia_id = request.user.yonghuid # 假设商家扩展表中有yonghuid字段 + shangjia_id = request.user.UserUID # 假设商家扩展表中有yonghuid字段 except AttributeError: # 没有shop_profile属性,说明不是商家 @@ -4179,10 +4179,10 @@ class DashouTijiaoView(APIView): # 3. 获取当前用户并验证打手身份 current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID try: - dashou_profile = current_user.dashou_profile + dashou_profile = current_user.DashouProfile except UserDashou.DoesNotExist: return Response({ 'code': 6, @@ -4271,10 +4271,10 @@ class DashouDingdanXiangqingView(APIView): # 2. 获取当前用户并验证权限 current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID try: - dashou_profile = current_user.dashou_profile + dashou_profile = current_user.DashouProfile except UserDashou.DoesNotExist: return Response({ 'code': 2, @@ -4426,14 +4426,14 @@ class AdGetDingdanXiangQing(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -4441,7 +4441,7 @@ class AdGetDingdanXiangQing(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -4633,14 +4633,14 @@ class AdGengHuanDaShou(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -4648,7 +4648,7 @@ class AdGengHuanDaShou(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -4685,7 +4685,7 @@ class AdGengHuanDaShou(APIView): # 9. 获取新打手扩展表 try: - new_dashou_profile = new_dashou_user.dashou_profile + new_dashou_profile = new_dashou_user.DashouProfile except AttributeError: return Response( {'code': 404, 'message': '打手信息不完整', 'data': None}, @@ -4774,7 +4774,7 @@ class AdGengHuanDaShou(APIView): ) # 获取原打手扩展表 - old_dashou_profile = old_dashou_user.dashou_profile + old_dashou_profile = old_dashou_user.DashouProfile # 更新原打手状态 old_dashou_profile.zhuangtai = 1 # 空闲 @@ -4852,14 +4852,14 @@ class AdQiangZhiJieDan(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -4867,7 +4867,7 @@ class AdQiangZhiJieDan(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -4918,7 +4918,7 @@ class AdQiangZhiJieDan(APIView): ) # 获取打手扩展表 - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile # 更新打手扩展表数据 # ✅ 确认字段:chengjiaozongliang 成交单总量 @@ -4961,7 +4961,7 @@ class AdQiangZhiJieDan(APIView): ) # 获取商家扩展表 - shangjia_profile = shangjia_user.shop_profile + shangjia_profile = shangjia_user.ShopProfile # ✅ 确认字段:chengjiao 商家成交订单总量 shangjia_profile.chengjiao += 1 @@ -5035,14 +5035,14 @@ class AdJuJueTuiKuan(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -5050,7 +5050,7 @@ class AdJuJueTuiKuan(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -5094,7 +5094,7 @@ class AdJuJueTuiKuan(APIView): ) # 获取打手扩展表 - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile # 更新打手扩展表数据 # ✅ 确认字段:chengjiaozongliang 成交单总量 @@ -5137,7 +5137,7 @@ class AdJuJueTuiKuan(APIView): ) # 获取商家扩展表 - shangjia_profile = shangjia_user.shop_profile + shangjia_profile = shangjia_user.ShopProfile # ✅ 确认字段:chengjiao 商家成交订单总量 shangjia_profile.chengjiao += 1 @@ -5236,14 +5236,14 @@ class AdTongYiTuiKuanShangJia(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -5251,7 +5251,7 @@ class AdTongYiTuiKuanShangJia(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -5259,7 +5259,7 @@ class AdTongYiTuiKuanShangJia(APIView): ) # 6. 验证支付密码(对比openid) - if pay_password != dangqianyonghu.openid: + if pay_password != dangqianyonghu.OpenID: return Response( {'code': 401, 'message': '支付密码错误', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -5325,7 +5325,7 @@ class AdTongYiTuiKuanShangJia(APIView): ) # 获取打手扩展表 - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile # ✅ 确认字段:tuikuanliang 退款订单总量 dashou_profile.tuikuanliang += 1 @@ -5350,7 +5350,7 @@ class AdTongYiTuiKuanShangJia(APIView): ) # 获取商家扩展表 - shangjia_profile = shangjia_user.shop_profile + shangjia_profile = shangjia_user.ShopProfile # ✅ 确认字段:tuikuan 退款订单总量 shangjia_profile.tuikuan += 1 @@ -5444,7 +5444,7 @@ class AdTongYiTuiKuanPingTai(APIView): ) # 4. 验证用户类型是否为管理员 - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -5452,7 +5452,7 @@ class AdTongYiTuiKuanPingTai(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -5460,7 +5460,7 @@ class AdTongYiTuiKuanPingTai(APIView): ) # 6. 验证支付密码(对比openid) - if pay_password != dangqianyonghu.openid: + if pay_password != dangqianyonghu.OpenID: return Response( {'code': 401, 'message': '支付密码错误', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -5530,7 +5530,7 @@ class AdTongYiTuiKuanPingTai(APIView): if jiedan_dashou_id: try: dashou_user = User.query.get(UserUID=jiedan_dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.zhuangtai = 1 # 打手状态设为空闲 dashou_profile.tuikuanliang += 1 dashou_profile.save() @@ -5541,7 +5541,7 @@ class AdTongYiTuiKuanPingTai(APIView): if laoban_id: try: laoban_user = User.query.get(UserUID=laoban_id) - laoban_profile = laoban_user.boss_profile + laoban_profile = laoban_user.BossProfile laoban_profile.alltui += 1 laoban_profile.zonge -= jine if laoban_profile.zonge < 0: @@ -5738,7 +5738,7 @@ class DashouHuoquLeixingView(APIView): 3. 数据已排序:paixu降序 → id升序 """ try: - logger.info(f"打手获取商品类型请求 - 用户ID: {request.user.yonghuid}") + logger.info(f"打手获取商品类型请求 - 用户ID: {request.user.UserUID}") # 🔴 查询所有正常状态的商品类型 # 条件:shenhezhuangtai=1(正常状态) @@ -5762,7 +5762,7 @@ class DashouHuoquLeixingView(APIView): }) # 🔴 记录日志 - logger.info(f"返回商品类型数据:{len(leixing_list)}条,用户: {request.user.yonghuid}") + logger.info(f"返回商品类型数据:{len(leixing_list)}条,用户: {request.user.UserUID}") # 🔴 返回成功响应 # 注意:保持与前端原数据格式完全一致 @@ -5776,7 +5776,7 @@ class DashouHuoquLeixingView(APIView): except Exception as e: # 🔴 异常处理 - logger.error(f"打手获取商品类型失败 - 用户ID: {request.user.yonghuid}, 错误: {str(e)}", exc_info=True) + logger.error(f"打手获取商品类型失败 - 用户ID: {request.user.UserUID}, 错误: {str(e)}", exc_info=True) return Response({ "code": 500, @@ -5821,14 +5821,14 @@ class AdJuJueJieSuan(APIView): dangqianyonghu = request.user # 3. 验证手机号匹配(管理员账号通常为手机号) - if str(dangqianyonghu.phone) != qianzhanghao: + if str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型为管理员 - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -5866,7 +5866,7 @@ class AdJuJueJieSuan(APIView): # 8. 更新打手扩展表 try: dashou_user = User.query.get(UserUID=jiedan_dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.zhuangtai = 1 # 打手状态设为正常 dashou_profile.tuikuanliang += 1 # 退款订单总量+1 dashou_profile.save() @@ -5909,13 +5909,13 @@ class AdZhuanYiDaTing(APIView): dangqianyonghu = request.user - if str(dangqianyonghu.phone) != qianzhanghao: + if str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -5948,7 +5948,7 @@ class AdZhuanYiDaTing(APIView): if jiedan_dashou_id: try: dashou_user = User.query.get(UserUID=jiedan_dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.zhuangtai = 1 dashou_profile.tuikuanliang += 1 dashou_profile.save() @@ -5991,13 +5991,13 @@ class AdQuXiaoZhiDing(APIView): dangqianyonghu = request.user - if str(dangqianyonghu.phone) != qianzhanghao: + if str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -6078,11 +6078,11 @@ class DashouXiugaiView(APIView): # ---------- 用户身份验证 ---------- current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID # 检查是否为打手 try: - dashou_profile = current_user.dashou_profile # 假设User模型有related_name='dashou_profile' + dashou_profile = current_user.DashouProfile # User 模型反向 related_name='DashouProfile' except UserDashou.DoesNotExist: return Response({'code': 5, 'msg': '用户不是打手身份', 'data': None}, status=status.HTTP_403_FORBIDDEN) @@ -6173,7 +6173,7 @@ class ZxsjghdsView(APIView): # ========== 1. 获取当前商家用户 ========== current_user = request.user try: - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID except AttributeError: return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED) @@ -6226,7 +6226,7 @@ class ZxsjghdsView(APIView): if dashou_id: try: dashou_user = User.query.get(UserUID=dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.tuikuanliang = (dashou_profile.tuikuanliang or 0) + 1 dashou_profile.zhuangtai = 1 # 空闲 dashou_profile.save(update_fields=['tuikuanliang', 'zhuangtai']) @@ -6371,7 +6371,7 @@ class ShangjiaFakuanApplyView(APIView): # ========== 1. 身份验证 ========== current_user = request.user try: - shangjia_id = current_user.yonghuid # 商家的用户ID + shangjia_id = current_user.UserUID # 商家的用户ID except AttributeError: return Response({'code': 1, 'msg': '用户认证失败'}, status=status.HTTP_401_UNAUTHORIZED) @@ -6439,7 +6439,7 @@ class ShangjiaFakuaiXiugaiView(APIView): user = request.user # 验证商家身份 try: - shangjia = user.shop_profile + shangjia = user.ShopProfile except UserShangjia.DoesNotExist: return Response({'code': 403, 'msg': '您不是商家'}, status=status.HTTP_403_FORBIDDEN) if shangjia.zhuangtai != 1: @@ -6455,7 +6455,7 @@ class ShangjiaFakuaiXiugaiView(APIView): dingdan = Order.query.select_related('shangjia_kuozhan').get(OrderID=dingdan_id) except Order.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) - if not hasattr(dingdan, 'shangjia_kuozhan') or dingdan.shangjia_kuozhan.MerchantID != user.yonghuid: + if not hasattr(dingdan, 'shangjia_kuozhan') or dingdan.shangjia_kuozhan.MerchantID != user.UserUID: return Response({'code': 403, 'msg': '无权操作此订单'}, status=status.HTTP_403_FORBIDDEN) # 处理订单相关操作 diff --git a/products/views.py b/products/views.py index a820e71..e4b41ed 100644 --- a/products/views.py +++ b/products/views.py @@ -241,9 +241,9 @@ class DashouHuiyuanList(APIView): try: # 1. 检查用户是否是打手(通过反向查询) try: - dashou = request.user.dashou_profile + dashou = request.user.DashouProfile except UserDashou.DoesNotExist: - logger.warning(f"用户{request.user.yonghuid}不是打手,尝试访问打手接口") + logger.warning(f"用户{request.user.UserUID}不是打手,尝试访问打手接口") return Response({ 'code': 400, 'message': '用户不是打手,无法访问此接口' @@ -251,7 +251,7 @@ class DashouHuiyuanList(APIView): # 2. 检查打手账号状态 if dashou.zhanghaozhuangtai != 1: - logger.warning(f"打手{request.user.yonghuid}账号状态异常: {dashou.zhanghaozhuangtai}") + logger.warning(f"打手{request.user.UserUID}账号状态异常: {dashou.zhanghaozhuangtai}") return Response({ 'code': 400, 'message': '账号状态异常,无法购买会员' @@ -307,7 +307,7 @@ class YajinGoumai(APIView): try: # 1. 验证用户身份和状态 try: - dashou = request.user.dashou_profile + dashou = request.user.DashouProfile except UserDashou.DoesNotExist: return Response({ 'code': 400, @@ -354,20 +354,20 @@ class YajinGoumai(APIView): chongzhi_jilu = Czjilu.query.create( dingdan_id=dingdanid, zhuangtai=9, # 未支付状态 - yonghuid=request.user.yonghuid, + yonghuid=request.user.UserUID, jine=jine, leixing=2, # 充值类型:2=押金 shuoming=settings.YAJIN_PAY_DESCRIPTION ) - logger.info(f"创建押金充值订单: 用户{request.user.yonghuid}, 订单{dingdanid}, 金额{jine}元") + logger.info(f"创建押金充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, 金额{jine}元") # 5. 生成微信支付参数 try: pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=jine, - openid=request.user.openid, # 直接从request.user获取openid + openid=request.user.OpenID, # 直接从request.user获取openid pay_type='yajin' ) except Exception as e: @@ -647,7 +647,7 @@ class YajinHuitiao(View): dashouid=order.yonghuid, shuoming='押金分红', fenhong=fenhong_jine, - avatar=user_main.avatar, + avatar=user_main.Avatar, nicheng=dashou.nicheng or '未知打手', fenhong_leixing=2, ) @@ -799,7 +799,7 @@ class YajinShibai(APIView): try: order = Czjilu.query.get( dingdan_id=dingdanid, - UserUID=request.user.yonghuid + UserUID=request.user.UserUID ) except Czjilu.DoesNotExist: return Response({ @@ -811,7 +811,7 @@ class YajinShibai(APIView): if order.zhuangtai == 9: # 删除订单 order.delete() - logger.info(f"用户{request.user.yonghuid}删除未支付订单: {dingdanid}") + logger.info(f"用户{request.user.UserUID}删除未支付订单: {dingdanid}") return Response({ 'code': 200, @@ -857,7 +857,7 @@ class YajinLunxun(APIView): try: order = Czjilu.query.get( dingdan_id=dingdanid, - UserUID=request.user.yonghuid + UserUID=request.user.UserUID ) except Czjilu.DoesNotExist: return Response({ @@ -875,10 +875,10 @@ class YajinLunxun(APIView): # 4. 获取打手押金(通过反向查询,最快捷的方式) try: - dashou = request.user.dashou_profile + dashou = request.user.DashouProfile yajin = float(dashou.yajin) # 转为浮点数方便前端处理 - logger.info(f"轮询成功: 订单{dingdanid}已支付,用户{request.user.yonghuid}押金为{yajin}元") + logger.info(f"轮询成功: 订单{dingdanid}已支付,用户{request.user.UserUID}押金为{yajin}元") return Response({ 'code': 200, @@ -887,7 +887,7 @@ class YajinLunxun(APIView): }, status=status.HTTP_200_OK) except UserDashou.DoesNotExist: - logger.error(f"打手信息不存在: 用户{request.user.yonghuid}") + logger.error(f"打手信息不存在: 用户{request.user.UserUID}") return Response({ 'code': 400, 'message': '打手信息不存在' @@ -917,7 +917,7 @@ class JifenBuchong(APIView): try: # 1. 验证用户身份和状态 try: - dashou = request.user.dashou_profile + dashou = request.user.DashouProfile except UserDashou.DoesNotExist: return Response({ 'code': 400, @@ -950,20 +950,20 @@ class JifenBuchong(APIView): chongzhi_jilu = Czjilu.query.create( dingdan_id=dingdanid, zhuangtai=9, # 未支付状态 - yonghuid=request.user.yonghuid, + yonghuid=request.user.UserUID, jine=jine, leixing=3, # 充值类型:3=积分 shuoming=settings.JIFEN_PAY_DESCRIPTION ) - logger.info(f"创建积分充值订单: 用户{request.user.yonghuid}, 订单{dingdanid}, 金额{jine}元") + logger.info(f"创建积分充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, 金额{jine}元") # 6. 生成微信支付参数(完整版) try: pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=jine, - openid=request.user.openid, + openid=request.user.OpenID, pay_type='jifen' ) except Exception as e: @@ -1343,7 +1343,7 @@ class JifenShibai(APIView): try: order = Czjilu.query.get( dingdan_id=dingdanid, - UserUID=request.user.yonghuid + UserUID=request.user.UserUID ) except Czjilu.DoesNotExist: return Response({ @@ -1362,7 +1362,7 @@ class JifenShibai(APIView): if order.zhuangtai == 9: # 删除订单 order.delete() - logger.info(f"用户{request.user.yonghuid}删除未支付积分订单: {dingdanid}") + logger.info(f"用户{request.user.UserUID}删除未支付积分订单: {dingdanid}") return Response({ 'code': 200, @@ -1408,7 +1408,7 @@ class JifenLunxun(APIView): try: order = Czjilu.query.get( dingdan_id=dingdanid, - UserUID=request.user.yonghuid + UserUID=request.user.UserUID ) except Czjilu.DoesNotExist: return Response({ @@ -1433,10 +1433,10 @@ class JifenLunxun(APIView): # 5. 获取打手积分(通过反向查询) try: - dashou = request.user.dashou_profile + dashou = request.user.DashouProfile jifen = dashou.jifen # 直接使用整数 - logger.info(f"积分轮询成功: 订单{dingdanid}已支付,用户{request.user.yonghuid}积分为{jifen}") + logger.info(f"积分轮询成功: 订单{dingdanid}已支付,用户{request.user.UserUID}积分为{jifen}") return Response({ 'code': 200, @@ -1445,7 +1445,7 @@ class JifenLunxun(APIView): }, status=status.HTTP_200_OK) except UserDashou.DoesNotExist: - logger.error(f"打手信息不存在: 用户{request.user.yonghuid}") + logger.error(f"打手信息不存在: 用户{request.user.UserUID}") return Response({ 'code': 400, 'message': '打手信息不存在' @@ -1483,7 +1483,7 @@ class HuiyuanGoumai(APIView): # 2. 验证用户身份和状态 try: - dashou = request.user.dashou_profile + dashou = request.user.DashouProfile except UserDashou.DoesNotExist: return Response({ 'code': 400, @@ -1521,7 +1521,7 @@ class HuiyuanGoumai(APIView): chongzhi_jilu = Czjilu.query.create( dingdan_id=dingdanid, zhuangtai=9, # 未支付状态 - yonghuid=request.user.yonghuid, + yonghuid=request.user.UserUID, jine=jine, leixing=1, # 充值类型:1=会员 shuoming=shuoming, @@ -1529,14 +1529,14 @@ class HuiyuanGoumai(APIView): ) logger.info( - f"创建会员充值订单: 用户{request.user.yonghuid}, 订单{dingdanid}, 会员{huiyuanid}, 金额{jine}元") + f"创建会员充值订单: 用户{request.user.UserUID}, 订单{dingdanid}, 会员{huiyuanid}, 金额{jine}元") # 7. 生成微信支付参数 try: pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=jine, - openid=request.user.openid, + openid=request.user.OpenID, pay_type='huiyuan' ) except Exception as e: @@ -1899,7 +1899,7 @@ class HuiyuanHuitiao(View): # 获取打手头像和昵称 user_main = dashou.user - avatar = user_main.avatar + avatar = user_main.Avatar nicheng = dashou.nicheng or "未知打手" # 查询该打手针对该管事的已分红次数 @@ -2145,7 +2145,7 @@ class HuiyuanShibai(APIView): try: order = Czjilu.query.get( dingdan_id=dingdanid, - UserUID=request.user.yonghuid + UserUID=request.user.UserUID ) except Czjilu.DoesNotExist: return Response({ @@ -2164,7 +2164,7 @@ class HuiyuanShibai(APIView): if order.zhuangtai == 9: # 删除订单 order.delete() - logger.info(f"用户{request.user.yonghuid}删除未支付会员订单: {dingdanid}") + logger.info(f"用户{request.user.UserUID}删除未支付会员订单: {dingdanid}") return Response({ 'code': 200, @@ -2210,7 +2210,7 @@ class HuiyuanLunxun(APIView): try: order = Czjilu.query.get( dingdan_id=dingdanid, - UserUID=request.user.yonghuid + UserUID=request.user.UserUID ) except Czjilu.DoesNotExist: return Response({ @@ -2239,7 +2239,7 @@ class HuiyuanLunxun(APIView): # 6. 查询会员购买记录 goumai_record = Huiyuangoumai.query.filter( - yonghu_id=request.user.yonghuid, + yonghu_id=request.user.UserUID, huiyuan_id=order.huiyuan_id ).first() @@ -2255,9 +2255,9 @@ class HuiyuanLunxun(APIView): # 直接格式化,不使用时区转换 daoqi_time = goumai_record.daoqi_time.strftime('%Y-%m-%d %H:%M:%S') current_user = request.user - dashou = current_user.dashou_profile + dashou = current_user.DashouProfile - logger.info(f"会员轮询成功: 订单{dingdanid}已支付,用户{request.user.yonghuid}, 会员{order.huiyuan_id}") + logger.info(f"会员轮询成功: 订单{dingdanid}已支付,用户{request.user.UserUID}, 会员{order.huiyuan_id}") # 8. 返回会员信息(用于更新前端全局变量) return Response({ @@ -2304,9 +2304,9 @@ class ShangjiaChongzhi(APIView): try: # 1. 验证用户身份和商家状态 try: - shangjia = request.user.shop_profile + shangjia = request.user.ShopProfile except UserShangjia.DoesNotExist: - logger.warning(f"用户{request.user.yonghuid}尝试充值但不是商家") + logger.warning(f"用户{request.user.UserUID}尝试充值但不是商家") return Response({ 'code': 400, 'message': '用户不是商家,无法充值' @@ -2355,7 +2355,7 @@ class ShangjiaChongzhi(APIView): chongzhi_jilu = Czjilu.query.create( dingdan_id=dingdanid, zhuangtai=9, # 未支付状态 - yonghuid=request.user.yonghuid, + yonghuid=request.user.UserUID, jine=jine, leixing=4, # 充值类型:4=商家充值 shuoming=settings.SHANGJIA_CZ_PAY_DESCRIPTION @@ -2373,7 +2373,7 @@ class ShangjiaChongzhi(APIView): pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=jine, - openid=request.user.openid, + openid=request.user.OpenID, pay_type='shangjia' ) except Exception as e: @@ -2750,11 +2750,11 @@ class ShangjiaShibai(APIView): try: order = Czjilu.query.get( dingdan_id=dingdanid, - UserUID=request.user.yonghuid, + UserUID=request.user.UserUID, leixing=4 # 确保是商家充值订单 ) except Czjilu.DoesNotExist: - logger.warning(f"商家订单不存在或不属于当前用户: {dingdanid}, 用户{request.user.yonghuid}") + logger.warning(f"商家订单不存在或不属于当前用户: {dingdanid}, 用户{request.user.UserUID}") return Response({ 'code': 400, 'message': '订单不存在或不属于当前用户' @@ -2764,7 +2764,7 @@ class ShangjiaShibai(APIView): if order.zhuangtai == 9: # 删除订单 order.delete() - logger.info(f"商家{request.user.shop_profile.nicheng}删除未支付订单: {dingdanid}") + logger.info(f"商家{request.user.ShopProfile.nicheng}删除未支付订单: {dingdanid}") return Response({ 'code': 200, @@ -2816,11 +2816,11 @@ class ShangjiaChenggong(APIView): try: order = Czjilu.query.get( dingdan_id=dingdanid, - UserUID=request.user.yonghuid, + UserUID=request.user.UserUID, leixing=4 # 商家充值类型 ) except Czjilu.DoesNotExist: - logger.warning(f"商家订单不存在: {dingdanid}, 用户{request.user.yonghuid}") + logger.warning(f"商家订单不存在: {dingdanid}, 用户{request.user.UserUID}") return Response({ 'code': 400, 'message': '订单不存在或不属于当前用户' @@ -2836,7 +2836,7 @@ class ShangjiaChenggong(APIView): # 4. 获取商家余额(通过反向查询) try: - shangjia = request.user.shop_profile + shangjia = request.user.ShopProfile yue = float(shangjia.yue) # 转为浮点数方便前端处理 logger.info(f"商家轮询成功: 订单{dingdanid}已支付,商家{shangjia.nicheng}余额为{yue}元") @@ -2848,7 +2848,7 @@ class ShangjiaChenggong(APIView): }, status=status.HTTP_200_OK) except UserShangjia.DoesNotExist: - logger.error(f"商家信息不存在: 用户{request.user.yonghuid}") + logger.error(f"商家信息不存在: 用户{request.user.UserUID}") return Response({ 'code': 400, 'message': '商家信息不存在' @@ -2877,7 +2877,7 @@ class GuanshiFenhongListAPI(APIView): def post(self, request): try: # 获取当前管事用户ID - yonghuid = request.user.yonghuid + yonghuid = request.user.UserUID # 获取分页参数 page = int(request.data.get('page', 1)) @@ -2962,7 +2962,7 @@ class AdGetAllDataView(APIView): zhanghao = request.data.get('zhanghao') if not zhanghao: - logger.warning(f"管理员接口:未接收到账号参数,请求用户:{request.user.yonghuid}") + logger.warning(f"管理员接口:未接收到账号参数,请求用户:{request.user.UserUID}") return Response({ 'code': 400, 'message': '缺少账号参数', @@ -2973,9 +2973,9 @@ class AdGetAllDataView(APIView): current_user = request.user # 3. 验证用户类型是否为管理员 - if current_user.user_type != 'admin': + if current_user.UserType != 'admin': logger.warning( - f"权限验证失败:用户 {current_user.yonghuid} 的用户类型为 {current_user.user_type},非管理员") + f"权限验证失败:用户 {current_user.UserUID} 的用户类型为 {current_user.UserType},非管理员") return Response({ 'code': 401, 'message': '无管理员权限', @@ -2983,9 +2983,9 @@ class AdGetAllDataView(APIView): }, status=status.HTTP_401_UNAUTHORIZED) # 4. 验证手机号与传递的账号是否一致 - if current_user.phone != zhanghao: + if current_user.Phone != zhanghao: logger.warning( - f"账号验证失败:用户 {current_user.yonghuid} 的phone={current_user.phone},传递的zhanghao={zhanghao}") + f"账号验证失败:用户 {current_user.UserUID} 的phone={current_user.Phone},传递的zhanghao={zhanghao}") return Response({ 'code': 401, 'message': '账号验证失败', @@ -2994,11 +2994,11 @@ class AdGetAllDataView(APIView): # 5. 验证管理员扩展表是否存在 try: - admin_profile = current_user.admin_profile - logger.info(f"管理员验证通过:用户 {current_user.yonghuid},手机号 {current_user.phone}") + admin_profile = current_user.AdminProfile + logger.info(f"管理员验证通过:用户 {current_user.UserUID},手机号 {current_user.Phone}") except AdminProfile.DoesNotExist: logger.error( - f"管理员扩展表不存在:用户 {current_user.yonghuid} 虽然user_type=admin,但没有AdminProfile记录") + f"管理员扩展表不存在:用户 {current_user.UserUID} 虽然user_type=admin,但没有AdminProfile记录") return Response({ 'code': 401, 'message': '管理员信息不完整', @@ -3124,14 +3124,14 @@ class ShangpinLeixingFabuView(APIView): ) # 验证账号是否匹配(使用phone字段比较) - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response( {'code': 401, 'msg': '账号验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 1.2 验证是否为管理员(检查user_type字段) - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response( {'code': 401, 'msg': '权限不足', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -3324,14 +3324,14 @@ class ShangpinZhuanquFabuView(APIView): status=status.HTTP_401_UNAUTHORIZED ) - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response( {'code': 401, 'msg': '账号验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 1.2 验证是否为管理员(检查user_type字段) - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response( {'code': 401, 'msg': '权限不足', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -3433,14 +3433,14 @@ class ShangpinFabuView(APIView): status=status.HTTP_401_UNAUTHORIZED ) - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response( {'code': 401, 'msg': '账号验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 1.2 验证是否为管理员(检查user_type字段) - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response( {'code': 401, 'msg': '权限不足', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -3737,14 +3737,14 @@ class GuizeTupianShangchuanView(APIView): status=status.HTTP_401_UNAUTHORIZED ) - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response( {'code': 401, 'msg': '账号验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 1.2 验证是否为管理员(检查user_type字段) - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response( {'code': 401, 'msg': '权限不足', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -3880,14 +3880,14 @@ class AdHuiyuanHqView(APIView): ) # 验证账号是否匹配(使用phone字段比较) - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response( {'code': 401, 'msg': '权限验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 1.2 验证是否为管理员(检查user_type字段) - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response( {'code': 401, 'msg': '权限验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -3987,14 +3987,14 @@ class HuiyuanXiugaiView(APIView): status=status.HTTP_401_UNAUTHORIZED ) - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response( {'code': 401, 'msg': '权限验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 1.2 验证是否为管理员(检查user_type字段) - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response( {'code': 401, 'msg': '权限验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -4160,14 +4160,14 @@ class AdHuiyuanFbView(APIView): status=status.HTTP_401_UNAUTHORIZED ) - if user_main.phone != zhanghao: + if user_main.Phone != zhanghao: return Response( {'code': 401, 'msg': '权限验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 1.2 验证是否为管理员 - if user_main.user_type != 'admin': + if user_main.UserType != 'admin': return Response( {'code': 401, 'msg': '权限验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -4339,14 +4339,14 @@ class AdShangpinHuQu(APIView): dangqianyonghu = request.user # 3. 验证账号匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型为管理员 - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED @@ -4354,7 +4354,7 @@ class AdShangpinHuQu(APIView): # 5. 验证管理员扩展表数据 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AdminProfile.DoesNotExist: return Response( {'code': 401, 'message': '身份验证失败'}, @@ -4456,14 +4456,14 @@ class AdGonggaoXiuGai(APIView): dangqianyonghu = request.user # 3. 验证账号匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型为管理员 - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED @@ -4471,7 +4471,7 @@ class AdGonggaoXiuGai(APIView): # 5. 验证管理员扩展表数据 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AdminProfile.DoesNotExist: return Response( {'code': 401, 'message': '身份验证失败'}, @@ -4545,20 +4545,20 @@ class AdZhuanquXiuGai(APIView): dangqianyonghu = request.user - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED ) - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED ) try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AdminProfile.DoesNotExist: return Response( {'code': 401, 'message': '身份验证失败'}, @@ -4645,20 +4645,20 @@ class AdLunboXiuGai(APIView): dangqianyonghu = request.user - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED ) - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED ) try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AdminProfile.DoesNotExist: return Response( {'code': 401, 'message': '身份验证失败'}, @@ -4802,14 +4802,14 @@ class AdLeixingXiuGai(APIView): return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) dangqianyonghu = request.user - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AdminProfile.DoesNotExist: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) @@ -5062,7 +5062,7 @@ class AdShangpinNeiRongHuoQu(APIView): dangqianyonghu = request.user # 验证phone字段匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: logger.warning( f"账号不匹配: 当前用户phone={getattr(dangqianyonghu, 'phone', '无')}, 前端账号={qianzhanghao}") return Response( @@ -5071,8 +5071,8 @@ class AdShangpinNeiRongHuoQu(APIView): ) # 验证用户类型 - if dangqianyonghu.user_type != 'admin': - logger.warning(f"非管理员访问: user_type={dangqianyonghu.user_type}") + if dangqianyonghu.UserType != 'admin': + logger.warning(f"非管理员访问: user_type={dangqianyonghu.UserType}") return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -5080,7 +5080,7 @@ class AdShangpinNeiRongHuoQu(APIView): # 3. 验证管理员扩展表 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AdminProfile.DoesNotExist: logger.warning(f"管理员扩展表不存在: user_id={dangqianyonghu.id}") return Response( @@ -5165,20 +5165,20 @@ class AdShangpinXiuGai(APIView): # 验证身份 dangqianyonghu = request.user - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AdminProfile.DoesNotExist: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -5424,20 +5424,20 @@ class AdShangpinGuizeXiuGai(APIView): # 验证身份 dangqianyonghu = request.user - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AdminProfile.DoesNotExist: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -5581,7 +5581,7 @@ class HuiyuanTypeListView(APIView): user = request.user # 检查是否是管理员 - if not hasattr(user, 'admin_profile'): + if not hasattr(user, 'AdminProfile'): return Response({ 'code': 401, 'message': '无权限访问', @@ -5639,7 +5639,7 @@ class HuiyuanRecordListView(APIView): user = request.user # 检查是否是管理员 - if not hasattr(user, 'admin_profile'): + if not hasattr(user, 'AdminProfile'): return Response({ 'code': 401, 'message': '无权限访问', @@ -5871,10 +5871,10 @@ class ZuzhangFenhongView(APIView): fenhong_zonge = zuzhang.fenyong_zonge # 3.2 邀请管事数(统计邀请人等于当前用户ID的管事记录) - yaoqing_guanshi = UserGuanshi.query.filter(yaoqingren=current_user.yonghuid).count() + yaoqing_guanshi = UserGuanshi.query.filter(yaoqingren=current_user.UserUID).count() # 3.3 分红次数(总记录数) - base_queryset = Gsfenhong.query.filter(zuzhang_id=current_user.yonghuid).order_by('-create_time') + base_queryset = Gsfenhong.query.filter(zuzhang_id=current_user.UserUID).order_by('-create_time') fenhong_cishu = base_queryset.count() # 4. 分页查询 @@ -5947,22 +5947,22 @@ class ZuzhangFenhongView(APIView): # 查询用户主表,并预加载 boss_profile users = User.query.filter(UserUID__in=guanshi_ids).select_related( - 'boss_profile' + 'BossProfile' ).only( 'UserUID', 'Avatar', - 'boss_profile__nickname' + 'BossProfile__nickname' ) info = {} for user in users: - avatar = user.avatar or '' + avatar = user.Avatar or '' # 从 boss_profile 获取昵称 - if hasattr(user, 'boss_profile') and user.boss_profile: - nicheng = user.boss_profile.nickname or '' + if hasattr(user, 'BossProfile') and user.BossProfile: + nicheng = user.BossProfile.nickname or '' else: # 如果没有 boss_profile,使用默认昵称 - nicheng = f"用户{user.yonghuid[-4:]}" - info[user.yonghuid] = {'avatar': avatar, 'nicheng': nicheng} + nicheng = f"用户{user.UserUID[-4:]}" + info[user.UserUID] = {'avatar': avatar, 'nicheng': nicheng} # 对于未查询到的用户,提供默认值 for gid in guanshi_ids: @@ -6009,7 +6009,7 @@ class CzhqdyView(APIView): { 'id': 1, 'name': '打手佣金抵扣', - 'profile_attr': 'dashou_profile', # User 的反向 related_name + 'profile_attr': 'DashouProfile', # User 的反向 related_name 'balance_field': 'yue', 'rate_key': '5', 'model': UserDashou, @@ -6017,7 +6017,7 @@ class CzhqdyView(APIView): { 'id': 2, 'name': '管事分红抵扣', - 'profile_attr': 'guanshi_profile', + 'profile_attr': 'GuanshiProfile', 'balance_field': 'yue', 'rate_key': '6', 'model': UserGuanshi, @@ -6025,7 +6025,7 @@ class CzhqdyView(APIView): { 'id': 3, 'name': '组长分红抵扣', - 'profile_attr': 'zuzhang_profile', + 'profile_attr': 'ZuzhangProfile', 'balance_field': 'ketixian_jine', 'rate_key': '8', 'model': UserZuzhang, @@ -6034,7 +6034,7 @@ class CzhqdyView(APIView): { 'id': 4, 'name': '打手押金抵扣', - 'profile_attr': 'dashou_profile', # 与打手佣金共用扩展表 + 'profile_attr': 'DashouProfile', # 与打手佣金共用扩展表 'balance_field': 'yajin', # 押金字段 'rate_key': '11', # 押金提现费率对应的 Platform 'model': UserDashou, @@ -6089,7 +6089,7 @@ class CzhqdyView(APIView): elif leixing == 4: # 罚款缴纳 try: - fadan = Penalty.query.get(id=fadan_id, PenalizedUserID=user.yonghuid) + fadan = Penalty.query.get(id=fadan_id, PenalizedUserID=user.UserUID) target_price = fadan.FineAmount except Penalty.DoesNotExist: return Response({'code': 404, 'msg': '罚单不存在或不属于当前用户'}, status=status.HTTP_404_NOT_FOUND) @@ -6228,7 +6228,7 @@ class DsqrgmdhView(APIView): if not fadan_id: return Response({'code': 400, 'msg': '缺少罚单ID'}, status=status.HTTP_400_BAD_REQUEST) try: - fadan_obj = Penalty.query.get(id=fadan_id, PenalizedUserID=request.user.yonghuid) + fadan_obj = Penalty.query.get(id=fadan_id, PenalizedUserID=request.user.UserUID) target_price = fadan_obj.FineAmount except Penalty.DoesNotExist: return Response({'code': 404, 'msg': '罚单不存在或不属于当前用户'}, status=status.HTTP_404_NOT_FOUND) @@ -6253,7 +6253,7 @@ class DsqrgmdhView(APIView): return Response({'code': 500, 'msg': f'金额计算错误: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) user = request.user - yonghuid = user.yonghuid + yonghuid = user.UserUID try: profile = identity['model'].query.get(user=user) diff --git a/rank/utils.py b/rank/utils.py index b43a30c..27732db 100644 --- a/rank/utils.py +++ b/rank/utils.py @@ -160,7 +160,7 @@ def create_shenhe_jilu(user, tag_id, game_nick, remark, reapply, jilu_id=None, s # 3. 检查是否有进行中的审核记录(待审核、审核中) has_pending = ShenheJilu.objects.filter( - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, chenghao=chenghao, zhuangtai__in=[0, 3] ).exists() @@ -175,7 +175,7 @@ def create_shenhe_jilu(user, tag_id, game_nick, remark, reapply, jilu_id=None, s # 查询原记录(未通过状态) last_record = ShenheJilu.objects.filter( jilu_id=jilu_id, - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, zhuangtai=2 ).first() if not last_record: @@ -215,7 +215,7 @@ def create_shenhe_jilu(user, tag_id, game_nick, remark, reapply, jilu_id=None, s else: # 首次申请 max_cishu = ShenheJilu.objects.filter( - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, chenghao=chenghao, zhuangtai__in=[1, 2] ).aggregate(max=Max('cishu'))['max'] or 0 @@ -229,7 +229,7 @@ def create_shenhe_jilu(user, tag_id, game_nick, remark, reapply, jilu_id=None, s initial_zhuangtai = 0 if shenheguan_id else 3 # 指定→审核中,否则待审核 record = ShenheJilu.objects.create( jilu_id=new_jilu_id, - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, zhuangtai=initial_zhuangtai, bankuai=bankuai, chenghao=chenghao, @@ -281,23 +281,23 @@ def create_shenhe_jilu_from_temp(user, temp_data): """ try: - logger.info(f"开始从临时表创建审核记录: 用户={user.yonghuid}, 订单={temp_data.dingdan_id}") + logger.info(f"开始从临时表创建审核记录: 用户={user.UserUID}, 订单={temp_data.dingdan_id}") chenghao = Chenghao.objects.get(id=temp_data.tag_id) logger.info(f"获取标签成功: {chenghao.mingcheng}") # 检查是否已拥有标签 if YonghuChenghao.objects.filter(yonghu=user, chenghao=chenghao).exists(): - logger.warning(f"用户 {user.yonghuid} 已拥有标签 {chenghao.mingcheng}") + logger.warning(f"用户 {user.UserUID} 已拥有标签 {chenghao.mingcheng}") return False, '您已拥有该标签,无需重复考核', None # 检查是否有进行中的审核 if ShenheJilu.objects.filter( - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, chenghao=chenghao, zhuangtai__in=[0, 3] ).exists(): - logger.warning(f"用户 {user.yonghuid} 有未完成的考核申请") + logger.warning(f"用户 {user.UserUID} 有未完成的考核申请") return False, '您有未完成的考核申请,请等待结果', None # 使用临时表中预计算好的次数和费用 @@ -316,7 +316,7 @@ def create_shenhe_jilu_from_temp(user, temp_data): return False, '再次申请必须提供原记录ID', None last = ShenheJilu.objects.filter( jilu_id=jilu_id, - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, zhuangtai=2 ).first() if not last: @@ -364,7 +364,7 @@ def create_shenhe_jilu_from_temp(user, temp_data): record = ShenheJilu.objects.create( jilu_id=new_jilu_id, - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, zhuangtai=initial_zhuangtai, bankuai=bankuai, chenghao=chenghao, diff --git a/rank/views.py b/rank/views.py index 73a312b..90802d9 100644 --- a/rank/views.py +++ b/rank/views.py @@ -139,7 +139,7 @@ class KaoheguanRegisterView(APIView): return Response({'code': 500, 'msg': '服务器内部错误'}) def _build_data(self, kg): - bankuai_qs = KaoheguanBankuai.objects.filter(kaoheguan_id=kg.user.yonghuid).select_related('bankuai') + bankuai_qs = KaoheguanBankuai.objects.filter(kaoheguan_id=kg.user.UserUID).select_related('bankuai') bankuai_list = [{'bankuai_id': b.bankuai.bankuai_id, 'mingcheng': b.bankuai.mingcheng} for b in bankuai_qs] return { 'zhuangtai': kg.zhuangtai, @@ -174,12 +174,12 @@ class KaoheguanRefreshView(APIView): return Response({'code': 200, 'data': {'zhuangtai': kg.zhuangtai, 'kaoheguanstatus': 0}}) # 查询所属板块 - bankuai_qs = KaoheguanBankuai.objects.filter(kaoheguan_id=kg.user.yonghuid).select_related('bankuai') + bankuai_qs = KaoheguanBankuai.objects.filter(kaoheguan_id=kg.user.UserUID).select_related('bankuai') bankuai_list = [{'bankuai_id': b.bankuai.bankuai_id, 'mingcheng': b.bankuai.mingcheng} for b in bankuai_qs] # 统计待审核 / 审核中的记录数量 pending_count = ShenheJilu.objects.filter( - shenheguan_id=yonghu.yonghuid, + shenheguan_id=yonghu.UserUID, zhuangtai=0 # 审核中 ).count() @@ -210,7 +210,7 @@ class ShenheDatingModuleView(APIView): def post(self, request): try: yonghu = request.user - related_ids = set(KaoheguanBankuai.objects.filter(kaoheguan_id=yonghu.yonghuid) + related_ids = set(KaoheguanBankuai.objects.filter(kaoheguan_id=yonghu.UserUID) .values_list('bankuai_id', flat=True)) bankuai_list = [] @@ -250,7 +250,7 @@ class ShenheDatingDataView(APIView): records = [] for jl in jilu_list: user = User.objects.filter(UserUID=jl.shenqingren_id).first() - avatar = user.avatar if user else '' + avatar = user.Avatar if user else '' texiao_json = {} if jl.chenghao and jl.chenghao.texiao_miaoshu: try: @@ -304,7 +304,7 @@ class ShenheQiangdanView(APIView): # 2. 检查是否有正在审核中的记录(zhuangtai=0),防止同时审核多个 ongoing = ShenheJilu.objects.filter( - shenheguan_id=yonghu.yonghuid, zhuangtai=0 + shenheguan_id=yonghu.UserUID, zhuangtai=0 ).exists() if ongoing: return Response({'code': 400, 'msg': '您有正在审核中的任务,请完成后再接新任务'}) @@ -320,12 +320,12 @@ class ShenheQiangdanView(APIView): return Response({'code': 400, 'msg': '该审核任务已被接走、状态变更或不存在'}) # 4. 不能审核自己 - if jilu.shenqingren_id == yonghu.yonghuid: + if jilu.shenqingren_id == yonghu.UserUID: return Response({'code': 400, 'msg': '不能审核自己提交的申请'}) # 5. 板块权限:考核官必须属于该板块 is_related = KaoheguanBankuai.objects.filter( - kaoheguan_id=yonghu.yonghuid, + kaoheguan_id=yonghu.UserUID, bankuai=jilu.bankuai ).exists() if not is_related: @@ -338,7 +338,7 @@ class ShenheQiangdanView(APIView): # 7. 更新审核记录:分配给当前考核官,状态变为审核中 jilu.zhuangtai = 0 # 审核中 - jilu.shenheguan_id = yonghu.yonghuid + jilu.shenheguan_id = yonghu.UserUID jilu.save() # 8. 更新考核官扩展表:审核总数 +1,审核状态置为审核中(1) @@ -379,7 +379,7 @@ class ShenheXiangqingView(APIView): # 查询当前考核官名下状态为“审核中”的记录 base_query = ShenheJilu.objects.filter( - shenheguan_id=yonghu.yonghuid, + shenheguan_id=yonghu.UserUID, zhuangtai=0 ).select_related('bankuai', 'chenghao') total = base_query.count() @@ -389,7 +389,7 @@ class ShenheXiangqingView(APIView): records = [] for jl in jilu_list: user = User.objects.filter(UserUID=jl.shenqingren_id).first() - avatar = user.avatar if user else '' + avatar = user.Avatar if user else '' texiao_json = {} if jl.chenghao and jl.chenghao.texiao_miaoshu: try: @@ -469,7 +469,7 @@ class ShenheCaozuoView(APIView): # 2. 锁定审核记录,确保属于当前考核官且状态为审核中(0) jilu = ShenheJilu.objects.select_for_update().filter( jilu_id=jilu_id, - shenheguan_id=yonghu.yonghuid, + shenheguan_id=yonghu.UserUID, zhuangtai=0 ).first() if not jilu: @@ -526,7 +526,7 @@ class ShenheCaozuoView(APIView): KaoheJujueJilu.objects.create( jilu=jilu, cishu=jilu.cishu, - shenheguan_id=yonghu.yonghuid, + shenheguan_id=yonghu.UserUID, yuanyin=yuanyin ) @@ -634,8 +634,8 @@ class KaoheJinpaiTagsView(APIView): # 新增:返回所有在职考核官信息(用于指定考核官) kaoheguan_list = [] for kg in UserShenheguan.objects.filter(zhuangtai=1).select_related('user'): - uid = kg.user.yonghuid - avatar = kg.user.avatar or '' + uid = kg.user.UserUID + avatar = kg.user.Avatar or '' # 获取考核官的打手昵称(从打手扩展表) dashou_nick = '' dashou_prof = UserDashou.objects.filter(user=kg.user).first() @@ -675,12 +675,12 @@ class KaoheJiluView(APIView): if status == 'pending': # 进行中/未通过:审核中(0)、待审核(3)、未通过(2) query = ShenheJilu.objects.filter( - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, zhuangtai__in=[0, 2, 3] ) else: query = ShenheJilu.objects.filter( - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, zhuangtai=1 # 已通过 ) @@ -700,7 +700,7 @@ class KaoheJiluView(APIView): # 考核官头像 kg_user = User.objects.filter(UserUID=jl.shenheguan_id).first() - kg_avatar = kg_user.avatar if kg_user else '' + kg_avatar = kg_user.Avatar if kg_user else '' data.append({ 'jilu_id': jl.jilu_id, @@ -762,14 +762,14 @@ class KaoheFreeApplyView(APIView): # 计算本次次数 if reapply and jilu_id: last_record = ShenheJilu.objects.filter( - jilu_id=jilu_id, shenqingren_id=user.yonghuid, zhuangtai=2 + jilu_id=jilu_id, shenqingren_id=user.UserUID, zhuangtai=2 ).first() if not last_record: return Response({'code': 400, 'msg': '原记录不存在或状态不是未通过'}) new_cishu = last_record.cishu + 1 else: max_cishu = ShenheJilu.objects.filter( - shenqingren_id=user.yonghuid, chenghao=chenghao, zhuangtai__in=[1,2] + shenqingren_id=user.UserUID, chenghao=chenghao, zhuangtai__in=[1,2] ).aggregate(max=models.Max('cishu'))['max'] or 0 new_cishu = max_cishu + 1 @@ -830,7 +830,7 @@ class KaoheJiluListView(APIView): page_size = int(request.data.get('page_size', 5)) # 3. 构建查询条件:只查当前考核官审核的记录 - base_query = ShenheJilu.objects.filter(shenheguan_id=yonghu.yonghuid) + base_query = ShenheJilu.objects.filter(shenheguan_id=yonghu.UserUID) # 板块筛选 if bankuai_id and int(bankuai_id) > 0: @@ -882,18 +882,18 @@ class KaoheJiluListView(APIView): applicant_ids = {j.shenqingren_id for j in jilu_list} user_map = {} if applicant_ids: - users = User.objects.filter(UserUID__in=applicant_ids).select_related('dashou_profile') + users = User.objects.filter(UserUID__in=applicant_ids).select_related('DashouProfile') for u in users: - avatar = u.avatar or '' + avatar = u.Avatar or '' nicheng = '' try: # 打手扩展表可能有 nicheng - nicheng = u.dashou_profile.nicheng if u.dashou_profile else '' + nicheng = u.DashouProfile.nicheng if getattr(u, 'DashouProfile', None) else '' except UserDashou.DoesNotExist: pass - user_map[u.yonghuid] = { + user_map[u.UserUID] = { 'avatar': avatar, - 'nicheng': nicheng or f'用户{u.yonghuid}' + 'nicheng': nicheng or f'用户{u.UserUID}' } # 7. 组装数据 diff --git a/refactor_replace.py b/refactor_replace.py new file mode 100644 index 0000000..d482e46 --- /dev/null +++ b/refactor_replace.py @@ -0,0 +1,122 @@ +import os +import re + +ROOT_DIR = r"d:\ALONGF\Django" +EXCLUDE_DIRS = {"migrations", "__pycache__", ".git", "node_modules", ".venv", "idea"} +EXCLUDE_FILES = {"migrate_to_gvsdsdk_user.py", "rp.py", "refactor_replace.py"} + +SAFE_REPLACEMENTS = { + "shoujihao_renzheng": "PhoneVerified", + "last_login_time": "UserLastLoginDate", + "openid": "OpenID", + "user_type": "UserType", + "dashou_profile": "DashouProfile", + "shop_profile": "ShopProfile", + "guanshi_profile": "GuanshiProfile", + "boss_profile": "BossProfile", + "admin_profile": "AdminProfile", + "kefu_profile": "KefuProfile", + "zuzhang_profile": "ZuzhangProfile", + "shenheguan_profile": "ShenheguanProfile", + "has_role": "HasRole", +} + +UNSAFE_REPLACEMENTS = { + "yonghuid": "UserUID", + "avatar": "Avatar", + "phone": "Phone", + "ip": "IP", + "create_time": "UserCreateTime", + "zhifu": "PaymentQRCode", + "skzhanghao": "PaymentAccount", +} + +NON_USER_VARS = { + "record", "tixian_record", "tixian", "audit", + "item", "items", + "shangjia", "product", "guanshi", "zuzhang", + "bk", "ch", "sg", "p", "m", + "huiyuan", "huiyuan_obj", "shangbin_obj", "shangpin_obj", + "moban", "link", "dingdan_obj", + "jl", "rej", "dianpu", "shop", + "r", "rec", "chufa", "auto_record", + "um", "hg", "xjl", "Dingdan", + "obj", "tixian_obj", + "sensitive_data", "self", + "boss_profile", "dashou_profile", "guanshi_profile", "shop_profile", + "kefu_profile", "admin_profile", "zuzhang_profile", "shenheguan_profile", + "BossProfile", "DashouProfile", "GuanshiProfile", "ShopProfile", + "KefuProfile", "AdminProfile", "ZuzhangProfile", "ShenheguanProfile", + "shangjia_profile", + "dingdan", "order", "tixian_shenhe", +} + + +def should_skip_dir(dirpath): + for excl in EXCLUDE_DIRS: + if excl in dirpath: + return True + return False + + +def process_content(content): + # 1. Safe replacements: global .old -> .new + for old, new in SAFE_REPLACEMENTS.items(): + pattern = r"\." + re.escape(old) + r"\b" + content = re.sub(pattern, "." + new, content) + + # 2. Unsafe replacements: targeted, only on User instance variables + for old, new in UNSAFE_REPLACEMENTS.items(): + def make_replacer(new_name): + def replacer(m): + var = m.group(1) + if var in NON_USER_VARS: + return m.group(0) + return var + "." + new_name + return replacer + pattern = r"(? 1: - feilv = Decimal('1.0000') - - # 计算平台收益(费率 * 总收益) - pingtai_shouyi = zong_shouyi * feilv - # 店铺收益 = 总收益 - 平台收益 - dianpu_shouyi = zong_shouyi - pingtai_shouyi - - logger.info( - f"收益计算: 商品ID={shangpin_id}, 订单金额={dingdan_jiage}, " - f"打手分成={dashou_fencheng}, 总收益={zong_shouyi}, 费率={feilv}, " - f"平台收益={pingtai_shouyi}, 店铺收益={dianpu_shouyi}" - ) - - return dianpu_id, pingtai_shouyi, dianpu_shouyi''' - - - - - - - -def update_dianpu_daily_stat( - dianpu_id, - caozuo_leixing, - dingdan_jiage=Decimal('0.00'), - dianpu_shouyi=Decimal('0.00'), - shangji_dianpu_id=None, # 新增:上级店铺ID - shangji_fenhong=Decimal('0.00') # 新增:该笔订单给上级的分红 -): - """ - 原子更新店铺每日统计数据,结算时同步更新店铺余额,并安全处理上级分红统计。 - - 参数: - dianpu_id: 本店铺ID - caozuo_leixing: 1=下单, 2=结算, 3=退款 - dingdan_jiage: 订单金额 - dianpu_shouyi: 店铺自身收益 - shangji_dianpu_id: 上级店铺ID(可选) - shangji_fenhong: 给上级的分红金额(可选) - - 注意: - - 金额自动归一化,负数取0 - - 使用 select_for_update 行锁保证并发安全 - - 结算时,本店余额和统计先落定,上级分红统计放在 try 块内,失败不影响本店 - - 新增字段均包含在统计表中:给上级次数/总额/上级ID,以及作为上级获得次数/总额 - """ - # 金额安全处理 - dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00') - dianpu_shouyi = Decimal(str(dianpu_shouyi)) if dianpu_shouyi else Decimal('0.00') - shangji_fenhong = Decimal(str(shangji_fenhong)) if shangji_fenhong else Decimal('0.00') - if dingdan_jiage < 0: dingdan_jiage = Decimal('0.00') - if dianpu_shouyi < 0: dianpu_shouyi = Decimal('0.00') - if shangji_fenhong < 0: shangji_fenhong = Decimal('0.00') - - today = date.today() - - # 轻量校验店铺存在性 - if not Dianpu.objects.filter(id=dianpu_id).exists(): - logger.error(f"店铺ID {dianpu_id} 不存在,统计更新失败") - return - - # 开启事务,全部使用行锁 - with transaction.atomic(): - # 1. 获取或创建本店今日统计记录并锁定 - tongji, created = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create( - dianpu_id=dianpu_id, - riqi=today, - defaults={ - 'nian': today.year, - 'yue': today.month, - 'ri': today.day, - 'xiadan_zongliang': 0, - 'xiadan_zonge': Decimal('0.00'), - 'chengjiao_zongliang': 0, - 'chengjiao_zonge': Decimal('0.00'), - 'tuikuan_zongliang': 0, - 'tuikuan_zonge': Decimal('0.00'), - 'shouyi_zonge': Decimal('0.00'), - # 新增字段默认值 - 'shangji_fenhong_cishu': 0, - 'shangji_fenhong_zonge': Decimal('0.00'), - 'gei_shangji_cishu': 0, - 'gei_shangji_zonge': Decimal('0.00'), - 'shangji_id': None, - } - ) - - # 构建更新字段字典 - update_fields_dict = {} - - if caozuo_leixing == 1: # 下单 - update_fields_dict['xiadan_zongliang'] = F('xiadan_zongliang') + 1 - update_fields_dict['xiadan_zonge'] = F('xiadan_zonge') + dingdan_jiage - - elif caozuo_leixing == 2: # 结算 - # --- 本店成交统计 --- - update_fields_dict['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1 - update_fields_dict['chengjiao_zonge'] = F('chengjiao_zonge') + dingdan_jiage - update_fields_dict['shouyi_zonge'] = F('shouyi_zonge') + dianpu_shouyi - - # --- 更新本店主表余额(加锁) --- - try: - dianpu_locked = Dianpu.objects.select_for_update().get(id=dianpu_id) - dianpu_locked.ketixian_yue = F('ketixian_yue') + dianpu_shouyi - dianpu_locked.zhengqu_zonge = F('zhengqu_zonge') + dianpu_shouyi - dianpu_locked.save(update_fields=['ketixian_yue', 'zhengqu_zonge', 'update_time']) - except Dianpu.DoesNotExist: - logger.error(f"结算时店铺 {dianpu_id} 不存在") - raise - - # --- 处理给上级的分红(本店作为下级)--- - if shangji_dianpu_id and shangji_fenhong > 0: - # 更新本店统计的“给上级”字段 - update_fields_dict['gei_shangji_cishu'] = F('gei_shangji_cishu') + 1 - update_fields_dict['gei_shangji_zonge'] = F('gei_shangji_zonge') + shangji_fenhong - - # 设置当天上级店铺ID(仅当天第一次结算时写入,后续保持不变) - current_shangji_id = tongji.shangji_id - if current_shangji_id is None: - update_fields_dict['shangji_id'] = shangji_dianpu_id - elif current_shangji_id != shangji_dianpu_id: - logger.warning( - f"店铺 {dianpu_id} 当天上级ID不一致,已有 {current_shangji_id}," - f"新上级 {shangji_dianpu_id},保持原上级ID" - ) - - # --- 更新上级店铺的“作为上级获得”统计(完全隔离保护) --- - try: - _update_shangji_gain_stat_within_tx(shangji_dianpu_id, shangji_fenhong) - except Exception as e: - logger.error( - f"更新上级店铺 {shangji_dianpu_id} 统计失败,不影响本店: {e}" - ) - - elif caozuo_leixing == 3: # 退款 - update_fields_dict['tuikuan_zongliang'] = F('tuikuan_zongliang') + 1 - update_fields_dict['tuikuan_zonge'] = F('tuikuan_zonge') + dingdan_jiage - else: - logger.warning(f"未知操作类型: {caozuo_leixing}") - return - - # 原子更新本店统计字段 - if update_fields_dict: - for field, value in update_fields_dict.items(): - setattr(tongji, field, value) - tongji.save(update_fields=list(update_fields_dict.keys())) - - logger.info( - f"店铺统计更新完成: dianpu={dianpu_id}, 操作={caozuo_leixing}, " - f"金额={dingdan_jiage}, 店铺收益={dianpu_shouyi}, " - f"上级ID={shangji_dianpu_id}, 上级分红={shangji_fenhong}" - ) - - -def _update_shangji_gain_stat_within_tx(shangji_dianpu_id, fenhong_jine): - """ - 在事务内更新上级店铺的“作为上级获得”统计及主表余额。 - 注意:本函数必须由处于同一事务中的 update_dianpu_daily_stat 调用, - 并且调用方已获取必要的行锁,这里再次加锁不会死锁(按相同顺序加锁)。 - """ - today = date.today() - - # 获取或创建上级店铺的今日统计记录并加锁 - tongji_shangji, _ = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create( - dianpu_id=shangji_dianpu_id, - riqi=today, - defaults={ - 'nian': today.year, - 'yue': today.month, - 'ri': today.day, - 'xiadan_zongliang': 0, - 'xiadan_zonge': Decimal('0.00'), - 'chengjiao_zongliang': 0, - 'chengjiao_zonge': Decimal('0.00'), - 'tuikuan_zongliang': 0, - 'tuikuan_zonge': Decimal('0.00'), - 'shouyi_zonge': Decimal('0.00'), - 'shangji_fenhong_cishu': 0, - 'shangji_fenhong_zonge': Decimal('0.00'), - 'gei_shangji_cishu': 0, - 'gei_shangji_zonge': Decimal('0.00'), - 'shangji_id': None, - } - ) - - # 更新上级店铺的“作为上级获得”字段 - tongji_shangji.shangji_fenhong_cishu = F('shangji_fenhong_cishu') + 1 - tongji_shangji.shangji_fenhong_zonge = F('shangji_fenhong_zonge') + fenhong_jine - tongji_shangji.save(update_fields=['shangji_fenhong_cishu', 'shangji_fenhong_zonge', 'update_time']) - - # 更新上级店铺主表的分红余额和累计分红 - shangji = Dianpu.objects.select_for_update().get(id=shangji_dianpu_id) - shangji.shangji_fenhong_leiji = F('shangji_fenhong_leiji') + fenhong_jine - shangji.shangji_ketixian_fenhong = F('shangji_ketixian_fenhong') + fenhong_jine - shangji.save(update_fields=['shangji_fenhong_leiji', 'shangji_ketixian_fenhong', 'update_time']) - - logger.info(f"上级店铺 {shangji_dianpu_id} 获得分红 +{fenhong_jine}") - - - -def calculate_pingtai_and_dianpu_shouyi(shangpin_id, dingdan_jiage, dashou_fencheng): - """ - 计算平台收益、店铺收益、上级分红 - - 分配链路(上级分红完全隔离,不影响平台及店铺核心计算): - 总收益 = 订单金额 - 打手分成 - 平台抽成 = 总收益 * (店铺开启则用店铺费率,否则用平台默认费率) - 剩余收益 = 总收益 - 平台抽成 - 上级分红 = 剩余收益 * 本店设置的上级分红费率(若有上级且满足条件) - 店铺收益 = 剩余收益 - 上级分红 - - 参数: - shangpin_id: 商品ID - dingdan_jiage: 订单金额 - dashou_fencheng: 打手分成金额 - - 返回: - 五元组 (dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong) - - dianpu_id: 店铺ID,可能为None - - pingtai_shouyi: 平台收益 - - dianpu_shouyi: 店铺最终收益 - - shangji_dianpu_id: 上级店铺ID,可能为None - - shangji_fenhong: 上级分红金额,默认0.00 - """ - # ==================== 金额安全处理 ==================== - dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00') - dashou_fencheng = Decimal(str(dashou_fencheng)) if dashou_fencheng else Decimal('0.00') - if dingdan_jiage < 0: - dingdan_jiage = Decimal('0.00') - if dashou_fencheng < 0: - dashou_fencheng = Decimal('0.00') - - zong_shouyi = dingdan_jiage - dashou_fencheng - if zong_shouyi < 0: - zong_shouyi = Decimal('0.00') - - # ==================== 获取商品及店铺 ==================== - try: - shangpin = Shangpin.objects.select_related('dianpu').only('id', 'dianpu').get(id=shangpin_id) - except Shangpin.DoesNotExist: - logger.error(f"收益计算失败:商品ID {shangpin_id} 不存在") - return None, zong_shouyi, Decimal('0.00'), None, Decimal('0.00') - - dianpu_id = shangpin.dianpu_id - if dianpu_id is None: - logger.info(f"商品 {shangpin_id} 未关联店铺,平台收益={zong_shouyi}") - return None, zong_shouyi, Decimal('0.00'), None, Decimal('0.00') - - # ==================== 获取店铺配置 ==================== - try: - dianpu = Dianpu.objects.only( - 'id', 'kaiqi_shouyi_choucheng', 'shouyi_choucheng_feilv', - 'yaoqing_dianpu_id', 'fenhong_choucheng_feilv' - ).get(id=dianpu_id) - except Dianpu.DoesNotExist: - logger.error(f"收益计算失败:店铺ID {dianpu_id} 不存在") - return dianpu_id, zong_shouyi, Decimal('0.00'), None, Decimal('0.00') - - # ==================== 平台抽成 ==================== - if dianpu.kaiqi_shouyi_choucheng and dianpu.shouyi_choucheng_feilv is not None: - feilv = dianpu.shouyi_choucheng_feilv - else: - moren_peizhi, created = DianpuMorenPeizhi.objects.get_or_create( - id=1, - defaults={ - 'moren_choucheng_feilv': Decimal('0.1000'), - 'moren_tixian_xiane': Decimal('1000.00') - } - ) - feilv = moren_peizhi.moren_choucheng_feilv - if feilv is None or feilv < 0: - feilv = Decimal('0.0000') - if feilv > 1: - feilv = Decimal('1.0000') - - pingtai_shouyi = zong_shouyi * feilv - shengyu = zong_shouyi - pingtai_shouyi - - # ==================== 上级分红(完全隔离,不影响店铺收益) ==================== - shangji_dianpu_id = dianpu.yaoqing_dianpu_id - shangji_fenhong = Decimal('0.00') - - if shangji_dianpu_id and dianpu.fenhong_choucheng_feilv: - try: - # 检查上级店铺状态 - shangji = Dianpu.objects.filter(id=shangji_dianpu_id).only('id', 'zhuangtai').first() - if shangji and shangji.zhuangtai == 1: - fz_rate = dianpu.fenhong_choucheng_feilv - if fz_rate and fz_rate > 0: - shangji_fenhong = shengyu * fz_rate - if shangji_fenhong < 0: - shangji_fenhong = Decimal('0.00') - if shangji_fenhong > shengyu: - shangji_fenhong = shengyu - shengyu -= shangji_fenhong - else: - if shangji: - logger.info(f"上级店铺 {shangji_dianpu_id} 状态非正常,不参与分红") - else: - logger.warning(f"上级店铺ID {shangji_dianpu_id} 不存在,不参与分红") - except Exception as e: - logger.exception(f"上级分红计算异常,忽略: {e}") - shangji_fenhong = Decimal('0.00') - # 注意:shengyu 未被修改,店铺收益不变 - - dianpu_shouyi = shengyu - - logger.info( - f"收益计算完成: 商品={shangpin_id}, 总收益={zong_shouyi}, 平台={pingtai_shouyi}, " - f"上级ID={shangji_dianpu_id}, 上级分红={shangji_fenhong}, 店铺={dianpu_shouyi}" - ) - - return dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong - - - - - - - - - - -def validate_shangpin_and_dianpu(shangpin_id): - """ - 校验商品及关联店铺(区分有无店铺) - - 无店铺:仅校验商品是否上架 - 有店铺:校验商品审核、封禁、上架,以及店铺是否封禁 - - 返回: - (True, None, shangpin_obj) # 通过 - (False, "错误信息", None) # 失败 - """ - try: - shangpin = Shangpin.objects.select_related('dianpu').get(id=shangpin_id) - except Shangpin.DoesNotExist: - return False, "商品不存在", None - - # 无店铺:只检查上架 - if not shangpin.dianpu_id: - if not shangpin.shangjia_zhuangtai: - return False, "商品已下架", None - return True, None, shangpin - - # 有店铺:完整校验 - if not shangpin.shenhe_zhuangtai: - return False, "商品未通过平台审核", None - if shangpin.fengjin_zhuangtai: - return False, "商品已被封禁", None - if not shangpin.shangjia_zhuangtai: - return False, "商品已下架", None - - try: - if shangpin.dianpu.zhuangtai != 1: - return False, "店铺已被封禁", None - except Exception: - return False, "关联店铺异常", None - + +from rest_framework.response import Response + +from .models import Dianpu, YonghuPingzheng + + +from products.models import Shangpin +from .models import Dianpu, DianpuMorenPeizhi + +# shangdian/utils.py 或 tongji/utils.py + +import logging +from datetime import date +from decimal import Decimal +from django.db import transaction +from django.db.models import F +from .models import Dianpu, DianpuShouzhiMeiriTongji + + +logger = logging.getLogger(__name__) + + +def verify_shop_permission(request, frontend_user_id=None): + """ + 验证商家后台用户身份及店铺状态 + + 参数: + request: DRF request 对象 + frontend_user_id: 前端传递的用户ID(用于防越权校验,可选) + + 返回: + (dianpu_obj, None) # 验证成功,返回店铺对象 + (None, Response) # 验证失败,返回错误响应 + """ + current_user = request.user + + # 1. 从 request.user 获取当前认证用户的 yonghuid + # 注意:您的用户模型可能通过 JWT 的 sub 或其他字段标识,此处假设 request.user.UserUID 存在 + if not hasattr(current_user, 'UserUID'): + logger.error(f"用户对象缺少 yonghuid 属性: {current_user}") + return None, Response( + {'code': 500, 'msg': '用户身份信息异常'} + ) + + auth_user_id = current_user.UserUID + + # 2. 越权检测:如果前端传了用户ID,必须与当前登录用户一致 + if frontend_user_id is not None and str(frontend_user_id) != str(auth_user_id): + logger.warning( + f"越权尝试: 登录用户 {auth_user_id} 尝试操作 {frontend_user_id} 的数据" + ) + return None, Response( + {'code': 403, 'msg': '无权操作其他用户数据'} + ) + + # 3. 查询用户凭证表,验证账号是否存在且状态正常 + try: + pingzheng = YonghuPingzheng.objects.get(yonghu=auth_user_id) + if not pingzheng.is_active: + return None, Response( + {'code': 403, 'msg': '用户账号已被禁用'} + ) + except YonghuPingzheng.DoesNotExist: + return None, Response( + {'code': 404, 'msg': '用户凭证不存在'} + ) + + # 4. 查询关联的店铺信息,验证店铺状态 + try: + dianpu = Dianpu.objects.get(pingzheng=pingzheng) + if dianpu.zhuangtai != 1: # 1 表示正常 + return None, Response( + {'code': 403, 'msg': '店铺已被封禁'} + ) + except Dianpu.DoesNotExist: + return None, Response( + {'code': 404, 'msg': '店铺信息不存在'} + ) + + # 验证通过,返回店铺对象 + return dianpu, None + + + + + + + + + + + + + +'''def update_dianpu_daily_stat( + dianpu_id, + caozuo_leixing, + dingdan_jiage=Decimal('0.00'), + dianpu_shouyi=Decimal('0.00') +): + """ + 原子更新店铺每日统计数据,并根据结算操作更新店铺余额与总额(高并发安全) + + 参数: + dianpu_id: 店铺ID (int) + caozuo_leixing: 操作类型 + 1 = 下单 + 2 = 结算(成交) + 3 = 退款 + dingdan_jiage: 订单金额(商品价格),默认0.00 + dianpu_shouyi: 店铺本次收益金额,默认0.00 + + 注意: + - 金额若小于0则自动设为0 + - 日期自动取当天 + - 使用 select_for_update 配合事务,保证并发时数据绝对准确 + - 若店铺不存在,仅记录日志不中断主流程 + - 结算操作会同步更新 Dianpu 表的 ketixian_yue 和 zhengqu_zonge + """ + # 参数归一化与合法性处理 + dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00') + dianpu_shouyi = Decimal(str(dianpu_shouyi)) if dianpu_shouyi else Decimal('0.00') + + if dingdan_jiage < 0: + dingdan_jiage = Decimal('0.00') + if dianpu_shouyi < 0: + dianpu_shouyi = Decimal('0.00') + + today = date.today() + + # 校验店铺是否存在(不加锁,仅校验) + if not Dianpu.objects.filter(id=dianpu_id).exists(): + logger.error(f"店铺ID {dianpu_id} 不存在,统计更新失败") + return + + # 开启事务,使用行锁确保并发安全 + with transaction.atomic(): + # 1. 获取或创建统计记录,并锁定该行 + tongji, created = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create( + dianpu_id=dianpu_id, + riqi=today, + defaults={ + 'nian': today.year, + 'yue': today.month, + 'ri': today.day, + 'xiadan_zongliang': 0, + 'xiadan_zonge': Decimal('0.00'), + 'chengjiao_zongliang': 0, + 'chengjiao_zonge': Decimal('0.00'), + 'tuikuan_zongliang': 0, + 'tuikuan_zonge': Decimal('0.00'), + 'shouyi_zonge': Decimal('0.00'), + } + ) + + # 根据操作类型构建统计更新字段 + update_fields = {} + + if caozuo_leixing == 1: # 下单 + update_fields['xiadan_zongliang'] = F('xiadan_zongliang') + 1 + update_fields['xiadan_zonge'] = F('xiadan_zonge') + dingdan_jiage + + elif caozuo_leixing == 2: # 结算(成交) + update_fields['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1 + update_fields['chengjiao_zonge'] = F('chengjiao_zonge') + dingdan_jiage + update_fields['shouyi_zonge'] = F('shouyi_zonge') + dianpu_shouyi + + # 结算时同步更新店铺表的可提现余额和赚取总额(需加锁店铺行) + try: + dianpu = Dianpu.objects.select_for_update().get(id=dianpu_id) + dianpu.ketixian_yue = F('ketixian_yue') + dianpu_shouyi + dianpu.zhengqu_zonge = F('zhengqu_zonge') + dianpu_shouyi + dianpu.save(update_fields=['ketixian_yue', 'zhengqu_zonge', 'update_time']) + except Dianpu.DoesNotExist: + logger.error(f"结算时店铺ID {dianpu_id} 不存在,但已校验过,此处不应发生") + raise + + elif caozuo_leixing == 3: # 退款 + update_fields['tuikuan_zongliang'] = F('tuikuan_zongliang') + 1 + update_fields['tuikuan_zonge'] = F('tuikuan_zonge') + dingdan_jiage + # 退款是否扣减收益需根据业务决定,此处只累加退款统计 + + else: + logger.warning(f"未知的操作类型: {caozuo_leixing}") + return + + # 原子更新统计字段 + if update_fields: + for field, value in update_fields.items(): + setattr(tongji, field, value) + tongji.save(update_fields=list(update_fields.keys())) + + logger.info( + f"店铺统计更新: 店铺ID={dianpu_id}, 操作类型={caozuo_leixing}, " + f"订单金额={dingdan_jiage}, 店铺收益={dianpu_shouyi}, 日期={today}" + ) + + + + + +def calculate_pingtai_and_dianpu_shouyi(shangpin_id, dingdan_jiage, dashou_fencheng): + """ + 根据商品价格和打手分成计算平台收益和店铺收益 + + 逻辑: + 总收益 = 订单金额 - 打手分成 + 1. 从商品表获取所属店铺ID + 2. 若商品无店铺ID:平台收益 = 总收益,店铺收益 = 0 + 3. 若有店铺ID: + - 查询店铺配置: + * 若店铺开启抽成,则费率 = 店铺表.shouyi_choucheng_feilv + * 否则从店铺默认配置表(ID=1)获取默认费率,若记录不存在则自动创建 + - 平台收益 = 总收益 * 费率 + - 店铺收益 = 总收益 - 平台收益 + + 参数: + shangpin_id: 商品ID (int) + dingdan_jiage: 订单金额(商品价格)(Decimal 或 数字) + dashou_fencheng: 打手分成金额 (Decimal 或 数字) + + 返回: + (dianpu_id, pingtai_shouyi, dianpu_shouyi) 三元组 + dianpu_id 可能为 None + pingtai_shouyi 必定有值 + dianpu_shouyi 必定有值 + """ + # 类型转换与安全处理 + dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00') + dashou_fencheng = Decimal(str(dashou_fencheng)) if dashou_fencheng else Decimal('0.00') + if dingdan_jiage < 0: + dingdan_jiage = Decimal('0.00') + if dashou_fencheng < 0: + dashou_fencheng = Decimal('0.00') + + # 计算总收益(可用于抽成的基础) + zong_shouyi = dingdan_jiage - dashou_fencheng + if zong_shouyi < 0: + zong_shouyi = Decimal('0.00') + + # 获取商品信息及所属店铺 + try: + shangpin = Shangpin.objects.select_related('dianpu').only('id', 'dianpu').get(id=shangpin_id) + except Shangpin.DoesNotExist: + logger.error(f"计算收益失败:商品ID {shangpin_id} 不存在") + return None, zong_shouyi, Decimal('0.00') # 无商品时平台收益为总收益,店铺0 + + dianpu_id = shangpin.dianpu_id + + # 无店铺ID:平台拿走全部总收益,店铺收益为0 + if dianpu_id is None: + logger.info(f"商品 {shangpin_id} 未关联店铺,平台收益 = 总收益 {zong_shouyi}") + return None, zong_shouyi, Decimal('0.00') + + # 有店铺ID:查询店铺抽成配置 + try: + dianpu = Dianpu.objects.only( + 'id', 'kaiqi_shouyi_choucheng', 'shouyi_choucheng_feilv' + ).get(id=dianpu_id) + except Dianpu.DoesNotExist: + logger.error(f"计算收益失败:店铺ID {dianpu_id} 不存在") + return dianpu_id, zong_shouyi, Decimal('0.00') # 店铺不存在时平台收益也为总收益 + + if dianpu.kaiqi_shouyi_choucheng and dianpu.shouyi_choucheng_feilv is not None: + feilv = dianpu.shouyi_choucheng_feilv + else: + # 从默认配置表获取或创建 + moren_peizhi, created = DianpuMorenPeizhi.objects.get_or_create( + id=1, + defaults={ + 'moren_choucheng_feilv': Decimal('0.1000'), # 默认10% + 'moren_tixian_xiane': Decimal('1000.00') + } + ) + feilv = moren_peizhi.moren_choucheng_feilv + + # 确保费率在合法范围内 + if feilv is None or feilv < 0: + feilv = Decimal('0.0000') + if feilv > 1: + feilv = Decimal('1.0000') + + # 计算平台收益(费率 * 总收益) + pingtai_shouyi = zong_shouyi * feilv + # 店铺收益 = 总收益 - 平台收益 + dianpu_shouyi = zong_shouyi - pingtai_shouyi + + logger.info( + f"收益计算: 商品ID={shangpin_id}, 订单金额={dingdan_jiage}, " + f"打手分成={dashou_fencheng}, 总收益={zong_shouyi}, 费率={feilv}, " + f"平台收益={pingtai_shouyi}, 店铺收益={dianpu_shouyi}" + ) + + return dianpu_id, pingtai_shouyi, dianpu_shouyi''' + + + + + + + +def update_dianpu_daily_stat( + dianpu_id, + caozuo_leixing, + dingdan_jiage=Decimal('0.00'), + dianpu_shouyi=Decimal('0.00'), + shangji_dianpu_id=None, # 新增:上级店铺ID + shangji_fenhong=Decimal('0.00') # 新增:该笔订单给上级的分红 +): + """ + 原子更新店铺每日统计数据,结算时同步更新店铺余额,并安全处理上级分红统计。 + + 参数: + dianpu_id: 本店铺ID + caozuo_leixing: 1=下单, 2=结算, 3=退款 + dingdan_jiage: 订单金额 + dianpu_shouyi: 店铺自身收益 + shangji_dianpu_id: 上级店铺ID(可选) + shangji_fenhong: 给上级的分红金额(可选) + + 注意: + - 金额自动归一化,负数取0 + - 使用 select_for_update 行锁保证并发安全 + - 结算时,本店余额和统计先落定,上级分红统计放在 try 块内,失败不影响本店 + - 新增字段均包含在统计表中:给上级次数/总额/上级ID,以及作为上级获得次数/总额 + """ + # 金额安全处理 + dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00') + dianpu_shouyi = Decimal(str(dianpu_shouyi)) if dianpu_shouyi else Decimal('0.00') + shangji_fenhong = Decimal(str(shangji_fenhong)) if shangji_fenhong else Decimal('0.00') + if dingdan_jiage < 0: dingdan_jiage = Decimal('0.00') + if dianpu_shouyi < 0: dianpu_shouyi = Decimal('0.00') + if shangji_fenhong < 0: shangji_fenhong = Decimal('0.00') + + today = date.today() + + # 轻量校验店铺存在性 + if not Dianpu.objects.filter(id=dianpu_id).exists(): + logger.error(f"店铺ID {dianpu_id} 不存在,统计更新失败") + return + + # 开启事务,全部使用行锁 + with transaction.atomic(): + # 1. 获取或创建本店今日统计记录并锁定 + tongji, created = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create( + dianpu_id=dianpu_id, + riqi=today, + defaults={ + 'nian': today.year, + 'yue': today.month, + 'ri': today.day, + 'xiadan_zongliang': 0, + 'xiadan_zonge': Decimal('0.00'), + 'chengjiao_zongliang': 0, + 'chengjiao_zonge': Decimal('0.00'), + 'tuikuan_zongliang': 0, + 'tuikuan_zonge': Decimal('0.00'), + 'shouyi_zonge': Decimal('0.00'), + # 新增字段默认值 + 'shangji_fenhong_cishu': 0, + 'shangji_fenhong_zonge': Decimal('0.00'), + 'gei_shangji_cishu': 0, + 'gei_shangji_zonge': Decimal('0.00'), + 'shangji_id': None, + } + ) + + # 构建更新字段字典 + update_fields_dict = {} + + if caozuo_leixing == 1: # 下单 + update_fields_dict['xiadan_zongliang'] = F('xiadan_zongliang') + 1 + update_fields_dict['xiadan_zonge'] = F('xiadan_zonge') + dingdan_jiage + + elif caozuo_leixing == 2: # 结算 + # --- 本店成交统计 --- + update_fields_dict['chengjiao_zongliang'] = F('chengjiao_zongliang') + 1 + update_fields_dict['chengjiao_zonge'] = F('chengjiao_zonge') + dingdan_jiage + update_fields_dict['shouyi_zonge'] = F('shouyi_zonge') + dianpu_shouyi + + # --- 更新本店主表余额(加锁) --- + try: + dianpu_locked = Dianpu.objects.select_for_update().get(id=dianpu_id) + dianpu_locked.ketixian_yue = F('ketixian_yue') + dianpu_shouyi + dianpu_locked.zhengqu_zonge = F('zhengqu_zonge') + dianpu_shouyi + dianpu_locked.save(update_fields=['ketixian_yue', 'zhengqu_zonge', 'update_time']) + except Dianpu.DoesNotExist: + logger.error(f"结算时店铺 {dianpu_id} 不存在") + raise + + # --- 处理给上级的分红(本店作为下级)--- + if shangji_dianpu_id and shangji_fenhong > 0: + # 更新本店统计的“给上级”字段 + update_fields_dict['gei_shangji_cishu'] = F('gei_shangji_cishu') + 1 + update_fields_dict['gei_shangji_zonge'] = F('gei_shangji_zonge') + shangji_fenhong + + # 设置当天上级店铺ID(仅当天第一次结算时写入,后续保持不变) + current_shangji_id = tongji.shangji_id + if current_shangji_id is None: + update_fields_dict['shangji_id'] = shangji_dianpu_id + elif current_shangji_id != shangji_dianpu_id: + logger.warning( + f"店铺 {dianpu_id} 当天上级ID不一致,已有 {current_shangji_id}," + f"新上级 {shangji_dianpu_id},保持原上级ID" + ) + + # --- 更新上级店铺的“作为上级获得”统计(完全隔离保护) --- + try: + _update_shangji_gain_stat_within_tx(shangji_dianpu_id, shangji_fenhong) + except Exception as e: + logger.error( + f"更新上级店铺 {shangji_dianpu_id} 统计失败,不影响本店: {e}" + ) + + elif caozuo_leixing == 3: # 退款 + update_fields_dict['tuikuan_zongliang'] = F('tuikuan_zongliang') + 1 + update_fields_dict['tuikuan_zonge'] = F('tuikuan_zonge') + dingdan_jiage + else: + logger.warning(f"未知操作类型: {caozuo_leixing}") + return + + # 原子更新本店统计字段 + if update_fields_dict: + for field, value in update_fields_dict.items(): + setattr(tongji, field, value) + tongji.save(update_fields=list(update_fields_dict.keys())) + + logger.info( + f"店铺统计更新完成: dianpu={dianpu_id}, 操作={caozuo_leixing}, " + f"金额={dingdan_jiage}, 店铺收益={dianpu_shouyi}, " + f"上级ID={shangji_dianpu_id}, 上级分红={shangji_fenhong}" + ) + + +def _update_shangji_gain_stat_within_tx(shangji_dianpu_id, fenhong_jine): + """ + 在事务内更新上级店铺的“作为上级获得”统计及主表余额。 + 注意:本函数必须由处于同一事务中的 update_dianpu_daily_stat 调用, + 并且调用方已获取必要的行锁,这里再次加锁不会死锁(按相同顺序加锁)。 + """ + today = date.today() + + # 获取或创建上级店铺的今日统计记录并加锁 + tongji_shangji, _ = DianpuShouzhiMeiriTongji.objects.select_for_update().get_or_create( + dianpu_id=shangji_dianpu_id, + riqi=today, + defaults={ + 'nian': today.year, + 'yue': today.month, + 'ri': today.day, + 'xiadan_zongliang': 0, + 'xiadan_zonge': Decimal('0.00'), + 'chengjiao_zongliang': 0, + 'chengjiao_zonge': Decimal('0.00'), + 'tuikuan_zongliang': 0, + 'tuikuan_zonge': Decimal('0.00'), + 'shouyi_zonge': Decimal('0.00'), + 'shangji_fenhong_cishu': 0, + 'shangji_fenhong_zonge': Decimal('0.00'), + 'gei_shangji_cishu': 0, + 'gei_shangji_zonge': Decimal('0.00'), + 'shangji_id': None, + } + ) + + # 更新上级店铺的“作为上级获得”字段 + tongji_shangji.shangji_fenhong_cishu = F('shangji_fenhong_cishu') + 1 + tongji_shangji.shangji_fenhong_zonge = F('shangji_fenhong_zonge') + fenhong_jine + tongji_shangji.save(update_fields=['shangji_fenhong_cishu', 'shangji_fenhong_zonge', 'update_time']) + + # 更新上级店铺主表的分红余额和累计分红 + shangji = Dianpu.objects.select_for_update().get(id=shangji_dianpu_id) + shangji.shangji_fenhong_leiji = F('shangji_fenhong_leiji') + fenhong_jine + shangji.shangji_ketixian_fenhong = F('shangji_ketixian_fenhong') + fenhong_jine + shangji.save(update_fields=['shangji_fenhong_leiji', 'shangji_ketixian_fenhong', 'update_time']) + + logger.info(f"上级店铺 {shangji_dianpu_id} 获得分红 +{fenhong_jine}") + + + +def calculate_pingtai_and_dianpu_shouyi(shangpin_id, dingdan_jiage, dashou_fencheng): + """ + 计算平台收益、店铺收益、上级分红 + + 分配链路(上级分红完全隔离,不影响平台及店铺核心计算): + 总收益 = 订单金额 - 打手分成 + 平台抽成 = 总收益 * (店铺开启则用店铺费率,否则用平台默认费率) + 剩余收益 = 总收益 - 平台抽成 + 上级分红 = 剩余收益 * 本店设置的上级分红费率(若有上级且满足条件) + 店铺收益 = 剩余收益 - 上级分红 + + 参数: + shangpin_id: 商品ID + dingdan_jiage: 订单金额 + dashou_fencheng: 打手分成金额 + + 返回: + 五元组 (dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong) + - dianpu_id: 店铺ID,可能为None + - pingtai_shouyi: 平台收益 + - dianpu_shouyi: 店铺最终收益 + - shangji_dianpu_id: 上级店铺ID,可能为None + - shangji_fenhong: 上级分红金额,默认0.00 + """ + # ==================== 金额安全处理 ==================== + dingdan_jiage = Decimal(str(dingdan_jiage)) if dingdan_jiage else Decimal('0.00') + dashou_fencheng = Decimal(str(dashou_fencheng)) if dashou_fencheng else Decimal('0.00') + if dingdan_jiage < 0: + dingdan_jiage = Decimal('0.00') + if dashou_fencheng < 0: + dashou_fencheng = Decimal('0.00') + + zong_shouyi = dingdan_jiage - dashou_fencheng + if zong_shouyi < 0: + zong_shouyi = Decimal('0.00') + + # ==================== 获取商品及店铺 ==================== + try: + shangpin = Shangpin.objects.select_related('dianpu').only('id', 'dianpu').get(id=shangpin_id) + except Shangpin.DoesNotExist: + logger.error(f"收益计算失败:商品ID {shangpin_id} 不存在") + return None, zong_shouyi, Decimal('0.00'), None, Decimal('0.00') + + dianpu_id = shangpin.dianpu_id + if dianpu_id is None: + logger.info(f"商品 {shangpin_id} 未关联店铺,平台收益={zong_shouyi}") + return None, zong_shouyi, Decimal('0.00'), None, Decimal('0.00') + + # ==================== 获取店铺配置 ==================== + try: + dianpu = Dianpu.objects.only( + 'id', 'kaiqi_shouyi_choucheng', 'shouyi_choucheng_feilv', + 'yaoqing_dianpu_id', 'fenhong_choucheng_feilv' + ).get(id=dianpu_id) + except Dianpu.DoesNotExist: + logger.error(f"收益计算失败:店铺ID {dianpu_id} 不存在") + return dianpu_id, zong_shouyi, Decimal('0.00'), None, Decimal('0.00') + + # ==================== 平台抽成 ==================== + if dianpu.kaiqi_shouyi_choucheng and dianpu.shouyi_choucheng_feilv is not None: + feilv = dianpu.shouyi_choucheng_feilv + else: + moren_peizhi, created = DianpuMorenPeizhi.objects.get_or_create( + id=1, + defaults={ + 'moren_choucheng_feilv': Decimal('0.1000'), + 'moren_tixian_xiane': Decimal('1000.00') + } + ) + feilv = moren_peizhi.moren_choucheng_feilv + if feilv is None or feilv < 0: + feilv = Decimal('0.0000') + if feilv > 1: + feilv = Decimal('1.0000') + + pingtai_shouyi = zong_shouyi * feilv + shengyu = zong_shouyi - pingtai_shouyi + + # ==================== 上级分红(完全隔离,不影响店铺收益) ==================== + shangji_dianpu_id = dianpu.yaoqing_dianpu_id + shangji_fenhong = Decimal('0.00') + + if shangji_dianpu_id and dianpu.fenhong_choucheng_feilv: + try: + # 检查上级店铺状态 + shangji = Dianpu.objects.filter(id=shangji_dianpu_id).only('id', 'zhuangtai').first() + if shangji and shangji.zhuangtai == 1: + fz_rate = dianpu.fenhong_choucheng_feilv + if fz_rate and fz_rate > 0: + shangji_fenhong = shengyu * fz_rate + if shangji_fenhong < 0: + shangji_fenhong = Decimal('0.00') + if shangji_fenhong > shengyu: + shangji_fenhong = shengyu + shengyu -= shangji_fenhong + else: + if shangji: + logger.info(f"上级店铺 {shangji_dianpu_id} 状态非正常,不参与分红") + else: + logger.warning(f"上级店铺ID {shangji_dianpu_id} 不存在,不参与分红") + except Exception as e: + logger.exception(f"上级分红计算异常,忽略: {e}") + shangji_fenhong = Decimal('0.00') + # 注意:shengyu 未被修改,店铺收益不变 + + dianpu_shouyi = shengyu + + logger.info( + f"收益计算完成: 商品={shangpin_id}, 总收益={zong_shouyi}, 平台={pingtai_shouyi}, " + f"上级ID={shangji_dianpu_id}, 上级分红={shangji_fenhong}, 店铺={dianpu_shouyi}" + ) + + return dianpu_id, pingtai_shouyi, dianpu_shouyi, shangji_dianpu_id, shangji_fenhong + + + + + + + + + + +def validate_shangpin_and_dianpu(shangpin_id): + """ + 校验商品及关联店铺(区分有无店铺) + + 无店铺:仅校验商品是否上架 + 有店铺:校验商品审核、封禁、上架,以及店铺是否封禁 + + 返回: + (True, None, shangpin_obj) # 通过 + (False, "错误信息", None) # 失败 + """ + try: + shangpin = Shangpin.objects.select_related('dianpu').get(id=shangpin_id) + except Shangpin.DoesNotExist: + return False, "商品不存在", None + + # 无店铺:只检查上架 + if not shangpin.dianpu_id: + if not shangpin.shangjia_zhuangtai: + return False, "商品已下架", None + return True, None, shangpin + + # 有店铺:完整校验 + if not shangpin.shenhe_zhuangtai: + return False, "商品未通过平台审核", None + if shangpin.fengjin_zhuangtai: + return False, "商品已被封禁", None + if not shangpin.shangjia_zhuangtai: + return False, "商品已下架", None + + try: + if shangpin.dianpu.zhuangtai != 1: + return False, "店铺已被封禁", None + except Exception: + return False, "关联店铺异常", None + return True, None, shangpin \ No newline at end of file diff --git a/shop/views.py b/shop/views.py index e104474..4d041d6 100644 --- a/shop/views.py +++ b/shop/views.py @@ -70,12 +70,12 @@ class BindDianpuView(APIView): return Response({'code': 400, 'msg': '店铺ID不能为空'}) user = request.user - if not hasattr(user, 'yonghuid'): + if not hasattr(user, 'UserUID'): return Response({'code': 500, 'msg': '用户身份信息异常'}) # 先获取 User 对象(不加锁,仅用于校验存在性) try: - user_main = User.query.get(UserUID=user.yonghuid) + user_main = User.query.get(UserUID=user.UserUID) except User.DoesNotExist: return Response({'code': 404, 'msg': '用户不存在'}) @@ -92,7 +92,7 @@ class BindDianpuView(APIView): try: with transaction.atomic(): # 重新获取加锁的用户对象(确保并发安全) - user_main_locked = User.objects.select_for_update().get(UserUID=user.yonghuid) + user_main_locked = User.objects.select_for_update().get(UserUID=user.UserUID) dianpu_locked = Dianpu.objects.select_for_update().get(id=dianpu_id) # 再次校验店铺状态(防止事务期间被修改) @@ -121,7 +121,7 @@ class BindDianpuView(APIView): dianpu_locked.bangding_yonghushu = F('bangding_yonghushu') + 1 dianpu_locked.save(update_fields=['bangding_yonghushu']) - logger.info(f"用户 {user.yonghuid} 绑定店铺 {dianpu_id} 成功") + logger.info(f"用户 {user.UserUID} 绑定店铺 {dianpu_id} 成功") return Response({'code': 200, 'msg': '绑定成功'}) except Exception as e: @@ -396,7 +396,7 @@ class ShangdianLoginView(APIView): # 10. 构造返回数据 data = { 'token': token, - 'yonghuid': user_main.yonghuid, + 'yonghuid': user_main.UserUID, 'dianpu_mingcheng': dianpu.dianpu_mingcheng, 'dianpu_touxiang': dianpu.dianpu_touxiang or '', 'erweima_url': dianpu.erweima_url or '' @@ -2589,7 +2589,7 @@ class ShopRefundView(APIView): if dashou_id: try: dashou_user = User.query.get(UserUID=dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.tuikuanliang += 1 dashou_profile.zhuangtai = 1 dashou_profile.save() @@ -2832,7 +2832,7 @@ class ShopForceCompleteView(APIView): # ---------- 更新打手余额和统计 ---------- try: dashou_user = User.query.get(UserUID=dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.chengjiaozongliang += 1 dashou_profile.yue += dashou_fencheng dashou_profile.zonge += dashou_fencheng diff --git a/users/migrations/0002_alter_adminprofile_user_alter_userboss_user_and_more.py b/users/migrations/0002_alter_adminprofile_user_alter_userboss_user_and_more.py new file mode 100644 index 0000000..f0985c0 --- /dev/null +++ b/users/migrations/0002_alter_adminprofile_user_alter_userboss_user_and_more.py @@ -0,0 +1,56 @@ +# Generated by Django 4.2.27 on 2026-06-19 01:08 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('users', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='adminprofile', + name='user', + field=models.OneToOneField(db_column='UserUUID', on_delete=django.db.models.deletion.CASCADE, related_name='AdminProfile', to=settings.AUTH_USER_MODEL, verbose_name='关联用户'), + ), + migrations.AlterField( + model_name='userboss', + name='user', + field=models.OneToOneField(db_column='UserUUID', on_delete=django.db.models.deletion.CASCADE, related_name='BossProfile', to=settings.AUTH_USER_MODEL, verbose_name='关联用户'), + ), + migrations.AlterField( + model_name='userdashou', + name='user', + field=models.OneToOneField(db_column='UserUUID', on_delete=django.db.models.deletion.CASCADE, related_name='DashouProfile', to=settings.AUTH_USER_MODEL, verbose_name='关联用户'), + ), + migrations.AlterField( + model_name='userguanshi', + name='user', + field=models.OneToOneField(db_column='UserUUID', on_delete=django.db.models.deletion.CASCADE, related_name='GuanshiProfile', to=settings.AUTH_USER_MODEL, verbose_name='关联用户'), + ), + migrations.AlterField( + model_name='userkefu', + name='user', + field=models.OneToOneField(db_column='UserUUID', on_delete=django.db.models.deletion.CASCADE, related_name='KefuProfile', to=settings.AUTH_USER_MODEL, verbose_name='关联用户'), + ), + migrations.AlterField( + model_name='usershangjia', + name='user', + field=models.OneToOneField(db_column='UserUUID', on_delete=django.db.models.deletion.CASCADE, related_name='ShopProfile', to=settings.AUTH_USER_MODEL, verbose_name='关联用户'), + ), + migrations.AlterField( + model_name='usershenheguan', + name='user', + field=models.OneToOneField(db_column='UserUUID', on_delete=django.db.models.deletion.CASCADE, related_name='ShenheguanProfile', to=settings.AUTH_USER_MODEL, verbose_name='关联用户'), + ), + migrations.AlterField( + model_name='userzuzhang', + name='user', + field=models.OneToOneField(db_column='UserUUID', on_delete=django.db.models.deletion.CASCADE, related_name='ZuzhangProfile', to=settings.AUTH_USER_MODEL, verbose_name='关联用户'), + ), + ] diff --git a/users/models.py b/users/models.py index e4c0383..19e0eff 100644 --- a/users/models.py +++ b/users/models.py @@ -1,629 +1,629 @@ -# models.py -from django.db import models -from django.contrib.auth.models import AbstractBaseUser -from gvsdsdk.model_base import QModel - - -# ==================== 用户主表(已废弃,迁移至 gvsdsdk.User) ==================== -class UserMain(AbstractBaseUser, QModel): - # ID字段 - id = models.AutoField(primary_key=True, verbose_name='主键ID') - yonghuid = models.CharField(max_length=7, unique=True, db_index=True, verbose_name='用户ID') - - # 微信相关字段 - openid = models.CharField(max_length=64, unique=True, db_index=True, verbose_name='微信OpenID') - unionid = models.CharField(max_length=64, null=True, blank=True, db_index=True, verbose_name='微信UnionID') - - # 基础信息字段 - #tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='商品图片URL') # 用CharField - avatar = models.CharField(max_length=500, null=True, blank=True,default='a_long/morentouxiang.jpg', verbose_name='头像') # 用CharField - phone = models.CharField(max_length=11, blank=True, null=True, verbose_name='手机号') - shoujihao_renzheng = models.BooleanField(default=False, verbose_name='手机号是否已认证') - password = models.CharField(max_length=200, null=True, blank=True, verbose_name='密码') - zhifu = models.CharField(max_length=500, null=True, blank=True, verbose_name='收款码图片') # 用CharField - skzhanghao = models.CharField(max_length=11, blank=True, null=True, verbose_name='收款账号') - ip = models.CharField(max_length=50, blank=True, null=True, verbose_name='来访用户ip') - last_login_time = models.DateTimeField(null=True, blank=True, verbose_name='最后登录时间') - - - # 用户类型字段 - user_type = models.CharField( - max_length=10, - choices=[ - ('normal', '普通用户'), - ('dashou', '打手'), - ('shop', '商家'), - ('guanshi', '管事'), - ('admin', '管理员') - ], - default='normal', - verbose_name='用户类型' - ) - - # 状态和时间字段 - #is_active = models.BooleanField(default=True, verbose_name='是否激活') - #last_login = models.DateTimeField(blank=True, null=True, verbose_name='最后登录时间') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - # 邀请码字段 - #yaoqingma = models.CharField(max_length=20, blank=True, null=True, verbose_name='邀请码') - - # 只需要添加这3行 - USERNAME_FIELD = 'openid' # 用openid作为用户名 - REQUIRED_FIELDS = ['yonghuid'] # 必填字段 - - class Meta: - managed = False - db_table = 'user_main' - verbose_name = '用户主表' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['yonghuid']), - models.Index(fields=['openid']), - #models.Index(fields=['phone']), - models.Index(fields=['user_type']), - #models.Index(fields=['create_time']), - ] - - def __str__(self): - return f'{self.yonghuid}' - - - -# 新增服务号用户表 -class OfficialAccountUser(QModel): - unionid = models.CharField(max_length=64, null =True,db_index=True, verbose_name='UnionID') - official_openid = models.CharField(max_length=64, db_index=True, unique=True, verbose_name='服务号OpenID') - subscribe_time = models.DateTimeField(auto_now_add=True, verbose_name='关注时间') - is_subscribed = models.BooleanField(default=True, verbose_name='是否关注') - created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'official_account_user' - verbose_name = '服务号用户' - verbose_name_plural = verbose_name - - def __str__(self): - return f"{self.unionid[:8]}... (关注: {self.is_subscribed})" - - -# ==================== 老板扩展表 ==================== -class UserBoss(QModel): - user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='boss_profile', - verbose_name='关联用户', db_column='UserUUID') - nickname = models.CharField(max_length=50, verbose_name='老板昵称') - zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='消费总额') - alldingdan = models.IntegerField(default=0, verbose_name='订单总数') - alltui = models.IntegerField(default=0, verbose_name='退款总数') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'user_boss' - verbose_name = '老板扩展表' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['user']), ] - - -# ==================== 打手扩展表 ==================== -class UserDashou(QModel): - user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='dashou_profile', - verbose_name='关联用户', db_column='UserUUID') - nicheng = models.CharField(max_length=50, null=True, verbose_name='打手昵称') - chenghao = models.CharField(max_length=50, null=True,verbose_name='打手称号') - zhuangtai = models.IntegerField(default=1, verbose_name='打手状态') - zaixianzhuangtai = models.IntegerField(default=1, verbose_name='打手在线状态') - zhanghaozhuangtai = models.IntegerField(default=1, verbose_name='打手账号状态') - jieshao = models.TextField(null=True, verbose_name='打手介绍') - jiedanzongliang = models.IntegerField(default=0, verbose_name='接单总量') - chengjiaozongliang = models.IntegerField(default=0, verbose_name='成交单总量') - tuikuanliang = models.IntegerField(default=0, verbose_name='退款订单总量') - yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现余额') - zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='打单赚取总额') - dianhua = models.CharField(max_length=20, null=True, verbose_name='打手电话') - wechat = models.CharField(max_length=50,null=True, verbose_name='打手微信号') - yaoqingren = models.CharField(max_length=7,null=True, db_index=True, verbose_name='邀请人ID') - jinrijiedan = models.IntegerField(default=0, verbose_name='今日接单数量') - jinrishouyi = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日收益金额') - jinyuejiedan = models.IntegerField(default=0, verbose_name='今月接单数量') - jinyueshouyi = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今月收益金额') - jifen = models.IntegerField(default=0, verbose_name='打手积分') - yajin= models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='打手押金') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - last_tixian_date = models.DateField(null=True, blank=True, verbose_name='最近提现日期') # 用于重置今日提现金额 - jinritixian_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日已提现金额') - kaioi_ewai_xiane = models.BooleanField(default=False, verbose_name='是否开启额外提现限额') # True=开启额外限额 - ewai_xiane = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='额外提现限额(元)') - - class Meta: - db_table = 'user_dashou' - verbose_name = '打手扩展表' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['user']), - #models.Index(fields=['zhuangtai']), - models.Index(fields=['yaoqingren']), - #models.Index(fields=['nicheng']), - - ] - - -# ==================== 商家扩展表 ==================== -class UserShangjia(QModel): - user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='shop_profile', - verbose_name='关联用户', db_column='UserUUID') - nicheng = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='商家昵称') - zhuangtai = models.IntegerField(choices=[(0, '待审核'), (1, '正常'), (2, '禁用')], default=1, - verbose_name='商家状态') - dianhua = models.CharField(max_length=20,null=True,verbose_name='商家电话') - wechat = models.CharField(max_length=50, null=True, verbose_name='商家微信号') - fabu = models.IntegerField(default=0, verbose_name='发布订单总数量') - tuikuan = models.IntegerField(default=0, verbose_name='退款订单总量') - yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='商家余额') - chengjiao = models.IntegerField(default=0, verbose_name='商家成交订单总量') - jinridingdan = models.IntegerField(default=0, verbose_name='商家今日订单总量') - jinriliushui = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='商家今日流水') - jinyuedingdan = models.IntegerField(default=0, verbose_name='商家今月订单总量') - jinyueliushui = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='商家今月流水') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'user_shangjia' - verbose_name = '商家扩展表' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['user']), - models.Index(fields=['nicheng']), - ] - - -# ==================== 管事扩展表 ==================== -class UserGuanshi(QModel): - user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='guanshi_profile', - verbose_name='关联用户', db_column='UserUUID') - yaoqingren = models.CharField(max_length=7,null=True, db_index=True, verbose_name='邀请人ID') - yaoqingma = models.CharField(max_length=20,null=True,db_index=True, verbose_name='邀请码') - invite_qrcode_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='邀请码二维码图片URL') - dianhua = models.CharField(max_length=20,null=True, verbose_name='管事电话') - wechat = models.CharField(max_length=50,null=True, verbose_name='管事微信号') - yaogingshuliang = models.IntegerField(default=0, verbose_name='邀请打手总数') - zhuangtai = models.IntegerField(choices=[(0, '禁用'), (1, '正常')], default=1, verbose_name='管事状态') - jinrichongzhi = models.IntegerField(default=0, verbose_name='今日充值打手数量') - jinyuechongzhi = models.IntegerField(default=0, verbose_name='今月充值打手数量') - chongzhifenrun = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='充值分佣总额') - yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现余额') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - # 新增:首次分红金额(针对特殊用户) - fenhong_yici = models.DecimalField(max_digits=10,decimal_places=2,default=0.00,verbose_name='首次分红金额') - - # 新增:二次分红开关 - fenghong_erci_enabled = models.BooleanField(default=False,verbose_name='开启二次分红') - - # 新增:二次分红金额 - fenghong_erci = models.DecimalField(max_digits=10,decimal_places=2,default=0.00,verbose_name='二次分红金额') - - - last_tixian_date = models.DateField(null=True, blank=True, verbose_name='最近提现日期') - jinritixian_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日已提现金额') - kaioi_ewai_xiane = models.BooleanField(default=False, verbose_name='是否开启额外提现限额') - ewai_xiane = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='额外提现限额(元)') - - class Meta: - db_table = 'user_guanshi' - verbose_name = '管事扩展表' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['user']), - models.Index(fields=['yaoqingma']), - ] - - - - -class UserZuzhang(QModel): - """组长扩展表(与UserMain一对一关联)""" - user = models.OneToOneField( - 'gvsdsdk.User', - on_delete=models.CASCADE, - related_name='zuzhang_profile', - verbose_name='关联用户', - db_index=True, - db_column='UserUUID' - ) - # 组长邀请码 - yaoqingma = models.CharField(max_length=20, null=True, db_index=True, verbose_name='组长邀请码') - # 分佣总额 - fenyong_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='分佣总额(元)') - # 可提现金额 - ketixian_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现金额(元)') - # 邀请总数 - yaoqing_zongshu = models.IntegerField(default=0, verbose_name='邀请总数') - # 今日分佣 - jinri_fenyong = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日分佣(元)') - # 今月分佣 - jinyue_fenyong = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今月分佣(元)') - # 账号状态 - zhuangtai = models.IntegerField(choices=[(0, '禁用'), (1, '正常')], default=1, verbose_name='账号状态') - # 推广海报相对URL - haibao_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='推广海报相对URL') - # 今日提现 - jinri_tixian = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日提现金额(元)') - # 最后提现时间 - last_tixian_time = models.DateTimeField(null=True, blank=True, verbose_name='最后提现时间') - # 额外提现限额 - ewai_tixian_xiane = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='额外提现限额(元)') - # 是否开启额外提现 - kaioi_ewai_tixian = models.BooleanField(default=False, verbose_name='开启额外提现') - # 是否开启额外分红 - kaioi_ewai_fenhong = models.BooleanField(default=False, verbose_name='开启额外分红') - # 额外分红金额 - ewai_fenhong_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='额外分红金额(元)') - # 创建时间 - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - # 更新时间 - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'user_zuzhang' - verbose_name = '组长扩展表' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['user']), - models.Index(fields=['yaoqingma']), - ] - - def __str__(self): - return f"组长 {self.user.yonghuid}" - -class UserShenheguan(QModel): - """审核官扩展表(与UserMain一对一关联)""" - user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='shenheguan_profile', verbose_name='关联用户', db_index=True, db_column='UserUUID') - zhuangtai = models.IntegerField(choices=[(0, '禁用'), (1, '正常')], default=1, verbose_name='账号状态') - shenhe_zhuangtai = models.IntegerField(choices=[(0, '空闲'), (1, '审核中')], default=0, verbose_name='审核官状态') - is_renzheng = models.BooleanField(default=False, verbose_name='是否认证') - shenhe_zongshu = models.IntegerField(default=0, verbose_name='审核总数') - tongguo_zongshu = models.IntegerField(default=0, verbose_name='通过总数') - yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现余额') - zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='赚取总额') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'user_shenheguan' - verbose_name = '审核官扩展表' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['user'])] - - -# ==================== 管理员扩展表 ==================== -class AdminProfile(QModel): - user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='admin_profile', - verbose_name='关联用户', db_column='UserUUID') - password = models.CharField(max_length=255, verbose_name='密码') - wechat = models.CharField(max_length=50, blank=True, verbose_name='管理员微信') - yaoqingma = models.CharField(max_length=20, null=True, db_index=True,verbose_name='管理员邀请码') # 🟢 新增字段 - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'admin_profile' - verbose_name = '管理员扩展表' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['user']), ] - - -class UserKefu(QModel): - user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='kefu_profile', verbose_name='关联用户', db_column='UserUUID') - nicheng = models.CharField(max_length=50, verbose_name='客服昵称') - erjimima = models.CharField(max_length=128, verbose_name='二级密码') - zhuangtai = models.IntegerField(choices=[(0, '禁用'), (1, '正常')], default=1, verbose_name='客服状态') - jinrichuli = models.IntegerField(default=0, verbose_name='今日处理订单数') - jinyuechuli = models.IntegerField(default=0, verbose_name='本月处理订单数') - zongchuli = models.IntegerField(default=0, verbose_name='累计处理订单数') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'user_kefu' - verbose_name = '客服扩展表' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['user']), - models.Index(fields=['zhuangtai']), - ] - - def __str__(self): - return f"{self.nicheng} ({self.user.yonghuid})" - - - - -class Tixianjilu(QModel): - id = models.AutoField(primary_key=True, verbose_name='提现记录ID') - yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='提现用户ID') - avatar = models.CharField(max_length=500, null=True, blank=True, verbose_name='头像') # 用CharField - phone = models.CharField(max_length=11, blank=True, null=True, verbose_name='手机号') - nicheng = models.CharField(max_length=50, null=True, verbose_name='打手昵称') - leixing = models.PositiveIntegerField(null=True, blank=True, verbose_name='提现类型1:打手佣金2:管事分红') - zhifu = models.CharField(max_length=500, null=True, blank=True, verbose_name='收款码图片') # 用CharField - skzhanghao = models.CharField(max_length=11, blank=True, null=True, verbose_name='收款账号') - jine = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='提现金额') - # 状态与平台:用整数表示,具体含义由业务逻辑定义 - zhuangtai = models.IntegerField(default=1, null=True, blank=True, db_index=True, verbose_name='提现状态1:审核2:成功3:失败') - fangshi = models.IntegerField(null=True, blank=True,db_index=True, verbose_name='收款方式1:微信2:支付宝') - shenheid = models.CharField(max_length=11, null=True, db_index=True, verbose_name='审核用户ID') - bhliyou = models.TextField(null=True, blank=True, verbose_name='驳回理由') - # ---------- 自动提现审核新流程关联字段(旧数据为空,兼容历史记录) ---------- - shenhe_jilu_id = models.IntegerField( - null=True, blank=True, db_index=True, - verbose_name='关联提现审核记录ID(TixianShenheJilu.id)' - ) - shenhe_danhao = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - verbose_name='关联提现审核单号' - ) - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'tixianjilu' - verbose_name = '提现记录表' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['yonghuid']), - models.Index(fields=['zhuangtai']), - models.Index(fields=['shenheid']), - models.Index(fields=['fangshi']), - models.Index(fields=['shenhe_danhao']), - models.Index(fields=['shenhe_jilu_id']), - ] - - - - - - -class TixianShenheJilu(QModel): - """ - 提现审核记录表(自动打款新流程) - 状态码与前端 TX_STATUS / Tixianjilu.zhuangtai 完全对标: - 1=审核中 2=提现成功(已收款) 3=提现失败 4=审核成功 5=审核失败 6=待收款 - """ - # 审核单号(唯一,前端字段 shenhe_danhao) - shenhe_danhao = models.CharField( - max_length=32, unique=True, db_index=True, verbose_name='审核单号' - ) - yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') - # 提现类型:1打手佣金 2管事分红 3组长分红 4考核官分佣 5打手押金 6商家余额 - leixing = models.PositiveSmallIntegerField(db_index=True, verbose_name='提现类型') - zhuangtai = models.IntegerField( - default=1, db_index=True, - verbose_name='审核状态(1审核中2提现成功3提现失败4审核成功5审核失败6待收款)' - ) - # 金额:shenqing_jine=申请扣款金额;shijidaozhang=实际打款到账金额 - shenqing_jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='申请提现金额(扣款额)') - shouxufei = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='手续费') - shijidaozhang = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='实际到账金额') - feilv = models.DecimalField(max_digits=5, decimal_places=4, default=0, verbose_name='手续费率') - # 审核/驳回 - bhliyou = models.TextField(null=True, blank=True, verbose_name='驳回/失败原因') - bo_hui_ren_id = models.CharField( - max_length=11, null=True, blank=True, db_index=True, verbose_name='驳回人用户ID' - ) - shenhe_ren_id = models.CharField( - max_length=11, null=True, blank=True, db_index=True, verbose_name='审核通过操作人ID' - ) - # 关联提现记录表主键 - tixianjilu_id = models.IntegerField(null=True, blank=True, db_index=True, verbose_name='关联Tixianjilu.id') - # 最近一次打款单号(仅作记录,不唯一;同一审核单可多次收款产生多条打款记录) - tixian_auto_id = models.CharField( - max_length=32, null=True, blank=True, db_index=True, - verbose_name='最近一次打款单号(非唯一,重试收款会更新)' - ) - fail_reason = models.TextField(null=True, blank=True, verbose_name='微信打款失败原因') - # 打款方式:1=手动打款 2=自动打款(微信商家转账) - dakuan_mode = models.PositiveSmallIntegerField( - default=1, db_index=True, verbose_name='打款方式(1手动2自动)' - ) - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'tixian_shenhe_jilu' - verbose_name = '提现审核记录' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['yonghuid', 'zhuangtai']), - models.Index(fields=['yonghuid', 'leixing']), - models.Index(fields=['shenhe_danhao']), - ] - - def __str__(self): - return f'{self.shenhe_danhao}({self.yonghuid})' - - - -class TixianAutoRecord(QModel): - """ - 自动提现记录表(微信自动打款) - """ - id = models.BigAutoField(primary_key=True) - tixian_id = models.CharField( - max_length=32, - unique=True, - db_index=True, - verbose_name='提现订单号(自定义)' - ) - yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') - leixing = models.IntegerField(verbose_name='提现类型 1打手 2管事') - jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='申请提现金额') - shouxufei = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='手续费') - shijidaozhang = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='实际到账金额') - feilv = models.DecimalField(max_digits=5, decimal_places=4, verbose_name='费率') - zhuangtai = models.IntegerField( - default=0, - choices=[(0, '处理中'), (1, '成功'), (2, '失败')], - verbose_name='状态' - ) - wechat_transfer_no = models.CharField( - max_length=64, - null=True, - blank=True, - verbose_name='微信转账单号' - ) - wechat_package = models.TextField( - null=True, - blank=True, - verbose_name='微信返回的package_info(JSON)' - ) - fail_reason = models.TextField(null=True, blank=True, verbose_name='失败原因') - # 关联提现审核单号(新流程必填,旧数据为空) - shenhe_danhao = models.CharField( - max_length=32, null=True, blank=True, db_index=True, verbose_name='关联审核单号' - ) - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'tixian_auto_record' - verbose_name = '自动提现记录' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['tixian_id']), - models.Index(fields=['yonghuid']), - models.Index(fields=['zhuangtai']), - ] - - def __str__(self): - return f"{self.tixian_id} - {self.get_zhuangtai_display()}" - - - - - -# yonghu/models.py -class Xiugaijilu(QModel): - id = models.AutoField(primary_key=True, verbose_name='记录ID') - yonghuid = models.CharField(max_length=7, verbose_name='被修改用户ID') - xiugaiid = models.CharField(max_length=30, verbose_name='修改人账号ID') - leixing = models.IntegerField(verbose_name='被修改用户类型') - - # 打手相关金钱字段 - xiugaitijiao = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, - verbose_name='修改后的打手提现金额') - xiugaitijiaoq = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, - verbose_name='修改前的打手提现金额') - - # 打手积分 - jifen = models.IntegerField(default=0, verbose_name='修改后的打手积分') - yjifen = models.IntegerField(default=0, verbose_name='修改前的打手积分') - - # 打手押金 - yajin = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改后的打手押金') - yyajin = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改前的打手押金') - - # 商家余额 - shangjiayue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改后的商家余额') - shangjiayueq = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改前的商家余额') - - # 管事余额 - guanshiyue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改后的管事余额') - guanshiyueq = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改前的管事余额') - - # 会员相关 - huiyuants = models.IntegerField(default=0, verbose_name='添加会员天数') - huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') - - # 其他字段 - qitashuoming = models.TextField(null=True, blank=True, verbose_name='其他说明') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'xiugaijilu' - verbose_name = '修改记录表' - verbose_name_plural = verbose_name - - def __str__(self): - return f"修改记录-用户{self.yonghuid}" - - - - - -# yonghu/models.py - 新增排行榜模型 -class RankingRecord(QModel): - # id: 主键ID - id = models.AutoField(primary_key=True, verbose_name='ID') - - # zhouqi: 周期类型,1=日榜,2=月榜 - zhouqi = models.IntegerField(choices=[(1, '日榜'), (2, '月榜')], verbose_name='周期类型') - - # tongji_date: 统计日期(日榜存具体日期,月榜存月份第一天) - tongji_date = models.DateField(db_index=True, verbose_name='统计日期') - - # shenfen: 用户身份,2=打手,3=管事,4=商家(1=普通用户不统计) - shenfen = models.IntegerField(choices=[(2, '打手'), (3, '管事'), (4, '商家')], verbose_name='用户身份') - - # yonghuid: 用户ID(冗余存储,避免连表查询) - yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') - - # paiming: 排名(从1开始) - paiming = models.IntegerField(verbose_name='排名') - - # zhizhi: 指标数值(打手:收益金额,管事:充值人数,商家:流水金额) - zhizhi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='指标值') - - # nicheng: 用户昵称 - nicheng = models.CharField(max_length=50, null=True,verbose_name='用户昵称') - - # avatar: 用户头像URL - avatar = models.CharField(max_length=500, blank=True, null=True, verbose_name='头像') - - # chuangjian_time: 记录创建时间 - chuangjian_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - - class Meta: - db_table = 'ranking_record' - verbose_name = '排行榜记录' - verbose_name_plural = verbose_name - indexes = [ - models.Index(fields=['zhouqi', 'tongji_date', 'shenfen', 'paiming']), - models.Index(fields=['yonghuid', 'zhouqi']), - models.Index(fields=['tongji_date']), - ] - unique_together = [['zhouqi', 'tongji_date', 'shenfen', 'paiming']] - - def __str__(self): - zhouqi_str = '日' if self.zhouqi == 1 else '月' - shenfen_map = {2: '打手', 3: '管事', 4: '商家'} - return f"{self.tongji_date} {zhouqi_str}榜 {shenfen_map.get(self.shenfen)} 第{self.paiming}名" - - -# ==================== 用户敏感数据表 ==================== -class UserSensitiveData(QModel): - """用户敏感数据表——独立存储敏感信息,不继承 gvsdsdk""" - user_uuid = models.OneToOneField( - 'gvsdsdk.User', - on_delete=models.CASCADE, - related_name='sensitive_data', - verbose_name='关联用户', - db_column='user_uuid_id', - ) - password = models.CharField(max_length=200, null=True, blank=True, verbose_name='密码') - zhifu = models.CharField(max_length=500, null=True, blank=True, verbose_name='收款码图片') - skzhanghao = models.CharField(max_length=11, blank=True, null=True, verbose_name='收款账号') - shoujihao_renzheng = models.BooleanField(default=False, verbose_name='手机号是否已认证') - create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') - update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') - - class Meta: - db_table = 'user_sensitive_data' - verbose_name = '用户敏感数据' - verbose_name_plural = verbose_name - indexes = [models.Index(fields=['user_uuid'])] - - def __str__(self): - return f'敏感数据-{self.user_uuid_id}' - - +# models.py +from django.db import models +from django.contrib.auth.models import AbstractBaseUser +from gvsdsdk.model_base import QModel + + +# ==================== 用户主表(已废弃,迁移至 gvsdsdk.User) ==================== +class UserMain(AbstractBaseUser, QModel): + # ID字段 + id = models.AutoField(primary_key=True, verbose_name='主键ID') + yonghuid = models.CharField(max_length=7, unique=True, db_index=True, verbose_name='用户ID') + + # 微信相关字段 + openid = models.CharField(max_length=64, unique=True, db_index=True, verbose_name='微信OpenID') + unionid = models.CharField(max_length=64, null=True, blank=True, db_index=True, verbose_name='微信UnionID') + + # 基础信息字段 + #tupian_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='商品图片URL') # 用CharField + avatar = models.CharField(max_length=500, null=True, blank=True,default='a_long/morentouxiang.jpg', verbose_name='头像') # 用CharField + phone = models.CharField(max_length=11, blank=True, null=True, verbose_name='手机号') + shoujihao_renzheng = models.BooleanField(default=False, verbose_name='手机号是否已认证') + password = models.CharField(max_length=200, null=True, blank=True, verbose_name='密码') + zhifu = models.CharField(max_length=500, null=True, blank=True, verbose_name='收款码图片') # 用CharField + skzhanghao = models.CharField(max_length=11, blank=True, null=True, verbose_name='收款账号') + ip = models.CharField(max_length=50, blank=True, null=True, verbose_name='来访用户ip') + last_login_time = models.DateTimeField(null=True, blank=True, verbose_name='最后登录时间') + + + # 用户类型字段 + user_type = models.CharField( + max_length=10, + choices=[ + ('normal', '普通用户'), + ('dashou', '打手'), + ('shop', '商家'), + ('guanshi', '管事'), + ('admin', '管理员') + ], + default='normal', + verbose_name='用户类型' + ) + + # 状态和时间字段 + #is_active = models.BooleanField(default=True, verbose_name='是否激活') + #last_login = models.DateTimeField(blank=True, null=True, verbose_name='最后登录时间') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + # 邀请码字段 + #yaoqingma = models.CharField(max_length=20, blank=True, null=True, verbose_name='邀请码') + + # 只需要添加这3行 + USERNAME_FIELD = 'openid' # 用openid作为用户名 + REQUIRED_FIELDS = ['yonghuid'] # 必填字段 + + class Meta: + managed = False + db_table = 'user_main' + verbose_name = '用户主表' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['yonghuid']), + models.Index(fields=['openid']), + #models.Index(fields=['phone']), + models.Index(fields=['user_type']), + #models.Index(fields=['create_time']), + ] + + def __str__(self): + return f'{self.yonghuid}' + + + +# 新增服务号用户表 +class OfficialAccountUser(QModel): + unionid = models.CharField(max_length=64, null =True,db_index=True, verbose_name='UnionID') + official_openid = models.CharField(max_length=64, db_index=True, unique=True, verbose_name='服务号OpenID') + subscribe_time = models.DateTimeField(auto_now_add=True, verbose_name='关注时间') + is_subscribed = models.BooleanField(default=True, verbose_name='是否关注') + created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'official_account_user' + verbose_name = '服务号用户' + verbose_name_plural = verbose_name + + def __str__(self): + return f"{self.unionid[:8]}... (关注: {self.is_subscribed})" + + +# ==================== 老板扩展表 ==================== +class UserBoss(QModel): + user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='BossProfile', + verbose_name='关联用户', db_column='UserUUID') + nickname = models.CharField(max_length=50, verbose_name='老板昵称') + zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='消费总额') + alldingdan = models.IntegerField(default=0, verbose_name='订单总数') + alltui = models.IntegerField(default=0, verbose_name='退款总数') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'user_boss' + verbose_name = '老板扩展表' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['user']), ] + + +# ==================== 打手扩展表 ==================== +class UserDashou(QModel): + user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='DashouProfile', + verbose_name='关联用户', db_column='UserUUID') + nicheng = models.CharField(max_length=50, null=True, verbose_name='打手昵称') + chenghao = models.CharField(max_length=50, null=True,verbose_name='打手称号') + zhuangtai = models.IntegerField(default=1, verbose_name='打手状态') + zaixianzhuangtai = models.IntegerField(default=1, verbose_name='打手在线状态') + zhanghaozhuangtai = models.IntegerField(default=1, verbose_name='打手账号状态') + jieshao = models.TextField(null=True, verbose_name='打手介绍') + jiedanzongliang = models.IntegerField(default=0, verbose_name='接单总量') + chengjiaozongliang = models.IntegerField(default=0, verbose_name='成交单总量') + tuikuanliang = models.IntegerField(default=0, verbose_name='退款订单总量') + yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现余额') + zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='打单赚取总额') + dianhua = models.CharField(max_length=20, null=True, verbose_name='打手电话') + wechat = models.CharField(max_length=50,null=True, verbose_name='打手微信号') + yaoqingren = models.CharField(max_length=7,null=True, db_index=True, verbose_name='邀请人ID') + jinrijiedan = models.IntegerField(default=0, verbose_name='今日接单数量') + jinrishouyi = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日收益金额') + jinyuejiedan = models.IntegerField(default=0, verbose_name='今月接单数量') + jinyueshouyi = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今月收益金额') + jifen = models.IntegerField(default=0, verbose_name='打手积分') + yajin= models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='打手押金') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + last_tixian_date = models.DateField(null=True, blank=True, verbose_name='最近提现日期') # 用于重置今日提现金额 + jinritixian_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日已提现金额') + kaioi_ewai_xiane = models.BooleanField(default=False, verbose_name='是否开启额外提现限额') # True=开启额外限额 + ewai_xiane = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='额外提现限额(元)') + + class Meta: + db_table = 'user_dashou' + verbose_name = '打手扩展表' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['user']), + #models.Index(fields=['zhuangtai']), + models.Index(fields=['yaoqingren']), + #models.Index(fields=['nicheng']), + + ] + + +# ==================== 商家扩展表 ==================== +class UserShangjia(QModel): + user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='ShopProfile', + verbose_name='关联用户', db_column='UserUUID') + nicheng = models.CharField(max_length=50, unique=True, db_index=True, verbose_name='商家昵称') + zhuangtai = models.IntegerField(choices=[(0, '待审核'), (1, '正常'), (2, '禁用')], default=1, + verbose_name='商家状态') + dianhua = models.CharField(max_length=20,null=True,verbose_name='商家电话') + wechat = models.CharField(max_length=50, null=True, verbose_name='商家微信号') + fabu = models.IntegerField(default=0, verbose_name='发布订单总数量') + tuikuan = models.IntegerField(default=0, verbose_name='退款订单总量') + yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='商家余额') + chengjiao = models.IntegerField(default=0, verbose_name='商家成交订单总量') + jinridingdan = models.IntegerField(default=0, verbose_name='商家今日订单总量') + jinriliushui = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='商家今日流水') + jinyuedingdan = models.IntegerField(default=0, verbose_name='商家今月订单总量') + jinyueliushui = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='商家今月流水') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'user_shangjia' + verbose_name = '商家扩展表' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['user']), + models.Index(fields=['nicheng']), + ] + + +# ==================== 管事扩展表 ==================== +class UserGuanshi(QModel): + user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='GuanshiProfile', + verbose_name='关联用户', db_column='UserUUID') + yaoqingren = models.CharField(max_length=7,null=True, db_index=True, verbose_name='邀请人ID') + yaoqingma = models.CharField(max_length=20,null=True,db_index=True, verbose_name='邀请码') + invite_qrcode_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='邀请码二维码图片URL') + dianhua = models.CharField(max_length=20,null=True, verbose_name='管事电话') + wechat = models.CharField(max_length=50,null=True, verbose_name='管事微信号') + yaogingshuliang = models.IntegerField(default=0, verbose_name='邀请打手总数') + zhuangtai = models.IntegerField(choices=[(0, '禁用'), (1, '正常')], default=1, verbose_name='管事状态') + jinrichongzhi = models.IntegerField(default=0, verbose_name='今日充值打手数量') + jinyuechongzhi = models.IntegerField(default=0, verbose_name='今月充值打手数量') + chongzhifenrun = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='充值分佣总额') + yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现余额') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + # 新增:首次分红金额(针对特殊用户) + fenhong_yici = models.DecimalField(max_digits=10,decimal_places=2,default=0.00,verbose_name='首次分红金额') + + # 新增:二次分红开关 + fenghong_erci_enabled = models.BooleanField(default=False,verbose_name='开启二次分红') + + # 新增:二次分红金额 + fenghong_erci = models.DecimalField(max_digits=10,decimal_places=2,default=0.00,verbose_name='二次分红金额') + + + last_tixian_date = models.DateField(null=True, blank=True, verbose_name='最近提现日期') + jinritixian_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日已提现金额') + kaioi_ewai_xiane = models.BooleanField(default=False, verbose_name='是否开启额外提现限额') + ewai_xiane = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='额外提现限额(元)') + + class Meta: + db_table = 'user_guanshi' + verbose_name = '管事扩展表' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['user']), + models.Index(fields=['yaoqingma']), + ] + + + + +class UserZuzhang(QModel): + """组长扩展表(与UserMain一对一关联)""" + user = models.OneToOneField( + 'gvsdsdk.User', + on_delete=models.CASCADE, + related_name='ZuzhangProfile', + verbose_name='关联用户', + db_index=True, + db_column='UserUUID' + ) + # 组长邀请码 + yaoqingma = models.CharField(max_length=20, null=True, db_index=True, verbose_name='组长邀请码') + # 分佣总额 + fenyong_zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='分佣总额(元)') + # 可提现金额 + ketixian_jine = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现金额(元)') + # 邀请总数 + yaoqing_zongshu = models.IntegerField(default=0, verbose_name='邀请总数') + # 今日分佣 + jinri_fenyong = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日分佣(元)') + # 今月分佣 + jinyue_fenyong = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今月分佣(元)') + # 账号状态 + zhuangtai = models.IntegerField(choices=[(0, '禁用'), (1, '正常')], default=1, verbose_name='账号状态') + # 推广海报相对URL + haibao_url = models.CharField(max_length=500, null=True, blank=True, verbose_name='推广海报相对URL') + # 今日提现 + jinri_tixian = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='今日提现金额(元)') + # 最后提现时间 + last_tixian_time = models.DateTimeField(null=True, blank=True, verbose_name='最后提现时间') + # 额外提现限额 + ewai_tixian_xiane = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='额外提现限额(元)') + # 是否开启额外提现 + kaioi_ewai_tixian = models.BooleanField(default=False, verbose_name='开启额外提现') + # 是否开启额外分红 + kaioi_ewai_fenhong = models.BooleanField(default=False, verbose_name='开启额外分红') + # 额外分红金额 + ewai_fenhong_jine = models.DecimalField(max_digits=10, decimal_places=2, default=0.00, verbose_name='额外分红金额(元)') + # 创建时间 + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + # 更新时间 + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'user_zuzhang' + verbose_name = '组长扩展表' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['user']), + models.Index(fields=['yaoqingma']), + ] + + def __str__(self): + return f"组长 {self.user.UserUID}" + +class UserShenheguan(QModel): + """审核官扩展表(与UserMain一对一关联)""" + user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='ShenheguanProfile', verbose_name='关联用户', db_index=True, db_column='UserUUID') + zhuangtai = models.IntegerField(choices=[(0, '禁用'), (1, '正常')], default=1, verbose_name='账号状态') + shenhe_zhuangtai = models.IntegerField(choices=[(0, '空闲'), (1, '审核中')], default=0, verbose_name='审核官状态') + is_renzheng = models.BooleanField(default=False, verbose_name='是否认证') + shenhe_zongshu = models.IntegerField(default=0, verbose_name='审核总数') + tongguo_zongshu = models.IntegerField(default=0, verbose_name='通过总数') + yue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='可提现余额') + zonge = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='赚取总额') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'user_shenheguan' + verbose_name = '审核官扩展表' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['user'])] + + +# ==================== 管理员扩展表 ==================== +class AdminProfile(QModel): + user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='AdminProfile', + verbose_name='关联用户', db_column='UserUUID') + password = models.CharField(max_length=255, verbose_name='密码') + wechat = models.CharField(max_length=50, blank=True, verbose_name='管理员微信') + yaoqingma = models.CharField(max_length=20, null=True, db_index=True,verbose_name='管理员邀请码') # 🟢 新增字段 + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'admin_profile' + verbose_name = '管理员扩展表' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['user']), ] + + +class UserKefu(QModel): + user = models.OneToOneField('gvsdsdk.User', on_delete=models.CASCADE, related_name='KefuProfile', verbose_name='关联用户', db_column='UserUUID') + nicheng = models.CharField(max_length=50, verbose_name='客服昵称') + erjimima = models.CharField(max_length=128, verbose_name='二级密码') + zhuangtai = models.IntegerField(choices=[(0, '禁用'), (1, '正常')], default=1, verbose_name='客服状态') + jinrichuli = models.IntegerField(default=0, verbose_name='今日处理订单数') + jinyuechuli = models.IntegerField(default=0, verbose_name='本月处理订单数') + zongchuli = models.IntegerField(default=0, verbose_name='累计处理订单数') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'user_kefu' + verbose_name = '客服扩展表' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['user']), + models.Index(fields=['zhuangtai']), + ] + + def __str__(self): + return f"{self.nicheng} ({self.user.UserUID})" + + + + +class Tixianjilu(QModel): + id = models.AutoField(primary_key=True, verbose_name='提现记录ID') + yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='提现用户ID') + avatar = models.CharField(max_length=500, null=True, blank=True, verbose_name='头像') # 用CharField + phone = models.CharField(max_length=11, blank=True, null=True, verbose_name='手机号') + nicheng = models.CharField(max_length=50, null=True, verbose_name='打手昵称') + leixing = models.PositiveIntegerField(null=True, blank=True, verbose_name='提现类型1:打手佣金2:管事分红') + zhifu = models.CharField(max_length=500, null=True, blank=True, verbose_name='收款码图片') # 用CharField + skzhanghao = models.CharField(max_length=11, blank=True, null=True, verbose_name='收款账号') + jine = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='提现金额') + # 状态与平台:用整数表示,具体含义由业务逻辑定义 + zhuangtai = models.IntegerField(default=1, null=True, blank=True, db_index=True, verbose_name='提现状态1:审核2:成功3:失败') + fangshi = models.IntegerField(null=True, blank=True,db_index=True, verbose_name='收款方式1:微信2:支付宝') + shenheid = models.CharField(max_length=11, null=True, db_index=True, verbose_name='审核用户ID') + bhliyou = models.TextField(null=True, blank=True, verbose_name='驳回理由') + # ---------- 自动提现审核新流程关联字段(旧数据为空,兼容历史记录) ---------- + shenhe_jilu_id = models.IntegerField( + null=True, blank=True, db_index=True, + verbose_name='关联提现审核记录ID(TixianShenheJilu.id)' + ) + shenhe_danhao = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + verbose_name='关联提现审核单号' + ) + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'tixianjilu' + verbose_name = '提现记录表' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['yonghuid']), + models.Index(fields=['zhuangtai']), + models.Index(fields=['shenheid']), + models.Index(fields=['fangshi']), + models.Index(fields=['shenhe_danhao']), + models.Index(fields=['shenhe_jilu_id']), + ] + + + + + + +class TixianShenheJilu(QModel): + """ + 提现审核记录表(自动打款新流程) + 状态码与前端 TX_STATUS / Tixianjilu.zhuangtai 完全对标: + 1=审核中 2=提现成功(已收款) 3=提现失败 4=审核成功 5=审核失败 6=待收款 + """ + # 审核单号(唯一,前端字段 shenhe_danhao) + shenhe_danhao = models.CharField( + max_length=32, unique=True, db_index=True, verbose_name='审核单号' + ) + yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') + # 提现类型:1打手佣金 2管事分红 3组长分红 4考核官分佣 5打手押金 6商家余额 + leixing = models.PositiveSmallIntegerField(db_index=True, verbose_name='提现类型') + zhuangtai = models.IntegerField( + default=1, db_index=True, + verbose_name='审核状态(1审核中2提现成功3提现失败4审核成功5审核失败6待收款)' + ) + # 金额:shenqing_jine=申请扣款金额;shijidaozhang=实际打款到账金额 + shenqing_jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='申请提现金额(扣款额)') + shouxufei = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='手续费') + shijidaozhang = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='实际到账金额') + feilv = models.DecimalField(max_digits=5, decimal_places=4, default=0, verbose_name='手续费率') + # 审核/驳回 + bhliyou = models.TextField(null=True, blank=True, verbose_name='驳回/失败原因') + bo_hui_ren_id = models.CharField( + max_length=11, null=True, blank=True, db_index=True, verbose_name='驳回人用户ID' + ) + shenhe_ren_id = models.CharField( + max_length=11, null=True, blank=True, db_index=True, verbose_name='审核通过操作人ID' + ) + # 关联提现记录表主键 + tixianjilu_id = models.IntegerField(null=True, blank=True, db_index=True, verbose_name='关联Tixianjilu.id') + # 最近一次打款单号(仅作记录,不唯一;同一审核单可多次收款产生多条打款记录) + tixian_auto_id = models.CharField( + max_length=32, null=True, blank=True, db_index=True, + verbose_name='最近一次打款单号(非唯一,重试收款会更新)' + ) + fail_reason = models.TextField(null=True, blank=True, verbose_name='微信打款失败原因') + # 打款方式:1=手动打款 2=自动打款(微信商家转账) + dakuan_mode = models.PositiveSmallIntegerField( + default=1, db_index=True, verbose_name='打款方式(1手动2自动)' + ) + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'tixian_shenhe_jilu' + verbose_name = '提现审核记录' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['yonghuid', 'zhuangtai']), + models.Index(fields=['yonghuid', 'leixing']), + models.Index(fields=['shenhe_danhao']), + ] + + def __str__(self): + return f'{self.shenhe_danhao}({self.yonghuid})' + + + +class TixianAutoRecord(QModel): + """ + 自动提现记录表(微信自动打款) + """ + id = models.BigAutoField(primary_key=True) + tixian_id = models.CharField( + max_length=32, + unique=True, + db_index=True, + verbose_name='提现订单号(自定义)' + ) + yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') + leixing = models.IntegerField(verbose_name='提现类型 1打手 2管事') + jine = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='申请提现金额') + shouxufei = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='手续费') + shijidaozhang = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='实际到账金额') + feilv = models.DecimalField(max_digits=5, decimal_places=4, verbose_name='费率') + zhuangtai = models.IntegerField( + default=0, + choices=[(0, '处理中'), (1, '成功'), (2, '失败')], + verbose_name='状态' + ) + wechat_transfer_no = models.CharField( + max_length=64, + null=True, + blank=True, + verbose_name='微信转账单号' + ) + wechat_package = models.TextField( + null=True, + blank=True, + verbose_name='微信返回的package_info(JSON)' + ) + fail_reason = models.TextField(null=True, blank=True, verbose_name='失败原因') + # 关联提现审核单号(新流程必填,旧数据为空) + shenhe_danhao = models.CharField( + max_length=32, null=True, blank=True, db_index=True, verbose_name='关联审核单号' + ) + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'tixian_auto_record' + verbose_name = '自动提现记录' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['tixian_id']), + models.Index(fields=['yonghuid']), + models.Index(fields=['zhuangtai']), + ] + + def __str__(self): + return f"{self.tixian_id} - {self.get_zhuangtai_display()}" + + + + + +# yonghu/models.py +class Xiugaijilu(QModel): + id = models.AutoField(primary_key=True, verbose_name='记录ID') + yonghuid = models.CharField(max_length=7, verbose_name='被修改用户ID') + xiugaiid = models.CharField(max_length=30, verbose_name='修改人账号ID') + leixing = models.IntegerField(verbose_name='被修改用户类型') + + # 打手相关金钱字段 + xiugaitijiao = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, + verbose_name='修改后的打手提现金额') + xiugaitijiaoq = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, + verbose_name='修改前的打手提现金额') + + # 打手积分 + jifen = models.IntegerField(default=0, verbose_name='修改后的打手积分') + yjifen = models.IntegerField(default=0, verbose_name='修改前的打手积分') + + # 打手押金 + yajin = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改后的打手押金') + yyajin = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改前的打手押金') + + # 商家余额 + shangjiayue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改后的商家余额') + shangjiayueq = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改前的商家余额') + + # 管事余额 + guanshiyue = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改后的管事余额') + guanshiyueq = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='修改前的管事余额') + + # 会员相关 + huiyuants = models.IntegerField(default=0, verbose_name='添加会员天数') + huiyuan_id = models.CharField(max_length=6, null=True, blank=True, verbose_name='会员ID') + + # 其他字段 + qitashuoming = models.TextField(null=True, blank=True, verbose_name='其他说明') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'xiugaijilu' + verbose_name = '修改记录表' + verbose_name_plural = verbose_name + + def __str__(self): + return f"修改记录-用户{self.yonghuid}" + + + + + +# yonghu/models.py - 新增排行榜模型 +class RankingRecord(QModel): + # id: 主键ID + id = models.AutoField(primary_key=True, verbose_name='ID') + + # zhouqi: 周期类型,1=日榜,2=月榜 + zhouqi = models.IntegerField(choices=[(1, '日榜'), (2, '月榜')], verbose_name='周期类型') + + # tongji_date: 统计日期(日榜存具体日期,月榜存月份第一天) + tongji_date = models.DateField(db_index=True, verbose_name='统计日期') + + # shenfen: 用户身份,2=打手,3=管事,4=商家(1=普通用户不统计) + shenfen = models.IntegerField(choices=[(2, '打手'), (3, '管事'), (4, '商家')], verbose_name='用户身份') + + # yonghuid: 用户ID(冗余存储,避免连表查询) + yonghuid = models.CharField(max_length=7, db_index=True, verbose_name='用户ID') + + # paiming: 排名(从1开始) + paiming = models.IntegerField(verbose_name='排名') + + # zhizhi: 指标数值(打手:收益金额,管事:充值人数,商家:流水金额) + zhizhi = models.DecimalField(max_digits=12, decimal_places=2, default=0.00, verbose_name='指标值') + + # nicheng: 用户昵称 + nicheng = models.CharField(max_length=50, null=True,verbose_name='用户昵称') + + # avatar: 用户头像URL + avatar = models.CharField(max_length=500, blank=True, null=True, verbose_name='头像') + + # chuangjian_time: 记录创建时间 + chuangjian_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + + class Meta: + db_table = 'ranking_record' + verbose_name = '排行榜记录' + verbose_name_plural = verbose_name + indexes = [ + models.Index(fields=['zhouqi', 'tongji_date', 'shenfen', 'paiming']), + models.Index(fields=['yonghuid', 'zhouqi']), + models.Index(fields=['tongji_date']), + ] + unique_together = [['zhouqi', 'tongji_date', 'shenfen', 'paiming']] + + def __str__(self): + zhouqi_str = '日' if self.zhouqi == 1 else '月' + shenfen_map = {2: '打手', 3: '管事', 4: '商家'} + return f"{self.tongji_date} {zhouqi_str}榜 {shenfen_map.get(self.shenfen)} 第{self.paiming}名" + + +# ==================== 用户敏感数据表 ==================== +class UserSensitiveData(QModel): + """用户敏感数据表——独立存储敏感信息,不继承 gvsdsdk""" + user_uuid = models.OneToOneField( + 'gvsdsdk.User', + on_delete=models.CASCADE, + related_name='sensitive_data', + verbose_name='关联用户', + db_column='user_uuid_id', + ) + password = models.CharField(max_length=200, null=True, blank=True, verbose_name='密码') + zhifu = models.CharField(max_length=500, null=True, blank=True, verbose_name='收款码图片') + skzhanghao = models.CharField(max_length=11, blank=True, null=True, verbose_name='收款账号') + shoujihao_renzheng = models.BooleanField(default=False, verbose_name='手机号是否已认证') + create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') + update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间') + + class Meta: + db_table = 'user_sensitive_data' + verbose_name = '用户敏感数据' + verbose_name_plural = verbose_name + indexes = [models.Index(fields=['user_uuid'])] + + def __str__(self): + return f'敏感数据-{self.user_uuid_id}' + + diff --git a/users/paihang_views.py b/users/paihang_views.py index 35fb2af..dc24aff 100644 --- a/users/paihang_views.py +++ b/users/paihang_views.py @@ -209,17 +209,17 @@ class PhbHqsjView(APIView): return {}, {} users = User.objects.filter(UserUID__in=yonghuids) - avatar_map = {u.yonghuid: u.avatar or '' for u in users} + avatar_map = {u.UserUID: u.Avatar or '' for u in users} nick_map = {} if shenfen == 'dashou': for p in UserDashou.objects.filter(user__UserUID__in=yonghuids).select_related('user'): - nick_map[p.user.yonghuid] = p.nicheng or '用户' + nick_map[p.user.UserUID] = p.nicheng or '用户' elif shenfen == 'shangjia': for p in UserShangjia.objects.filter(user__UserUID__in=yonghuids).select_related('user'): - nick_map[p.user.yonghuid] = p.nicheng or '用户' + nick_map[p.user.UserUID] = p.nicheng or '用户' else: for b in UserBoss.objects.filter(user__UserUID__in=yonghuids).select_related('user'): - nick_map[b.user.yonghuid] = b.nickname or '用户' + nick_map[b.user.UserUID] = b.nickname or '用户' return nick_map, avatar_map diff --git a/users/ranking_tasks.py b/users/ranking_tasks.py index fbd94c1..f1c343f 100644 --- a/users/ranking_tasks.py +++ b/users/ranking_tasks.py @@ -1,281 +1,281 @@ -# yonghu/ranking_tasks.py -""" -排行榜数据转移任务 - 生产环境专用 -在清空前将数据转移到排行榜表 -""" - -from celery import shared_task -from django.db import transaction -from django.utils import timezone -from datetime import datetime, timedelta -import logging -from decimal import Decimal - -from users.models import ( - UserDashou, UserShangjia, UserGuanshi, UserBoss, - RankingRecord -) -from gvsdsdk.models import User - -logger = logging.getLogger(__name__) - -def get_user_nicheng(user, shenfen): - """ - 获取用户昵称 - 规则:打手用打手nicheng,其他用老板nickname - """ - try: - if shenfen == 2: # 打手 - dashou_profile = getattr(user, 'dashou_profile', None) - if dashou_profile and dashou_profile.nicheng: - return dashou_profile.nicheng - else: # 管事或商家 - boss_profile = getattr(user, 'boss_profile', None) - if boss_profile and boss_profile.nickname: - return boss_profile.nickname - - # 默认返回用户ID - return f"用户{user.yonghuid}" - except Exception: - return f"用户{user.yonghuid}" - -@shared_task -def zhuanyi_ribang(): - """ - 转移日榜数据 - 每天23:55执行 - 在清零前将今日数据转移到排行榜表 - """ - try: - # 统计日期(今天) - today = timezone.now().date() - logger.info(f"开始转移日榜数据,日期: {today}") - - records_to_create = [] - - # 1. 转移打手日榜(今日收益 > 0) - dashou_list = UserDashou.objects.filter( - jinrishouyi__gt=0 # 只转移收益大于0的 - ).select_related('user').order_by('-jinrishouyi') - - for i, dashou in enumerate(dashou_list, start=1): - if i > 100: # 最多100条 - break - - user = dashou.user - record = RankingRecord( - zhouqi=1, # 日榜 - tongji_date=today, - shenfen=2, # 打手 - yonghuid=user.yonghuid, - paiming=i, - zhizhi=dashou.jinrishouyi, - nicheng=get_user_nicheng(user, 2), - avatar=user.avatar or '' - ) - records_to_create.append(record) - - # 2. 转移商家日榜(今日流水 > 0) - shangjia_list = UserShangjia.objects.filter( - jinriliushui__gt=0 # 只转移流水大于0的 - ).select_related('user').order_by('-jinriliushui') - - for i, shangjia in enumerate(shangjia_list, start=1): - if i > 100: # 最多100条 - break - - user = shangjia.user - record = RankingRecord( - zhouqi=1, - tongji_date=today, - shenfen=4, # 商家 - yonghuid=user.yonghuid, - paiming=i, - zhizhi=shangjia.jinriliushui, - nicheng=get_user_nicheng(user, 4), - avatar=user.avatar or '' - ) - records_to_create.append(record) - - # 3. 转移管事日榜(今日充值人数 > 0) - guanshi_list = UserGuanshi.objects.filter( - jinrichongzhi__gt=0 # 只转移充值人数大于0的 - ).select_related('user').order_by('-jinrichongzhi') - - for i, guanshi in enumerate(guanshi_list, start=1): - if i > 100: # 最多100条 - break - - user = guanshi.user - record = RankingRecord( - zhouqi=1, - tongji_date=today, - shenfen=3, # 管事 - yonghuid=user.yonghuid, - paiming=i, - zhizhi=Decimal(guanshi.jinrichongzhi), - nicheng=get_user_nicheng(user, 3), - avatar=user.avatar or '' - ) - records_to_create.append(record) - - # 批量保存 - if records_to_create: - with transaction.atomic(): - RankingRecord.objects.bulk_create(records_to_create) - logger.info(f"日榜数据转移完成,保存{len(records_to_create)}条记录") - - result = { - 'success': True, - 'date': today.strftime('%Y-%m-%d'), - 'total_count': len(records_to_create), - } - - return result - - except Exception as e: - logger.error(f"日榜数据转移失败: {str(e)}", exc_info=True) - raise - -@shared_task -def zhuanyi_yuebang(): - """ - 转移月榜数据 - 每月最后一天23:50执行 - 在清零前将本月数据转移到排行榜表 - """ - try: - # 🔥 获取当前时间 - now = timezone.now() - today = now.date() - - # 🔥 判断今天是否是当月的最后一天 - # 方法:计算明天的日期,如果明天的月份和今天不同,那么今天就是最后一天 - tomorrow = today + timedelta(days=1) - if tomorrow.month == today.month: - # 今天不是最后一天,直接返回 - logger.info(f"今天不是当月最后一天({today}),跳过月榜转移") - return { - 'success': True, - 'message': f'今天不是当月最后一天,跳过执行', - 'is_last_day': False, - 'today': today.strftime('%Y-%m-%d'), - } - - logger.info(f"今天是当月最后一天({today}),开始转移月榜数据") - - # 🔥 统计日期为当月第一天(如2024-01-01) - tongji_date = datetime(today.year, today.month, 1).date() - - records_to_create = [] - - # 1. 转移打手月榜(今月收益 > 0) - dashou_list = UserDashou.objects.filter( - jinyueshouyi__gt=0 # 只转移收益大于0的 - ).select_related('user').order_by('-jinyueshouyi') - - for i, dashou in enumerate(dashou_list, start=1): - if i > 100: # 最多100条 - break - - user = dashou.user - record = RankingRecord( - zhouqi=2, # 月榜 - tongji_date=tongji_date, - shenfen=2, # 打手 - yonghuid=user.yonghuid, - paiming=i, - zhizhi=dashou.jinyueshouyi, - nicheng=get_user_nicheng(user, 2), - avatar=user.avatar or '' - ) - records_to_create.append(record) - - # 2. 转移商家月榜(今月流水 > 0) - shangjia_list = UserShangjia.objects.filter( - jinyueliushui__gt=0 # 只转移流水大于0的 - ).select_related('user').order_by('-jinyueliushui') - - for i, shangjia in enumerate(shangjia_list, start=1): - if i > 100: # 最多100条 - break - - user = shangjia.user - record = RankingRecord( - zhouqi=2, - tongji_date=tongji_date, - shenfen=4, # 商家 - yonghuid=user.yonghuid, - paiming=i, - zhizhi=shangjia.jinyueliushui, - nicheng=get_user_nicheng(user, 4), - avatar=user.avatar or '' - ) - records_to_create.append(record) - - # 3. 转移管事月榜(今月充值人数 > 0) - guanshi_list = UserGuanshi.objects.filter( - jinyuechongzhi__gt=0 # 只转移充值人数大于0的 - ).select_related('user').order_by('-jinyuechongzhi') - - for i, guanshi in enumerate(guanshi_list, start=1): - if i > 100: # 最多100条 - break - - user = guanshi.user - record = RankingRecord( - zhouqi=2, - tongji_date=tongji_date, - shenfen=3, # 管事 - yonghuid=user.yonghuid, - paiming=i, - zhizhi=Decimal(guanshi.jinyuechongzhi), - nicheng=get_user_nicheng(user, 3), - avatar=user.avatar or '' - ) - records_to_create.append(record) - - # 批量保存 - if records_to_create: - with transaction.atomic(): - RankingRecord.objects.bulk_create(records_to_create) - logger.info(f"月榜数据转移完成,保存{len(records_to_create)}条记录") - - result = { - 'success': True, - 'month': tongji_date.strftime('%Y-%m'), - 'total_count': len(records_to_create), - 'is_last_day': True, - 'today': today.strftime('%Y-%m-%d'), - } - - return result - - except Exception as e: - logger.error(f"月榜数据转移失败: {str(e)}", exc_info=True) - raise - -@shared_task -def qingli_jiulishuju(): - """ - 清理旧历史数据 - 每月1号凌晨1点执行 - 清理3个月前的排行榜数据,保留最近3个月的数据 - """ - try: - # 计算3个月前的日期 - three_months_ago = timezone.now().date() - timedelta(days=90) - - # 删除旧数据 - deleted_count, _ = RankingRecord.objects.filter( - tongji_date__lt=three_months_ago - ).delete() - - logger.info(f"清理旧历史数据完成,删除{deleted_count}条记录") - - return { - 'success': True, - 'deleted_count': deleted_count, - 'before_date': three_months_ago.strftime('%Y-%m-%d') - } - - except Exception as e: - logger.error(f"清理旧历史数据失败: {str(e)}") +# yonghu/ranking_tasks.py +""" +排行榜数据转移任务 - 生产环境专用 +在清空前将数据转移到排行榜表 +""" + +from celery import shared_task +from django.db import transaction +from django.utils import timezone +from datetime import datetime, timedelta +import logging +from decimal import Decimal + +from users.models import ( + UserDashou, UserShangjia, UserGuanshi, UserBoss, + RankingRecord +) +from gvsdsdk.models import User + +logger = logging.getLogger(__name__) + +def get_user_nicheng(user, shenfen): + """ + 获取用户昵称 + 规则:打手用打手nicheng,其他用老板nickname + """ + try: + if shenfen == 2: # 打手 + dashou_profile = getattr(user, 'DashouProfile', None) + if dashou_profile and dashou_profile.nicheng: + return dashou_profile.nicheng + else: # 管事或商家 + boss_profile = getattr(user, 'BossProfile', None) + if boss_profile and boss_profile.nickname: + return boss_profile.nickname + + # 默认返回用户ID + return f"用户{user.UserUID}" + except Exception: + return f"用户{user.UserUID}" + +@shared_task +def zhuanyi_ribang(): + """ + 转移日榜数据 - 每天23:55执行 + 在清零前将今日数据转移到排行榜表 + """ + try: + # 统计日期(今天) + today = timezone.now().date() + logger.info(f"开始转移日榜数据,日期: {today}") + + records_to_create = [] + + # 1. 转移打手日榜(今日收益 > 0) + dashou_list = UserDashou.objects.filter( + jinrishouyi__gt=0 # 只转移收益大于0的 + ).select_related('user').order_by('-jinrishouyi') + + for i, dashou in enumerate(dashou_list, start=1): + if i > 100: # 最多100条 + break + + user = dashou.user + record = RankingRecord( + zhouqi=1, # 日榜 + tongji_date=today, + shenfen=2, # 打手 + yonghuid=user.UserUID, + paiming=i, + zhizhi=dashou.jinrishouyi, + nicheng=get_user_nicheng(user, 2), + avatar=user.Avatar or '' + ) + records_to_create.append(record) + + # 2. 转移商家日榜(今日流水 > 0) + shangjia_list = UserShangjia.objects.filter( + jinriliushui__gt=0 # 只转移流水大于0的 + ).select_related('user').order_by('-jinriliushui') + + for i, shangjia in enumerate(shangjia_list, start=1): + if i > 100: # 最多100条 + break + + user = shangjia.user + record = RankingRecord( + zhouqi=1, + tongji_date=today, + shenfen=4, # 商家 + yonghuid=user.UserUID, + paiming=i, + zhizhi=shangjia.jinriliushui, + nicheng=get_user_nicheng(user, 4), + avatar=user.Avatar or '' + ) + records_to_create.append(record) + + # 3. 转移管事日榜(今日充值人数 > 0) + guanshi_list = UserGuanshi.objects.filter( + jinrichongzhi__gt=0 # 只转移充值人数大于0的 + ).select_related('user').order_by('-jinrichongzhi') + + for i, guanshi in enumerate(guanshi_list, start=1): + if i > 100: # 最多100条 + break + + user = guanshi.user + record = RankingRecord( + zhouqi=1, + tongji_date=today, + shenfen=3, # 管事 + yonghuid=user.UserUID, + paiming=i, + zhizhi=Decimal(guanshi.jinrichongzhi), + nicheng=get_user_nicheng(user, 3), + avatar=user.Avatar or '' + ) + records_to_create.append(record) + + # 批量保存 + if records_to_create: + with transaction.atomic(): + RankingRecord.objects.bulk_create(records_to_create) + logger.info(f"日榜数据转移完成,保存{len(records_to_create)}条记录") + + result = { + 'success': True, + 'date': today.strftime('%Y-%m-%d'), + 'total_count': len(records_to_create), + } + + return result + + except Exception as e: + logger.error(f"日榜数据转移失败: {str(e)}", exc_info=True) + raise + +@shared_task +def zhuanyi_yuebang(): + """ + 转移月榜数据 - 每月最后一天23:50执行 + 在清零前将本月数据转移到排行榜表 + """ + try: + # 🔥 获取当前时间 + now = timezone.now() + today = now.date() + + # 🔥 判断今天是否是当月的最后一天 + # 方法:计算明天的日期,如果明天的月份和今天不同,那么今天就是最后一天 + tomorrow = today + timedelta(days=1) + if tomorrow.month == today.month: + # 今天不是最后一天,直接返回 + logger.info(f"今天不是当月最后一天({today}),跳过月榜转移") + return { + 'success': True, + 'message': f'今天不是当月最后一天,跳过执行', + 'is_last_day': False, + 'today': today.strftime('%Y-%m-%d'), + } + + logger.info(f"今天是当月最后一天({today}),开始转移月榜数据") + + # 🔥 统计日期为当月第一天(如2024-01-01) + tongji_date = datetime(today.year, today.month, 1).date() + + records_to_create = [] + + # 1. 转移打手月榜(今月收益 > 0) + dashou_list = UserDashou.objects.filter( + jinyueshouyi__gt=0 # 只转移收益大于0的 + ).select_related('user').order_by('-jinyueshouyi') + + for i, dashou in enumerate(dashou_list, start=1): + if i > 100: # 最多100条 + break + + user = dashou.user + record = RankingRecord( + zhouqi=2, # 月榜 + tongji_date=tongji_date, + shenfen=2, # 打手 + yonghuid=user.UserUID, + paiming=i, + zhizhi=dashou.jinyueshouyi, + nicheng=get_user_nicheng(user, 2), + avatar=user.Avatar or '' + ) + records_to_create.append(record) + + # 2. 转移商家月榜(今月流水 > 0) + shangjia_list = UserShangjia.objects.filter( + jinyueliushui__gt=0 # 只转移流水大于0的 + ).select_related('user').order_by('-jinyueliushui') + + for i, shangjia in enumerate(shangjia_list, start=1): + if i > 100: # 最多100条 + break + + user = shangjia.user + record = RankingRecord( + zhouqi=2, + tongji_date=tongji_date, + shenfen=4, # 商家 + yonghuid=user.UserUID, + paiming=i, + zhizhi=shangjia.jinyueliushui, + nicheng=get_user_nicheng(user, 4), + avatar=user.Avatar or '' + ) + records_to_create.append(record) + + # 3. 转移管事月榜(今月充值人数 > 0) + guanshi_list = UserGuanshi.objects.filter( + jinyuechongzhi__gt=0 # 只转移充值人数大于0的 + ).select_related('user').order_by('-jinyuechongzhi') + + for i, guanshi in enumerate(guanshi_list, start=1): + if i > 100: # 最多100条 + break + + user = guanshi.user + record = RankingRecord( + zhouqi=2, + tongji_date=tongji_date, + shenfen=3, # 管事 + yonghuid=user.UserUID, + paiming=i, + zhizhi=Decimal(guanshi.jinyuechongzhi), + nicheng=get_user_nicheng(user, 3), + avatar=user.Avatar or '' + ) + records_to_create.append(record) + + # 批量保存 + if records_to_create: + with transaction.atomic(): + RankingRecord.objects.bulk_create(records_to_create) + logger.info(f"月榜数据转移完成,保存{len(records_to_create)}条记录") + + result = { + 'success': True, + 'month': tongji_date.strftime('%Y-%m'), + 'total_count': len(records_to_create), + 'is_last_day': True, + 'today': today.strftime('%Y-%m-%d'), + } + + return result + + except Exception as e: + logger.error(f"月榜数据转移失败: {str(e)}", exc_info=True) + raise + +@shared_task +def qingli_jiulishuju(): + """ + 清理旧历史数据 - 每月1号凌晨1点执行 + 清理3个月前的排行榜数据,保留最近3个月的数据 + """ + try: + # 计算3个月前的日期 + three_months_ago = timezone.now().date() - timedelta(days=90) + + # 删除旧数据 + deleted_count, _ = RankingRecord.objects.filter( + tongji_date__lt=three_months_ago + ).delete() + + logger.info(f"清理旧历史数据完成,删除{deleted_count}条记录") + + return { + 'success': True, + 'deleted_count': deleted_count, + 'before_date': three_months_ago.strftime('%Y-%m-%d') + } + + except Exception as e: + logger.error(f"清理旧历史数据失败: {str(e)}") raise \ No newline at end of file diff --git a/users/tixian_shenhe_services.py b/users/tixian_shenhe_services.py index 96279b2..83a6b82 100644 --- a/users/tixian_shenhe_services.py +++ b/users/tixian_shenhe_services.py @@ -188,7 +188,7 @@ def validate_withdraw_eligibility(user_main, leixing, jine): 提现申请资格校验(完整基本条件;仅放宽「不限制并行申请笔数」)。 与手动提现 TixianShenqingView 对齐。 """ - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(yonghuid, 2) if not fadan_ok: @@ -314,7 +314,7 @@ def validate_collect_eligibility(user_main, leixing): 打手佣金/押金:罚单、封禁、接单中、积分、会员、订单等; 其他身份:账号状态、罚单/订单等。 """ - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(yonghuid, 2) if not fadan_ok: @@ -650,7 +650,7 @@ def reserve_collect_quota_limits(user_main, leixing, jine, shijidaozhang, shenhe logger.info( '收款限额预占成功 yonghuid=%s leixing=%s shenhe_danhao=%s jine=%s shijidaozhang=%s platform_total=%s', - user_main.yonghuid, leixing, shenhe_danhao, jine, shijidaozhang, platform_stat.total_amount, + user_main.UserUID, leixing, shenhe_danhao, jine, shijidaozhang, platform_stat.total_amount, ) return True, '', '' @@ -691,13 +691,13 @@ def release_collect_quota_reservation(user_main, leixing, jine, shijidaozhang): except Exception as e: logger.error( '回滚收款限额预占失败 yonghuid=%s leixing=%s err=%s', - user_main.yonghuid, leixing, e, exc_info=True, + user_main.UserUID, leixing, e, exc_info=True, ) return logger.info( '收款限额预占已回滚 yonghuid=%s leixing=%s jine=%s shijidaozhang=%s', - user_main.yonghuid, leixing, jine, shijidaozhang, + user_main.UserUID, leixing, jine, shijidaozhang, ) @@ -1371,7 +1371,7 @@ def create_audit_application(user_main, leixing, jine): 不创建打款记录表(TixianAutoRecord 仅在收款时创建) 允许多笔并行申请(不阻塞待收款/审核中单) """ - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID ok, msg, extra = validate_withdraw_eligibility(user_main, leixing, jine) if not ok: @@ -1408,12 +1408,12 @@ def create_audit_application(user_main, leixing, jine): tixian_record = Tixianjilu.query.create( yonghuid=yonghuid, - avatar=user_main.avatar or '', - phone=user_main.phone or '', + avatar=user_main.Avatar or '', + phone=user_main.Phone or '', nicheng=nicheng, leixing=leixing, - zhifu=user_main.zhifu or '', - skzhanghao=user_main.skzhanghao or '', + zhifu=user_main.PaymentQRCode or '', + skzhanghao=user_main.PaymentAccount or '', jine=shijidaozhang, zhuangtai=1, fangshi=1, diff --git a/users/tixian_shenhe_views.py b/users/tixian_shenhe_views.py index 00088db..de96730 100644 --- a/users/tixian_shenhe_views.py +++ b/users/tixian_shenhe_views.py @@ -1,406 +1,406 @@ -# yonghu/tixian_shenhe_views.py -""" -自动提现审核 — 用户侧接口: - ① TixianZddkshApplyView → POST /yonghu/zddksh 提交审核(扣款) - ② process_audit_collect → POST /yonghu/tixiansq 收款(P0:一单到底,先查微信再决定是否新建) -""" -import decimal -import json -import logging -import random -import string -import time - -import requests -from django.conf import settings -from django.db import transaction -from django.utils import timezone -from rest_framework import permissions -from rest_framework.response import Response -from rest_framework.views import APIView - -from utils.redis_lock import acquire_lock, release_lock -from utils.wechat_v3 import build_authorization -from gvsdsdk.fluent import db, func, FQ - -from .models import TixianAutoRecord -from .tixian_shenhe_services import ( - RECONCILE_ALLOW_NEW, - RECONCILE_AUDIT_CLOSED, - RECONCILE_COMPLETED, - RECONCILE_PENDING, - RECONCILE_WAIT_CONFIRM, - check_collect_quota_limits, - create_audit_application, - close_audit_over_limit_refund, - close_audit_wechat_failed, - get_audit_for_collect, - handle_post_transfer_failure, - reconcile_shenhe_wechat_bills, - release_collect_quota_for_record, - reserve_collect_quota_limits, - resolve_collect_context, - validate_collect_amount, - validate_collect_eligibility, -) - -logger = logging.getLogger('yonghu.tixian_shenhe') - - -def generate_tixian_id(): - """生成唯一微信商户打款单号:TX + 时间戳(13位) + 4位随机数""" - timestamp = str(int(time.time() * 1000)) - rand = ''.join(random.choices(string.digits, k=4)) - return f'TX{timestamp}{rand}' - - -def _build_collect_success_response(payload): - return Response({ - 'code': 0, - 'msg': '请确认收款', - 'data': payload, - }) - - -def _is_wx_balance_insufficient(err_code='', err_msg=''): - blob = f'{err_code} {err_msg}'.upper() - return any(k in blob for k in ('NOT_ENOUGH', 'INSUFFICIENT', '余额不足', '资金不足')) - - -def _verify_collect_quota_or_response(user_main, audit, shenhe_danhao): - """收款限额校验;不通过时直接返回 Response""" - ok, msg, limit_kind = check_collect_quota_limits( - user_main, - audit.leixing, - audit.shenqing_jine, - audit.shijidaozhang, - shenhe_danhao, - ) - if not ok: - limit_code = 33 if limit_kind == 'platform' else 400 - logger.warning( - '收款限额校验未通过 yonghuid=%s shenhe_danhao=%s leixing=%s kind=%s msg=%s', - user_main.yonghuid, shenhe_danhao, audit.leixing, limit_kind, msg, - ) - return Response({'code': limit_code, 'msg': msg}) - return None - - -def _mark_auto_record_failed(tixian_id, fail_reason): - """微信明确失败时标记打款记录终态,避免后续一直被当成「处理中」""" - TixianAutoRecord.query.filter(tixian_id=tixian_id, zhuangtai=0).update( - zhuangtai=2, - fail_reason=(fail_reason or '打款失败')[:500], - update_time=timezone.now(), - ) - - -def _build_wx_transfer_body(wx_cfg, tixian_id, openid, yonghuid, leixing, shijidaozhang): - transfer_scene_id = wx_cfg['TRANSFER_SCENE_ID'] - role_map = {1: '打手', 2: '管事', 3: '组长', 4: '考核官', 5: '打手', 6: '商家'} - desc_map = {1: '佣金提现', 2: '分红提现', 3: '分红提现', 4: '分佣提现', 5: '押金提现', 6: '余额提现'} - transfer_scene_report_infos = [] - if transfer_scene_id == '1005': - transfer_scene_report_infos = [ - {'info_type': '岗位类型', 'info_content': role_map.get(leixing, '用户')}, - {'info_type': '报酬说明', 'info_content': desc_map.get(leixing, '提现')}, - ] - elif transfer_scene_id == '1009': - transfer_scene_report_infos = [ - {'info_type': '采购商品名称', 'info_content': '平台服务'}, - ] - return { - 'appid': wx_cfg['APPID'], - 'out_bill_no': tixian_id, - 'transfer_scene_id': transfer_scene_id, - 'openid': openid, - 'transfer_amount': int(shijidaozhang * 100), - 'transfer_remark': f'平台提现-{yonghuid}', - 'transfer_scene_report_infos': transfer_scene_report_infos, - } - - -def _call_wechat_transfer(body, wx_cfg): - url = 'https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills' - body_json = json.dumps(body, separators=(',', ':')) - auth = build_authorization('POST', url, body_json) - headers = { - 'Authorization': auth, - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'User-Agent': 'Mozilla/5.0', - } - resp = requests.post(url, data=body_json, headers=headers, timeout=15) - try: - wx_result = resp.json() - except ValueError: - wx_result = {} - return resp, wx_result - - -class TixianZddkshApplyView(APIView): - """POST /yonghu/zddksh 提交审核申请(扣款,不创建 TixianAutoRecord)""" - permission_classes = [permissions.IsAuthenticated] - - def post(self, request): - user_main = request.user - lock_key = f'tixian_audit_apply:{user_main.yonghuid}' - identifier = acquire_lock(lock_key, timeout=10) - if not identifier: - return Response({'code': 429, 'msg': '操作频繁,请稍后重试'}) - - try: - leixing = request.data.get('leixing') - jine_str = request.data.get('jine') - if leixing is None or jine_str is None: - return Response({'code': 1, 'msg': '缺少参数: leixing 或 jine'}) - - try: - leixing = int(leixing) - jine = decimal.Decimal(str(jine_str)) - except (ValueError, decimal.InvalidOperation): - return Response({'code': 2, 'msg': '参数格式错误'}) - - if leixing not in [1, 2, 3, 4, 5, 6]: - return Response({'code': 3, 'msg': '提现类型无效'}) - if jine <= decimal.Decimal('0.1'): - return Response({'code': 4, 'msg': '提现金额必须大于0.1'}) - - try: - data = create_audit_application(user_main, leixing, jine) - except ValueError as e: - return Response({'code': 400, 'msg': str(e)}) - except Exception as e: - logger.error(f'提现审核申请异常: {e}', exc_info=True) - return Response({'code': 99, 'msg': '提现申请失败,请稍后重试'}) - - return Response({'code': 0, 'msg': '提现申请已提交,请等待审核', 'data': data}) - - finally: - release_lock(lock_key, identifier) - - -def process_audit_collect(request): - """ - POST /yonghu/tixiansq 用户收款(P0 防多提) - - 主路径:tixianjilu_id → Tixianjilu → TixianShenheJilu(状态6) - 若 TixianAutoRecord 已有 tixian_id → 只问微信,非终态绝不新建 - 仅微信 FAIL/CANCELLED 终态后才允许新建打款记录 - """ - user_main = request.user - yonghuid = user_main.yonghuid - tixianjilu_id = request.data.get('tixianjilu_id') or request.data.get('id') - shenhe_danhao = (request.data.get('shenhe_danhao') or '').strip() - shenhe_jilu_id = request.data.get('shenhe_jilu_id') - - logger.info( - '收款请求 yonghuid=%s tixianjilu_id=%s shenhe_jilu_id=%s shenhe_danhao=%s', - yonghuid, tixianjilu_id, shenhe_jilu_id, shenhe_danhao, - ) - - try: - if not user_main.openid: - return Response({'code': 10, 'msg': '用户未绑定微信,无法收款'}) - - wx_cfg = settings.WECHAT_PAY_V3_CONFIG - if not wx_cfg.get('APPID') or not wx_cfg.get('MCHID') or not wx_cfg.get('TRANSFER_SCENE_ID'): - logger.error('微信打款配置不完整 APPID=%s MCHID=%s', wx_cfg.get('APPID'), wx_cfg.get('MCHID')) - return Response({'code': 11, 'msg': '系统配置异常,请联系客服'}) - - ctx, err = resolve_collect_context( - yonghuid, - tixianjilu_id=tixianjilu_id, - shenhe_danhao=shenhe_danhao, - shenhe_jilu_id=shenhe_jilu_id, - ) - if not ctx: - return Response({'code': 400, 'msg': err}) - - audit = ctx['audit'] - shenhe_danhao = ctx['shenhe_danhao'] - tixianjilu_id_val = ctx['jilu'].id if ctx.get('jilu') else audit.tixianjilu_id - leixing = audit.leixing - - amount_ok, amount_msg = validate_collect_amount(audit) - if not amount_ok: - with transaction.atomic(): - close_audit_over_limit_refund(audit, None, amount_msg) - return Response({ - 'code': 400, - 'msg': f'{amount_msg},可到账金额已退回余额(手续费不退),请重新申请', - }) - - # 收款前严格资格校验:不通过仅返回错误,审核单保持待收款(6),不退款 - collect_ok, collect_msg = validate_collect_eligibility(user_main, leixing) - if not collect_ok: - logger.warning( - '收款校验未通过 yonghuid=%s shenhe_danhao=%s leixing=%s msg=%s', - yonghuid, shenhe_danhao, leixing, collect_msg, - ) - return Response({'code': 400, 'msg': collect_msg}) - - quota_resp = _verify_collect_quota_or_response(user_main, audit, shenhe_danhao) - if quota_resp: - return quota_resp - - lock_key = f'tixian_collect:{shenhe_danhao}' - identifier = acquire_lock(lock_key, timeout=15) - if not identifier: - return Response({'code': 429, 'msg': '操作频繁,请稍后重试'}) - - tixian_id = None - quota_reserved = False - try: - action, payload = reconcile_shenhe_wechat_bills(shenhe_danhao) - if action == RECONCILE_COMPLETED: - return Response({'code': 8, 'msg': payload.get('msg', '该提现已完成,请勿重复操作')}) - if action == RECONCILE_AUDIT_CLOSED: - return Response({ - 'code': 400, - 'msg': payload.get('msg', '该笔提现已关闭,请重新申请'), - }) - if action == RECONCILE_WAIT_CONFIRM: - quota_resp = _verify_collect_quota_or_response(user_main, audit, shenhe_danhao) - if quota_resp: - return quota_resp - return _build_collect_success_response(payload) - if action == RECONCILE_PENDING: - msg = payload.get('msg') if isinstance(payload, dict) else payload - return Response({'code': 12, 'msg': msg or '有收款处理中,请稍后再试'}) - - if action != RECONCILE_ALLOW_NEW: - return Response({'code': 12, 'msg': '收款处理中,请稍后再试'}) - - with transaction.atomic(): - audit, err = get_audit_for_collect( - shenhe_danhao, yonghuid, audit.id, - ) - if not audit: - return Response({'code': 400, 'msg': err}) - - # 金额一律用审核表已落库数据(申请时扣款+算费),收款不再重算 - jine = audit.shenqing_jine - shouxufei = audit.shouxufei - shijidaozhang = audit.shijidaozhang - feilv = audit.feilv - - ok, msg, limit_kind = reserve_collect_quota_limits( - user_main, leixing, jine, shijidaozhang, shenhe_danhao, - ) - if not ok: - limit_code = 33 if limit_kind == 'platform' else 400 - return Response({'code': limit_code, 'msg': msg}) - - quota_reserved = True - - tixian_id = generate_tixian_id() - TixianAutoRecord.query.create( - tixian_id=tixian_id, - yonghuid=yonghuid, - leixing=leixing, - jine=jine, - shouxufei=shouxufei, - shijidaozhang=shijidaozhang, - feilv=feilv, - zhuangtai=0, - shenhe_danhao=shenhe_danhao, - ) - audit.tixian_auto_id = tixian_id - audit.save(update_fields=['tixian_auto_id', 'update_time']) - - body = _build_wx_transfer_body( - wx_cfg, tixian_id, user_main.openid, yonghuid, leixing, shijidaozhang, - ) - - try: - resp, wx_result = _call_wechat_transfer(body, wx_cfg) - - if resp.status_code == 200 and wx_result.get('state') == 'WAIT_USER_CONFIRM': - package_info = wx_result.get('package_info') - auto_record = TixianAutoRecord.query.get(tixian_id=tixian_id) - auto_record.wechat_package = json.dumps(package_info, ensure_ascii=False) - auto_record.save(update_fields=['wechat_package']) - - return _build_collect_success_response({ - 'tixian_id': tixian_id, - 'package_info': package_info, - 'shouxufei': str(shouxufei), - 'shijidaozhang': str(shijidaozhang), - 'current_rate': str(feilv), - 'mch_id': wx_cfg['MCHID'], - 'shenhe_danhao': shenhe_danhao, - 'tixianjilu_id': tixianjilu_id_val, - }) - - err_code = wx_result.get('code', '') - err_msg = wx_result.get('message', wx_result.get('detail', '微信接口调用失败')) - logger.error( - '微信发起转账未进入待确认: bill=%s code=%s msg=%s HTTP%s', - tixian_id, err_code, err_msg, resp.status_code, - ) - - is_balance_issue = _is_wx_balance_insufficient(err_code, err_msg) - if is_balance_issue: - _mark_auto_record_failed(tixian_id, err_msg) - if quota_reserved: - release_collect_quota_for_record( - TixianAutoRecord.query.get(tixian_id=tixian_id), - ) - quota_reserved = False - return Response({ - 'code': 99, - 'msg': '运营账户资金不足,需等管理员充值后才能提现', - }) - - post_action, post_payload = handle_post_transfer_failure( - tixian_id, shenhe_danhao, err_code, err_msg, - ) - if post_action == RECONCILE_WAIT_CONFIRM: - quota_resp = _verify_collect_quota_or_response(user_main, audit, shenhe_danhao) - if quota_resp: - return quota_resp - return _build_collect_success_response(post_payload) - if post_action == RECONCILE_COMPLETED: - return Response({'code': 8, 'msg': '该提现已完成,请勿重复操作'}) - if post_action == RECONCILE_AUDIT_CLOSED: - return Response({ - 'code': 400, - 'msg': post_payload.get('msg', '该笔提现已关闭,请重新申请'), - }) - if post_action == RECONCILE_PENDING: - pending_msg = ( - post_payload.get('msg', post_payload) - if isinstance(post_payload, dict) else post_payload - ) - return Response({'code': 12, 'msg': pending_msg or '有收款处理中,请稍后再试'}) - - user_msg = err_msg or '微信收款发起失败,请稍后重试' - _mark_auto_record_failed(tixian_id, user_msg) - if quota_reserved: - release_collect_quota_for_record( - TixianAutoRecord.query.get(tixian_id=tixian_id), - ) - quota_reserved = False - return Response({'code': 99, 'msg': user_msg}) - - except requests.RequestException as e: - logger.error( - '微信打款网络异常(保持待查单): bill=%s err=%s', - tixian_id, e, exc_info=True, - ) - return Response({ - 'code': 12, - 'msg': '收款请求已提交,系统正在核对状态,请稍后再试,切勿重复点击', - }) - - finally: - release_lock(lock_key, identifier) - - except Exception as e: - logger.error( - '收款接口未捕获异常 yonghuid=%s tixianjilu_id=%s shenhe_danhao=%s err=%s', - yonghuid, tixianjilu_id, shenhe_danhao, e, - exc_info=True, - ) - return Response({'code': 99, 'msg': '系统繁忙,请稍后重试'}) +# yonghu/tixian_shenhe_views.py +""" +自动提现审核 — 用户侧接口: + ① TixianZddkshApplyView → POST /yonghu/zddksh 提交审核(扣款) + ② process_audit_collect → POST /yonghu/tixiansq 收款(P0:一单到底,先查微信再决定是否新建) +""" +import decimal +import json +import logging +import random +import string +import time + +import requests +from django.conf import settings +from django.db import transaction +from django.utils import timezone +from rest_framework import permissions +from rest_framework.response import Response +from rest_framework.views import APIView + +from utils.redis_lock import acquire_lock, release_lock +from utils.wechat_v3 import build_authorization +from gvsdsdk.fluent import db, func, FQ + +from .models import TixianAutoRecord +from .tixian_shenhe_services import ( + RECONCILE_ALLOW_NEW, + RECONCILE_AUDIT_CLOSED, + RECONCILE_COMPLETED, + RECONCILE_PENDING, + RECONCILE_WAIT_CONFIRM, + check_collect_quota_limits, + create_audit_application, + close_audit_over_limit_refund, + close_audit_wechat_failed, + get_audit_for_collect, + handle_post_transfer_failure, + reconcile_shenhe_wechat_bills, + release_collect_quota_for_record, + reserve_collect_quota_limits, + resolve_collect_context, + validate_collect_amount, + validate_collect_eligibility, +) + +logger = logging.getLogger('yonghu.tixian_shenhe') + + +def generate_tixian_id(): + """生成唯一微信商户打款单号:TX + 时间戳(13位) + 4位随机数""" + timestamp = str(int(time.time() * 1000)) + rand = ''.join(random.choices(string.digits, k=4)) + return f'TX{timestamp}{rand}' + + +def _build_collect_success_response(payload): + return Response({ + 'code': 0, + 'msg': '请确认收款', + 'data': payload, + }) + + +def _is_wx_balance_insufficient(err_code='', err_msg=''): + blob = f'{err_code} {err_msg}'.upper() + return any(k in blob for k in ('NOT_ENOUGH', 'INSUFFICIENT', '余额不足', '资金不足')) + + +def _verify_collect_quota_or_response(user_main, audit, shenhe_danhao): + """收款限额校验;不通过时直接返回 Response""" + ok, msg, limit_kind = check_collect_quota_limits( + user_main, + audit.leixing, + audit.shenqing_jine, + audit.shijidaozhang, + shenhe_danhao, + ) + if not ok: + limit_code = 33 if limit_kind == 'platform' else 400 + logger.warning( + '收款限额校验未通过 yonghuid=%s shenhe_danhao=%s leixing=%s kind=%s msg=%s', + user_main.UserUID, shenhe_danhao, audit.leixing, limit_kind, msg, + ) + return Response({'code': limit_code, 'msg': msg}) + return None + + +def _mark_auto_record_failed(tixian_id, fail_reason): + """微信明确失败时标记打款记录终态,避免后续一直被当成「处理中」""" + TixianAutoRecord.query.filter(tixian_id=tixian_id, zhuangtai=0).update( + zhuangtai=2, + fail_reason=(fail_reason or '打款失败')[:500], + update_time=timezone.now(), + ) + + +def _build_wx_transfer_body(wx_cfg, tixian_id, openid, yonghuid, leixing, shijidaozhang): + transfer_scene_id = wx_cfg['TRANSFER_SCENE_ID'] + role_map = {1: '打手', 2: '管事', 3: '组长', 4: '考核官', 5: '打手', 6: '商家'} + desc_map = {1: '佣金提现', 2: '分红提现', 3: '分红提现', 4: '分佣提现', 5: '押金提现', 6: '余额提现'} + transfer_scene_report_infos = [] + if transfer_scene_id == '1005': + transfer_scene_report_infos = [ + {'info_type': '岗位类型', 'info_content': role_map.get(leixing, '用户')}, + {'info_type': '报酬说明', 'info_content': desc_map.get(leixing, '提现')}, + ] + elif transfer_scene_id == '1009': + transfer_scene_report_infos = [ + {'info_type': '采购商品名称', 'info_content': '平台服务'}, + ] + return { + 'appid': wx_cfg['APPID'], + 'out_bill_no': tixian_id, + 'transfer_scene_id': transfer_scene_id, + 'openid': openid, + 'transfer_amount': int(shijidaozhang * 100), + 'transfer_remark': f'平台提现-{yonghuid}', + 'transfer_scene_report_infos': transfer_scene_report_infos, + } + + +def _call_wechat_transfer(body, wx_cfg): + url = 'https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills' + body_json = json.dumps(body, separators=(',', ':')) + auth = build_authorization('POST', url, body_json) + headers = { + 'Authorization': auth, + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'Mozilla/5.0', + } + resp = requests.post(url, data=body_json, headers=headers, timeout=15) + try: + wx_result = resp.json() + except ValueError: + wx_result = {} + return resp, wx_result + + +class TixianZddkshApplyView(APIView): + """POST /yonghu/zddksh 提交审核申请(扣款,不创建 TixianAutoRecord)""" + permission_classes = [permissions.IsAuthenticated] + + def post(self, request): + user_main = request.user + lock_key = f'tixian_audit_apply:{user_main.UserUID}' + identifier = acquire_lock(lock_key, timeout=10) + if not identifier: + return Response({'code': 429, 'msg': '操作频繁,请稍后重试'}) + + try: + leixing = request.data.get('leixing') + jine_str = request.data.get('jine') + if leixing is None or jine_str is None: + return Response({'code': 1, 'msg': '缺少参数: leixing 或 jine'}) + + try: + leixing = int(leixing) + jine = decimal.Decimal(str(jine_str)) + except (ValueError, decimal.InvalidOperation): + return Response({'code': 2, 'msg': '参数格式错误'}) + + if leixing not in [1, 2, 3, 4, 5, 6]: + return Response({'code': 3, 'msg': '提现类型无效'}) + if jine <= decimal.Decimal('0.1'): + return Response({'code': 4, 'msg': '提现金额必须大于0.1'}) + + try: + data = create_audit_application(user_main, leixing, jine) + except ValueError as e: + return Response({'code': 400, 'msg': str(e)}) + except Exception as e: + logger.error(f'提现审核申请异常: {e}', exc_info=True) + return Response({'code': 99, 'msg': '提现申请失败,请稍后重试'}) + + return Response({'code': 0, 'msg': '提现申请已提交,请等待审核', 'data': data}) + + finally: + release_lock(lock_key, identifier) + + +def process_audit_collect(request): + """ + POST /yonghu/tixiansq 用户收款(P0 防多提) + + 主路径:tixianjilu_id → Tixianjilu → TixianShenheJilu(状态6) + 若 TixianAutoRecord 已有 tixian_id → 只问微信,非终态绝不新建 + 仅微信 FAIL/CANCELLED 终态后才允许新建打款记录 + """ + user_main = request.user + yonghuid = user_main.UserUID + tixianjilu_id = request.data.get('tixianjilu_id') or request.data.get('id') + shenhe_danhao = (request.data.get('shenhe_danhao') or '').strip() + shenhe_jilu_id = request.data.get('shenhe_jilu_id') + + logger.info( + '收款请求 yonghuid=%s tixianjilu_id=%s shenhe_jilu_id=%s shenhe_danhao=%s', + yonghuid, tixianjilu_id, shenhe_jilu_id, shenhe_danhao, + ) + + try: + if not user_main.OpenID: + return Response({'code': 10, 'msg': '用户未绑定微信,无法收款'}) + + wx_cfg = settings.WECHAT_PAY_V3_CONFIG + if not wx_cfg.get('APPID') or not wx_cfg.get('MCHID') or not wx_cfg.get('TRANSFER_SCENE_ID'): + logger.error('微信打款配置不完整 APPID=%s MCHID=%s', wx_cfg.get('APPID'), wx_cfg.get('MCHID')) + return Response({'code': 11, 'msg': '系统配置异常,请联系客服'}) + + ctx, err = resolve_collect_context( + yonghuid, + tixianjilu_id=tixianjilu_id, + shenhe_danhao=shenhe_danhao, + shenhe_jilu_id=shenhe_jilu_id, + ) + if not ctx: + return Response({'code': 400, 'msg': err}) + + audit = ctx['audit'] + shenhe_danhao = ctx['shenhe_danhao'] + tixianjilu_id_val = ctx['jilu'].id if ctx.get('jilu') else audit.tixianjilu_id + leixing = audit.leixing + + amount_ok, amount_msg = validate_collect_amount(audit) + if not amount_ok: + with transaction.atomic(): + close_audit_over_limit_refund(audit, None, amount_msg) + return Response({ + 'code': 400, + 'msg': f'{amount_msg},可到账金额已退回余额(手续费不退),请重新申请', + }) + + # 收款前严格资格校验:不通过仅返回错误,审核单保持待收款(6),不退款 + collect_ok, collect_msg = validate_collect_eligibility(user_main, leixing) + if not collect_ok: + logger.warning( + '收款校验未通过 yonghuid=%s shenhe_danhao=%s leixing=%s msg=%s', + yonghuid, shenhe_danhao, leixing, collect_msg, + ) + return Response({'code': 400, 'msg': collect_msg}) + + quota_resp = _verify_collect_quota_or_response(user_main, audit, shenhe_danhao) + if quota_resp: + return quota_resp + + lock_key = f'tixian_collect:{shenhe_danhao}' + identifier = acquire_lock(lock_key, timeout=15) + if not identifier: + return Response({'code': 429, 'msg': '操作频繁,请稍后重试'}) + + tixian_id = None + quota_reserved = False + try: + action, payload = reconcile_shenhe_wechat_bills(shenhe_danhao) + if action == RECONCILE_COMPLETED: + return Response({'code': 8, 'msg': payload.get('msg', '该提现已完成,请勿重复操作')}) + if action == RECONCILE_AUDIT_CLOSED: + return Response({ + 'code': 400, + 'msg': payload.get('msg', '该笔提现已关闭,请重新申请'), + }) + if action == RECONCILE_WAIT_CONFIRM: + quota_resp = _verify_collect_quota_or_response(user_main, audit, shenhe_danhao) + if quota_resp: + return quota_resp + return _build_collect_success_response(payload) + if action == RECONCILE_PENDING: + msg = payload.get('msg') if isinstance(payload, dict) else payload + return Response({'code': 12, 'msg': msg or '有收款处理中,请稍后再试'}) + + if action != RECONCILE_ALLOW_NEW: + return Response({'code': 12, 'msg': '收款处理中,请稍后再试'}) + + with transaction.atomic(): + audit, err = get_audit_for_collect( + shenhe_danhao, yonghuid, audit.id, + ) + if not audit: + return Response({'code': 400, 'msg': err}) + + # 金额一律用审核表已落库数据(申请时扣款+算费),收款不再重算 + jine = audit.shenqing_jine + shouxufei = audit.shouxufei + shijidaozhang = audit.shijidaozhang + feilv = audit.feilv + + ok, msg, limit_kind = reserve_collect_quota_limits( + user_main, leixing, jine, shijidaozhang, shenhe_danhao, + ) + if not ok: + limit_code = 33 if limit_kind == 'platform' else 400 + return Response({'code': limit_code, 'msg': msg}) + + quota_reserved = True + + tixian_id = generate_tixian_id() + TixianAutoRecord.query.create( + tixian_id=tixian_id, + yonghuid=yonghuid, + leixing=leixing, + jine=jine, + shouxufei=shouxufei, + shijidaozhang=shijidaozhang, + feilv=feilv, + zhuangtai=0, + shenhe_danhao=shenhe_danhao, + ) + audit.tixian_auto_id = tixian_id + audit.save(update_fields=['tixian_auto_id', 'update_time']) + + body = _build_wx_transfer_body( + wx_cfg, tixian_id, user_main.OpenID, yonghuid, leixing, shijidaozhang, + ) + + try: + resp, wx_result = _call_wechat_transfer(body, wx_cfg) + + if resp.status_code == 200 and wx_result.get('state') == 'WAIT_USER_CONFIRM': + package_info = wx_result.get('package_info') + auto_record = TixianAutoRecord.query.get(tixian_id=tixian_id) + auto_record.wechat_package = json.dumps(package_info, ensure_ascii=False) + auto_record.save(update_fields=['wechat_package']) + + return _build_collect_success_response({ + 'tixian_id': tixian_id, + 'package_info': package_info, + 'shouxufei': str(shouxufei), + 'shijidaozhang': str(shijidaozhang), + 'current_rate': str(feilv), + 'mch_id': wx_cfg['MCHID'], + 'shenhe_danhao': shenhe_danhao, + 'tixianjilu_id': tixianjilu_id_val, + }) + + err_code = wx_result.get('code', '') + err_msg = wx_result.get('message', wx_result.get('detail', '微信接口调用失败')) + logger.error( + '微信发起转账未进入待确认: bill=%s code=%s msg=%s HTTP%s', + tixian_id, err_code, err_msg, resp.status_code, + ) + + is_balance_issue = _is_wx_balance_insufficient(err_code, err_msg) + if is_balance_issue: + _mark_auto_record_failed(tixian_id, err_msg) + if quota_reserved: + release_collect_quota_for_record( + TixianAutoRecord.query.get(tixian_id=tixian_id), + ) + quota_reserved = False + return Response({ + 'code': 99, + 'msg': '运营账户资金不足,需等管理员充值后才能提现', + }) + + post_action, post_payload = handle_post_transfer_failure( + tixian_id, shenhe_danhao, err_code, err_msg, + ) + if post_action == RECONCILE_WAIT_CONFIRM: + quota_resp = _verify_collect_quota_or_response(user_main, audit, shenhe_danhao) + if quota_resp: + return quota_resp + return _build_collect_success_response(post_payload) + if post_action == RECONCILE_COMPLETED: + return Response({'code': 8, 'msg': '该提现已完成,请勿重复操作'}) + if post_action == RECONCILE_AUDIT_CLOSED: + return Response({ + 'code': 400, + 'msg': post_payload.get('msg', '该笔提现已关闭,请重新申请'), + }) + if post_action == RECONCILE_PENDING: + pending_msg = ( + post_payload.get('msg', post_payload) + if isinstance(post_payload, dict) else post_payload + ) + return Response({'code': 12, 'msg': pending_msg or '有收款处理中,请稍后再试'}) + + user_msg = err_msg or '微信收款发起失败,请稍后重试' + _mark_auto_record_failed(tixian_id, user_msg) + if quota_reserved: + release_collect_quota_for_record( + TixianAutoRecord.query.get(tixian_id=tixian_id), + ) + quota_reserved = False + return Response({'code': 99, 'msg': user_msg}) + + except requests.RequestException as e: + logger.error( + '微信打款网络异常(保持待查单): bill=%s err=%s', + tixian_id, e, exc_info=True, + ) + return Response({ + 'code': 12, + 'msg': '收款请求已提交,系统正在核对状态,请稍后再试,切勿重复点击', + }) + + finally: + release_lock(lock_key, identifier) + + except Exception as e: + logger.error( + '收款接口未捕获异常 yonghuid=%s tixianjilu_id=%s shenhe_danhao=%s err=%s', + yonghuid, tixianjilu_id, shenhe_danhao, e, + exc_info=True, + ) + return Response({'code': 99, 'msg': '系统繁忙,请稍后重试'}) diff --git a/users/views.py b/users/views.py index bd0bb00..3cc39c2 100644 --- a/users/views.py +++ b/users/views.py @@ -592,7 +592,7 @@ class UserInfoUpdateView(APIView): url = self._upload_avatar(user, av) if url: result['touxiang'] = url - user.avatar = url + user.Avatar = url else: return Response({'code': 2, 'msg': '头像上传失败', 'data': None}, status=500) @@ -611,8 +611,8 @@ class UserInfoUpdateView(APIView): phone = self._get_phone_safe(code) if phone is None: return Response({'code': 6, 'msg': '手机号获取失败,请重新授权', 'data': None}, status=400) - user.phone = phone - user.shoujihao_renzheng = True + user.Phone = phone + user.PhoneVerified = True result['phone'] = phone if not result: @@ -690,9 +690,9 @@ class UserInfoUpdateView(APIView): def _upload_avatar(self, user, file): try: - uid = user.yonghuid + uid = user.UserUID if not uid: return None - old = user.avatar + old = user.Avatar if old: delete_from_oss(old) ext = os.path.splitext(file.name)[1] or '.jpg' name = f"{uuid.uuid4().hex}{ext}" @@ -718,11 +718,11 @@ class DashouXinxiAPIView(APIView): try: # 1. 获取当前登录用户 current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID # 2. 查询打手扩展表 try: - dashou_profile = current_user.dashou_profile + dashou_profile = current_user.DashouProfile except UserDashou.DoesNotExist: return Response({ 'code': 400, @@ -807,7 +807,7 @@ class HuoQuYaoQingRenView(APIView): def post(self, request): try: current_user = request.user # 当前登录用户 - yonghuid = current_user.yonghuid # 7位用户ID + yonghuid = current_user.UserUID # 7位用户ID # 1. 校验打手身份及账号状态(只检查账号状态,不检查接单状态) try: @@ -837,7 +837,7 @@ class HuoQuYaoQingRenView(APIView): # 3. 查询邀请人主表(头像) try: inviter_main = User.query.get(UserUID=yaoqingren_id) - touxiang = inviter_main.avatar or '' + touxiang = inviter_main.Avatar or '' except User.DoesNotExist: return Response({ 'code': 404, @@ -919,13 +919,13 @@ class ZaixianZhuangtaiAPIView(APIView): # 2. 获取当前登录用户 current_user = request.user - yonghuid = current_user.yonghuid + yonghuid = current_user.UserUID # 3. 查询打手扩展表 try: - dashou_profile = current_user.dashou_profile + dashou_profile = current_user.DashouProfile except UserDashou.DoesNotExist: return Response({ @@ -1004,7 +1004,7 @@ class GuanshiXinxiView(APIView): # 🔥【新增】查询已充值打手数量(从分红表中统计) # Gsfenhong 表中 guanshi 字段存储管事ID(yonghuid) - yichongzhi_count = Gsfenhong.query.filter(guanshi=current_user.yonghuid).count() + yichongzhi_count = Gsfenhong.query.filter(guanshi=current_user.UserUID).count() # 返回管事信息 return Response({ @@ -1051,7 +1051,7 @@ class ShangJiaXinXiView(APIView): def post(self, request): try: # 获取商家扩展资料 - shangjia = request.user.shop_profile + shangjia = request.user.ShopProfile except ObjectDoesNotExist: return Response({ 'code': 4001, @@ -1060,7 +1060,7 @@ class ShangJiaXinXiView(APIView): }, status=status.HTTP_200_OK) user = request.user - yonghuid = user.yonghuid + yonghuid = user.UserUID today = timezone.now().date() this_month_start = today.replace(day=1) @@ -1150,14 +1150,14 @@ class BossBiaoshiView(APIView): user = request.user # 2. 获取yonghuid(确保用户已登录) - if not user or not hasattr(user, 'yonghuid'): + if not user or not hasattr(user, 'UserUID'): return Response({ 'code': 401, 'msg': '用户未认证或用户信息不完整' }, status=401) # 3. 生成标识:Boss + yonghuid(与前端约定) - boss_biaoshi = f"Boss{user.yonghuid}" + boss_biaoshi = f"Boss{user.UserUID}" # 4. 返回给前端 return Response({ @@ -1165,8 +1165,8 @@ class BossBiaoshiView(APIView): 'msg': '获取成功', 'data': { 'boss_biaoshi': boss_biaoshi, - 'yonghuid': user.yonghuid, - 'user_type': user.user_type + 'yonghuid': user.UserUID, + 'user_type': user.UserType } }) @@ -1198,7 +1198,7 @@ class YaoqingmaView(APIView): # 1. 使用反向查询获取管事扩展信息 (性能最优方式) try: - guanshi_profile = current_user.guanshi_profile + guanshi_profile = current_user.GuanshiProfile except UserGuanshi.DoesNotExist: # 用户不是管事,没有扩展表记录 return Response({ @@ -1231,7 +1231,7 @@ class YaoqingmaView(APIView): # 4. 生成新的邀请码 (使用用户ID字符串) # 注意: 这里使用yonghuid,不是主键id,如你要求的"123456"这种形式 - yonghuid_str = str(current_user.yonghuid) + yonghuid_str = str(current_user.UserUID) new_yaoqingma = CreateInvitationCode(yonghuid_str) # 5. 验证生成的邀请码格式 @@ -1305,7 +1305,7 @@ class DashouZhuceView(APIView): # 2. 检查用户是否已是打手 (使用反向查询,性能最优) try: - dashou_profile = current_user.dashou_profile + dashou_profile = current_user.DashouProfile # 用户已是打手,直接返回现有信息 return self.fanhuiXianyouDashouXinxi(dashou_profile, current_user) except UserDashou.DoesNotExist: @@ -1329,7 +1329,7 @@ class DashouZhuceView(APIView): try: with transaction.atomic(): # 获取管事的用户ID (yonghuid) - guanshi_yonghuid = guanshi_profile.user.yonghuid + guanshi_yonghuid = guanshi_profile.user.UserUID # 5.1 创建打手扩展表 dashou_profile = UserDashou.query.create( @@ -1342,7 +1342,7 @@ class DashouZhuceView(APIView): # 5.2 创建商家扩展表 try: - boss_profile = current_user.boss_profile + boss_profile = current_user.BossProfile if boss_profile.nickname: boss_nickname = boss_profile.nickname except: @@ -1352,7 +1352,7 @@ class DashouZhuceView(APIView): # 5.3 创建管事扩展表 # 5.4 更新主表的用户类型 (如果需要,可以设为'dashou',或保持原逻辑) - # current_user.user_type = 'dashou' # 根据你的业务逻辑决定 + # current_user.UserType = 'dashou' # 根据你的业务逻辑决定 # current_user.save() # 5.5 更新原管事的邀请数量 (使用行锁防止并发问题) @@ -1422,7 +1422,7 @@ class DashouZhuceView(APIView): # 使用当前用户的yonghuid进行查询 huiyuan_goumai_list = [] if not is_new: # 如果是新用户,直接返回空列表,无需查询 - goumai_records = Huiyuangoumai.query.filter(yonghu_id=current_user.yonghuid) + goumai_records = Huiyuangoumai.query.filter(yonghu_id=current_user.UserUID) for record in goumai_records: # 调用方法检查是否到期,此方法内部会更新状态 shifou_daoqi = record.jiance_shifou_daoqi() @@ -1461,7 +1461,7 @@ class GuanshiYaoqingDashouListView(APIView): def post(self, request): try: current_user = request.user - guanshi_yonghuid = current_user.yonghuid + guanshi_yonghuid = current_user.UserUID # 1. 分页参数 page = int(request.data.get('page', 1)) @@ -1529,11 +1529,11 @@ class GuanshiYaoqingDashouListView(APIView): for dashou in page_obj: user_main = dashou.user dashou_list.append({ - 'uid': user_main.yonghuid, + 'uid': user_main.UserUID, 'nicheng': dashou.nicheng or '打手', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, - 'touxiang': user_main.avatar or '', + 'touxiang': user_main.Avatar or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, 'tuikuanliang': dashou.tuikuanliang, @@ -1542,7 +1542,7 @@ class GuanshiYaoqingDashouListView(APIView): # 8. 获取管事邀请总数(来自管事扩展表) try: - yaoqingzongshu = current_user.guanshi_profile.yaogingshuliang + yaoqingzongshu = current_user.GuanshiProfile.yaogingshuliang except Exception: yaoqingzongshu = total_all @@ -1598,9 +1598,9 @@ class TixianXinxiHuoquView(APIView): # 构建返回数据 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 '', + 'txdianhua': user_main.Phone if user_main.Phone else '', + 'txtupian': user_main.PaymentQRCode if user_main.PaymentQRCode else '', + 'txzh': user_main.PaymentAccount if user_main.PaymentAccount else '', # 🔥【修改开始】 'dashou_rate': dashou_rate, # 打手提现费率 'guanshi_rate': guanshi_rate, # 管事提现费率 @@ -1641,7 +1641,7 @@ class ShoukuanXinxiShangchuanView(APIView): try: # 获取当前用户 user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID # 准备响应数据 response_data = {} @@ -1704,11 +1704,11 @@ class ShoukuanXinxiShangchuanView(APIView): # 5. 保存所有信息到数据库 with transaction.atomic(): user_main.Phone = txdianhua - user_main.skzhanghao = txzh if txzh else '' + user_main.PaymentAccount = txzh if txzh else '' # 只有上传了图片才更新zhifu字段 if txtupian_url: - user_main.zhifu = txtupian_url + user_main.PaymentQRCode = txtupian_url user_main.save() @@ -1737,12 +1737,12 @@ class ShoukuanXinxiShangchuanView(APIView): 存储路径: tixian/{yonghuid}/{yonghuid}.扩展名 """ try: - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID if not yonghuid: return None # 获取旧收款码URL - old_shoukuanma_url = user_main.zhifu + old_shoukuanma_url = user_main.PaymentQRCode # 生成文件扩展名 file_name = image_file.name @@ -1802,7 +1802,7 @@ class TixianShenqingView(APIView): def post(self, request): try: user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID # 1. 获取并验证参数 leixing = request.data.get('leixing') @@ -1948,10 +1948,10 @@ class TixianShenqingView(APIView): # 4. 创建提现记录(先扣款后创建,确保资金安全) try: - avatar = user_main.avatar or '' - phone = user_main.phone or '' - zhifu = user_main.zhifu or '' - skzhanghao = user_main.skzhanghao or '' + avatar = user_main.Avatar or '' + phone = user_main.Phone or '' + zhifu = user_main.PaymentQRCode or '' + skzhanghao = user_main.PaymentAccount or '' tixian_record = Tixianjilu.query.create( yonghuid=yonghuid, avatar=avatar, @@ -2067,7 +2067,7 @@ class TixianShenqingView(APIView): # 订单检查(使用 Q 取反) unfinished_orders = MerchantOrderExt.query.filter( - MerchantID=user_main.yonghuid, + MerchantID=user_main.UserUID, ).exclude( Order__Status__in=[ORDER_STATUS_COMPLETED, ORDER_STATUS_REFUNDED,ORDER_STATUS_FAIL] ).exists() @@ -2076,7 +2076,7 @@ class TixianShenqingView(APIView): # 自身被处罚的罚单检查(状态不是已缴纳(2)和已驳回(4)) self_fadan = Penalty.query.filter( - PenalizedUserID=user_main.yonghuid, + PenalizedUserID=user_main.UserUID, ).exclude( Status__in=[2, 4] ).exists() @@ -2085,7 +2085,7 @@ class TixianShenqingView(APIView): # 申请的对他人处罚罚单检查(状态不是已缴纳(2)和已驳回(4)) applied_fadan = Penalty.query.filter( - ApplicantID=user_main.yonghuid, + ApplicantID=user_main.UserUID, ).exclude( Status__in=[2, 4] ).exists() @@ -2113,7 +2113,7 @@ class TixianShenqingView(APIView): # 订单检查(使用 exclude + __in) unfinished_orders = Order.query.filter( - PlayerID=user_main.yonghuid, + PlayerID=user_main.UserUID, ).exclude( Status__in=[ORDER_STATUS_COMPLETED, ORDER_STATUS_REFUNDED] ).exists() @@ -2122,7 +2122,7 @@ class TixianShenqingView(APIView): # 自身罚单检查 self_fadan = Penalty.query.filter( - PenalizedUserID=user_main.yonghuid, + PenalizedUserID=user_main.UserUID, ).exclude( Status__in=[2, 4] ).exists() @@ -2150,7 +2150,7 @@ class TixianJiluHuoquViewV2(APIView): def post(self, request): try: user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID page = request.data.get('page', 1) limit = request.data.get('limit', 50) mode = int(request.data.get('mode', 1) or 1) @@ -2315,8 +2315,8 @@ class AdminLoginView(APIView): # 只查询管理员类型的用户(密码用 CheckPassword 验证,不能在 ORM 中直接匹配哈希) user = User.query.filter( Phone=zhanghao, # 手机号作为账号 - admin_profile__isnull=False # 必须是管理员类型 - ).select_related('admin_profile').first() + AdminProfile__isnull=False # 必须是管理员类型 + ).select_related('AdminProfile').first() # 如果用户不存在或不是管理员,返回401 if not user: @@ -2333,14 +2333,14 @@ class AdminLoginView(APIView): ) # 【优化点2】检查管理员扩展表是否存在 - if not hasattr(user, 'admin_profile'): + if not hasattr(user, 'AdminProfile'): return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 【优化点3】验证二次密码(管理员扩展表中的password字段) - admin_profile = user.admin_profile + admin_profile = user.AdminProfile if admin_profile.password != erci_yanzhengma: return Response( {'code': 401, 'msg': '登录失败', 'data': None}, @@ -2358,8 +2358,8 @@ class AdminLoginView(APIView): #print(cunchu_ip) # 更新用户信息 - user.ip = cunchu_ip # 存储客户端IP - user.last_login_time = now_time # 更新最后登录时间 + user.IP = cunchu_ip # 存储客户端IP + user.UserLastLoginDate = now_time # 更新最后登录时间 user.save() # 【优化点4】生成JWT token(使用和微信登录相同的方式) @@ -2371,15 +2371,15 @@ class AdminLoginView(APIView): # 返回用户基本信息(可根据需要调整) response_data = { 'token': token, - 'yonghuid': user.yonghuid, - 'phone': user.phone or '', - 'user_type': user.user_type, + 'yonghuid': user.UserUID, + 'phone': user.Phone or '', + 'user_type': user.UserType, 'last_login_time': now_time.strftime('%Y-%m-%d %H:%M:%S') if now_time else '', } # 可以添加管理员特定信息 - if hasattr(user, 'admin_profile'): - response_data['wechat'] = user.admin_profile.wechat or '' + if hasattr(user, 'AdminProfile'): + response_data['wechat'] = user.AdminProfile.wechat or '' return Response({ 'code': 0, @@ -2442,14 +2442,14 @@ class AdminFinancialDataView(APIView): dangqianyonghu = request.user # 3. 验证用户类型是否为admin - if not hasattr(dangqianyonghu, 'user_type') or dangqianyonghu.user_type != 'admin': + if not hasattr(dangqianyonghu, 'UserType') or dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'msg': '权限不足', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证前端传递的账号与当前用户的phone字段是否一致 - if not dangqianyonghu.phone or dangqianyonghu.phone != qianzhanghao: + if not dangqianyonghu.Phone or dangqianyonghu.Phone != qianzhanghao: return Response( {'code': 401, 'msg': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -2460,13 +2460,13 @@ class AdminFinancialDataView(APIView): try: # 使用select_related一次性获取管理员扩展表 # 注意:这里假设user_type='admin'的用户都有admin_profile - yonghu_xiangxi = User.query.select_related('admin_profile').get( + yonghu_xiangxi = User.query.select_related('AdminProfile').get( pk=dangqianyonghu.pk, user_type='admin' ) # 检查管理员扩展表是否存在 - if not hasattr(yonghu_xiangxi, 'admin_profile'): + if not hasattr(yonghu_xiangxi, 'AdminProfile'): return Response( {'code': 401, 'msg': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -2564,14 +2564,14 @@ class AdGetOrderTypes(APIView): dangqianyonghu = request.user # 验证手机号 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 验证管理员身份 - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST @@ -2579,7 +2579,7 @@ class AdGetOrderTypes(APIView): # 3. 验证管理员扩展表 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, @@ -2671,14 +2671,14 @@ class AdGetOrderList(APIView): dangqianyonghu = request.user # 验证手机号 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + if not hasattr(dangqianyonghu, 'Phone') or str(dangqianyonghu.Phone) != qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 验证管理员身份 - if dangqianyonghu.user_type != 'admin': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST @@ -2686,7 +2686,7 @@ class AdGetOrderList(APIView): # 3. 验证管理员扩展表 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, @@ -2861,14 +2861,14 @@ class AdYaoQingDaShou(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -2876,7 +2876,7 @@ class AdYaoQingDaShou(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -2889,7 +2889,7 @@ class AdYaoQingDaShou(APIView): # 7. 尝试获取或创建管事扩展表 try: # 尝试获取现有的管事扩展表 - guanshi_profile = dangqianyonghu.guanshi_profile + guanshi_profile = dangqianyonghu.GuanshiProfile except AttributeError: # 管事扩展表不存在,创建一个 try: @@ -2906,7 +2906,7 @@ class AdYaoQingDaShou(APIView): chongzhifenrun=0, # 充值分佣0 yue=0 # 余额0 ) - #logger.info(f"为管理员{dangqianyonghu.yonghuid}创建了管事扩展表") + #logger.info(f"为管理员{dangqianyonghu.UserUID}创建了管事扩展表") except Exception as e: logger.error(f"创建管事扩展表失败: {str(e)}") return Response( @@ -2956,7 +2956,7 @@ class AdYaoQingDaShou(APIView): guanshi_profile.yaoqingma = new_yaoqingma guanshi_profile.save() - logger.info(f"管理员{dangqianyonghu.yonghuid}生成邀请码: {new_yaoqingma}") + logger.info(f"管理员{dangqianyonghu.UserUID}生成邀请码: {new_yaoqingma}") except Exception as e: logger.error(f"保存邀请码失败: {str(e)}") return Response( @@ -3029,14 +3029,14 @@ class AdGuanLiYongHu(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -3044,7 +3044,7 @@ class AdGuanLiYongHu(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -3164,8 +3164,8 @@ class AdGuanLiYongHu(APIView): user_main = boss.user user_data = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'nicheng': boss.nickname or '', } @@ -3207,8 +3207,8 @@ class AdGuanLiYongHu(APIView): user_main = dashou.user user_data = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'nicheng': dashou.nicheng or '', @@ -3251,10 +3251,10 @@ class AdGuanLiYongHu(APIView): user_main = guanshi.user user_data = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zhanghaozhuangtai': guanshi.zhuangtai, - 'nicheng': f'管事{user_main.yonghuid}', + 'nicheng': f'管事{user_main.UserUID}', } user_list.append(user_data) @@ -3294,8 +3294,8 @@ class AdGuanLiYongHu(APIView): user_main = shangjia.user user_data = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zhanghaozhuangtai': shangjia.zhuangtai, 'nicheng': shangjia.nicheng or '', } @@ -3360,14 +3360,14 @@ class CfGuanLi(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -3375,7 +3375,7 @@ class CfGuanLi(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -3596,14 +3596,14 @@ class AdTongYiChuFa(APIView): dangqianyonghu = request.user # 3. 验证手机号是否匹配 - if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: + 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': + if dangqianyonghu.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED @@ -3611,7 +3611,7 @@ class AdTongYiChuFa(APIView): # 5. 验证管理员扩展表是否存在 try: - guanliyuan = dangqianyonghu.admin_profile + guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, @@ -3720,7 +3720,7 @@ class AdTongYiChuFa(APIView): dashou.jifen += chufajilu.DeductedPoints # 加5分 dashou.save() logger.info( - f"拒绝处罚,给打手{dashou.user.yonghuid}返还积分{chufajilu.DeductedPoints}分,当前积分{dashou.jifen}") + f"拒绝处罚,给打手{dashou.user.UserUID}返还积分{chufajilu.DeductedPoints}分,当前积分{dashou.jifen}") else: # 积分已超过5分,可能是异常情况,记录警告但不中断处理 logger.warning( @@ -3792,7 +3792,7 @@ class AdckyhxqView(APIView): }, status=status.HTTP_401_UNAUTHORIZED) # 2. 验证用户类型为管理员 - if request.user.user_type != 'admin': + if request.user.UserType != 'admin': return Response({ 'code': 403, 'message': '权限不足', @@ -3800,7 +3800,7 @@ class AdckyhxqView(APIView): }, status=status.HTTP_403_FORBIDDEN) # 3. 验证管理员扩展表存在 - admin_profile = getattr(request.user, 'admin_profile', None) + admin_profile = getattr(request.user, 'AdminProfile', None) if not admin_profile: return Response({ 'code': 403, @@ -3810,7 +3810,7 @@ class AdckyhxqView(APIView): # 4. 验证管理员账号(phone)与前端传递的zhanghao一致 zhanghao = request.data.get('zhanghao') - if not zhanghao or request.user.phone != zhanghao: + if not zhanghao or request.user.Phone != zhanghao: return Response({ 'code': 401, 'message': '账号验证失败', @@ -3859,11 +3859,11 @@ class AdckyhxqView(APIView): # 通用字段(所有用户都有) user_info = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar, - 'phone': user_main.phone, - 'ip': user_main.ip, - 'create_time': user_main.create_time, + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar, + 'phone': user_main.Phone, + 'ip': user_main.IP, + 'create_time': user_main.UserCreateTime, 'update_time': user_main.update_time, 'user_type': user_type } @@ -3871,9 +3871,9 @@ class AdckyhxqView(APIView): # 根据用户类型查询扩展信息 if user_type == 1: # 老板 # 使用select_related预取boss_profile - user_main = User.query.filter(UserUID=uid).select_related('boss_profile').first() - if user_main.boss_profile: - boss_profile = user_main.boss_profile + user_main = User.query.filter(UserUID=uid).select_related('BossProfile').first() + if getattr(user_main, 'BossProfile', None): + boss_profile = user_main.BossProfile user_info.update({ 'nickname': boss_profile.nickname, 'zonge': boss_profile.zonge, @@ -3884,9 +3884,9 @@ class AdckyhxqView(APIView): elif user_type == 2: # 打手 # 使用select_related预取dashou_profile - user_main = User.query.filter(UserUID=uid).select_related('dashou_profile').first() - if user_main.dashou_profile: - dashou_profile = user_main.dashou_profile + user_main = User.query.filter(UserUID=uid).select_related('DashouProfile').first() + if getattr(user_main, 'DashouProfile', None): + dashou_profile = user_main.DashouProfile # 构建打手信息 user_info.update({ @@ -3943,11 +3943,11 @@ class AdckyhxqView(APIView): elif user_type == 3: # 管事 # 使用select_related预取guanshi_profile - user_main = User.query.filter(UserUID=uid).select_related('guanshi_profile').first() - if user_main.guanshi_profile: - guanshi_profile = user_main.guanshi_profile + user_main = User.query.filter(UserUID=uid).select_related('GuanshiProfile').first() + if getattr(user_main, 'GuanshiProfile', None): + guanshi_profile = user_main.GuanshiProfile user_info.update({ - 'nicheng': user_main.phone, # 管事昵称用手机号 + 'nicheng': user_main.Phone, # 管事昵称用手机号 'yaoqingma': guanshi_profile.yaoqingma, 'dianhua': guanshi_profile.dianhua, 'wechat': guanshi_profile.wechat, @@ -3962,9 +3962,9 @@ class AdckyhxqView(APIView): elif user_type == 4: # 商家 # 使用select_related预取shop_profile - user_main = User.query.filter(UserUID=uid).select_related('shop_profile').first() - if user_main.shop_profile: - shop_profile = user_main.shop_profile + user_main = User.query.filter(UserUID=uid).select_related('ShopProfile').first() + if getattr(user_main, 'ShopProfile', None): + shop_profile = user_main.ShopProfile user_info.update({ 'nicheng': shop_profile.nicheng, 'zhuangtai': shop_profile.zhuangtai, @@ -4029,14 +4029,14 @@ class AdcjxgView(APIView): 'data': None }, status=status.HTTP_401_UNAUTHORIZED) - if request.user.user_type != 'admin': + if request.user.UserType != 'admin': return Response({ 'code': 403, 'message': '权限不足', 'data': None }, status=status.HTTP_403_FORBIDDEN) - admin_profile = getattr(request.user, 'admin_profile', None) + admin_profile = getattr(request.user, 'AdminProfile', None) if not admin_profile: return Response({ 'code': 403, @@ -4045,7 +4045,7 @@ class AdcjxgView(APIView): }, status=status.HTTP_403_FORBIDDEN) zhanghao = request.data.get('zhanghao') - if not zhanghao or request.user.phone != zhanghao: + if not zhanghao or request.user.Phone != zhanghao: return Response({ 'code': 401, 'message': '账号验证失败', @@ -4109,7 +4109,7 @@ class AdcjxgView(APIView): # 获取或创建打手扩展记录 dashou_profile, created = UserDashou.query.get_or_create( user=user_main, - defaults={'nicheng': user_main.phone or f'打手{uid}'} + defaults={'nicheng': user_main.Phone or f'打手{uid}'} ) # 记录敏感字段的旧值 @@ -4233,7 +4233,7 @@ class AdcjxgView(APIView): # 获取或创建商家扩展记录 shop_profile, created = UserShangjia.query.get_or_create( user=user_main, - defaults={'nicheng': user_main.phone or f'商家{uid}'} + defaults={'nicheng': user_main.Phone or f'商家{uid}'} ) # 记录敏感字段的旧值 @@ -4414,7 +4414,7 @@ def admin_required(view_func): }, status=status.HTTP_401_UNAUTHORIZED) # 2. 验证用户类型为管理员 - if request.user.user_type != 'admin': + if request.user.UserType != 'admin': return Response({ 'code': 403, 'message': '权限不足', @@ -4422,7 +4422,7 @@ def admin_required(view_func): }, status=status.HTTP_403_FORBIDDEN) # 3. 验证管理员扩展表存在 - admin_profile = getattr(request.user, 'admin_profile', None) + admin_profile = getattr(request.user, 'AdminProfile', None) if not admin_profile: return Response({ 'code': 403, @@ -4432,7 +4432,7 @@ def admin_required(view_func): # 4. 验证管理员账号(phone)与前端传递的zhanghao一致 zhanghao = request.data.get('zhanghao') - if not zhanghao or request.user.phone != zhanghao: + if not zhanghao or request.user.Phone != zhanghao: return Response({ 'code': 401, 'message': '账号验证失败', @@ -4623,7 +4623,7 @@ class AdtixianqkView(APIView): with transaction.atomic(): # 更新提现记录 withdrawal.zhuangtai = result - withdrawal.shenheid = request.user.yonghuid + withdrawal.shenheid = request.user.UserUID if result == 3: # 拒绝 withdrawal.bhliyou = reason @@ -4704,8 +4704,8 @@ class DashouZiliaoHuoquView(APIView): 'jieshao': dashou.jieshao if dashou.jieshao else '', 'dianhua': dashou.dianhua if dashou.dianhua else '', 'wechat': dashou.wechat if dashou.wechat else '', - 'uid': request.user.yonghuid, # 从主表获取UID - 'avatar': request.user.avatar if request.user.avatar else '', # 头像相对URL + 'uid': request.user.UserUID, # 从主表获取UID + 'avatar': request.user.Avatar if request.user.Avatar else '', # 头像相对URL } return Response({ @@ -4892,7 +4892,7 @@ class ShangjiaZhuceView(APIView): # 2. 检查用户是否已是商家 try: - shangjia_profile = current_user.shop_profile + shangjia_profile = current_user.ShopProfile # 用户已是商家,直接返回现有信息 return self.fanhuiXianyouShangjiaXinxi(shangjia_profile, current_user) except UserShangjia.DoesNotExist: @@ -4916,7 +4916,7 @@ class ShangjiaZhuceView(APIView): # 4.1 查询老板扩展表获取老板昵称 boss_nickname = '普通商家' try: - boss_profile = current_user.boss_profile + boss_profile = current_user.BossProfile if boss_profile.nickname and boss_profile.nickname.strip(): boss_nickname = boss_profile.nickname.strip() except UserBoss.DoesNotExist: @@ -4998,7 +4998,7 @@ class ShangjiaZhuceView(APIView): 'wechat': shangjia_profile.wechat or '', # 微信 # 🟢 可能需要的主表信息 - 'uid': current_user.yonghuid, # 用户ID + 'uid': current_user.UserUID, # 用户ID } return data @@ -5341,8 +5341,8 @@ class WechatLoginAndDashouRegisterView(APIView): # 更新用户IP和最后登录时间 cunchu_ip = kehuduan_ip - user_main.ip = cunchu_ip - user_main.last_login_time = timezone.now() + user_main.IP = cunchu_ip + user_main.UserLastLoginDate = timezone.now() user_main.save() if created: @@ -5368,7 +5368,7 @@ class WechatLoginAndDashouRegisterView(APIView): return self.fanhuiDengluZhuceshuju(user_main, dashou_profile) # 4.4 核心:为用户创建所有身份(打手、商家、管事) - guanshi_yonghuid = guanshi_profile.user.yonghuid + guanshi_yonghuid = guanshi_profile.user.UserUID # 创建打手扩展表 dashou_profile = UserDashou.query.create( @@ -5551,8 +5551,8 @@ class WechatLoginAndDashouRegisterView(APIView): response_data = { 'token': token, 'nicheng': boss_nickname, - 'uid': user_main.yonghuid, - 'touxiang': user_main.avatar or '', + 'uid': user_main.UserUID, + 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, @@ -5609,8 +5609,8 @@ class WechatLoginAndDashouRegisterView(APIView): response_data = { 'token': token, 'nicheng': boss_nickname, - 'uid': user_main.yonghuid, - 'touxiang': user_main.avatar or '', + 'uid': user_main.UserUID, + 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, @@ -5656,8 +5656,8 @@ class WechatLoginAndDashouRegisterView(APIView): response_data = { 'token': token, 'nicheng': boss_nickname, - 'uid': user_main.yonghuid, - 'touxiang': user_main.avatar or '', + 'uid': user_main.UserUID, + 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, @@ -5692,7 +5692,7 @@ class WechatLoginAndDashouRegisterView(APIView): # 查询并处理会员列表 (clumber) # 使用当前用户的yonghuid进行查询 huiyuan_goumai_list = [] - goumai_records = Huiyuangoumai.query.filter(yonghu_id=dashou_profile.user.yonghuid) + goumai_records = Huiyuangoumai.query.filter(yonghu_id=dashou_profile.user.UserUID) for record in goumai_records: # 调用方法检查是否到期,此方法内部会更新状态 shifou_daoqi = record.jiance_shifou_daoqi() @@ -5729,7 +5729,7 @@ class YonghuTixianShenheXiugaiView(APIView): try: # 1. 获取当前用户 user_main = request.user - current_yonghuid = user_main.yonghuid + current_yonghuid = user_main.UserUID # 2. 验证必填字段:提现记录ID tixian_id = request.data.get('tixian_id') @@ -5911,7 +5911,7 @@ class YonghuTixianShenheXiugaiView(APIView): 返回: 新的图片相对路径 """ try: - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID if not yonghuid: return None @@ -5969,7 +5969,7 @@ class DashouPermission: try: # 通过反向关系获取打手扩展信息 - dashou_profile = user.dashou_profile + dashou_profile = user.DashouProfile # 检查账号状态是否为1(正常) if dashou_profile.zhanghaozhuangtai != 1: @@ -5998,11 +5998,11 @@ class ChufaJiluHuoquView(APIView): try: # 获取当前用户信息 user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID # 通过反向关系获取打手扩展信息 try: - dashou_profile = user_main.dashou_profile + dashou_profile = user_main.DashouProfile # 再次确认账号状态 if dashou_profile.zhanghaozhuangtai != 1: return Response({ @@ -6280,11 +6280,11 @@ class DashouShensuView(APIView): try: # 获取当前用户信息 user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID # 🔴🆕【关键】验证打手账号状态 try: - dashou_profile = user_main.dashou_profile + dashou_profile = user_main.DashouProfile if dashou_profile.zhanghaozhuangtai != 1: return Response({ 'code': 3, @@ -6441,11 +6441,11 @@ class ShangjiaChufaJiluHuoquView(APIView): try: # 获取当前用户信息 user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID # 通过反向关系获取商家扩展信息 try: - shangjia_profile = user_main.shop_profile + shangjia_profile = user_main.ShopProfile # 再次确认账号状态 if shangjia_profile.zhuangtai != 1: return Response({ @@ -6736,8 +6736,8 @@ class KefuLoginView(APIView): # 3. 查询该手机号下所有客服账号(User 字段为 Phone,与 backend 创建客服校验写法一致) user_mains = User.query.filter( Phone=phone, - kefu_profile__isnull=False, - ).select_related('kefu_profile') + KefuProfile__isnull=False, + ).select_related('KefuProfile') if not user_mains.exists(): logger.warning(f"登录失败,手机号不存在或非客服: {phone}, IP: {client_ip}") @@ -6752,7 +6752,7 @@ class KefuLoginView(APIView): target_kefu = None for user in user_mains: - kefu = user.kefu_profile + kefu = user.KefuProfile # 验证主密码(bcrypt 哈希验证) if not user.CheckPassword(password): continue @@ -6814,7 +6814,7 @@ class KefuLoginView(APIView): 'data': { 'token': token, 'nicheng': target_kefu.nicheng, - 'yonghuid': target_user.yonghuid, + 'yonghuid': target_user.UserUID, } }) @@ -6855,28 +6855,28 @@ class KefuStatsView(APIView): phone = request.data.get('phone', '').strip() if not phone: # 缺少参数,但按约定返回 401(可以改为 400,但为了统一,用401) - logger.warning(f"统计接口缺少 phone 参数,用户: {request.user.yonghuid}") + logger.warning(f"统计接口缺少 phone 参数,用户: {request.user.UserUID}") return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 获取当前登录用户(从 JWT 中解析) current_user = request.user # 3. 验证前端传来的 phone 是否与当前用户的 phone 一致 - if current_user.phone != phone: + if current_user.Phone != phone: # 不一致,可能 token 被冒用或参数错误 - logger.warning(f"统计接口 phone 不匹配: 请求phone={phone}, 用户phone={current_user.phone}") + logger.warning(f"统计接口 phone 不匹配: 请求phone={phone}, 用户phone={current_user.Phone}") return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 4. 验证用户类型是否为客服 - if current_user.user_type != 'kefu': - logger.warning(f"统计接口用户类型非客服: {current_user.yonghuid}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"统计接口用户类型非客服: {current_user.UserUID}") return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 5. 尝试获取客服扩展表数据 try: - kefu_profile = current_user.kefu_profile # OneToOneField 反向关系 + kefu_profile = current_user.KefuProfile # OneToOneField 反向关系 except UserKefu.DoesNotExist: - logger.warning(f"统计接口客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"统计接口客服扩展表不存在: {current_user.UserUID}") return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 6. 返回统计数据(字段名与前端约定一致) @@ -6915,22 +6915,22 @@ class KefuGetOrderTypesView(APIView): # 2. 验证手机号一致性 if getattr(current_user, 'phone', '') != phone: - logger.warning(f"手机号不匹配: 请求phone={phone}, 用户phone={current_user.phone}") + logger.warning(f"手机号不匹配: 请求phone={phone}, 用户phone={current_user.Phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 验证用户类型及客服状态 - if current_user.user_type != 'kefu': - logger.warning(f"用户类型非客服: {current_user.yonghuid}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu_profile = current_user.kefu_profile + kefu_profile = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu_profile.zhuangtai != 1: - logger.warning(f"客服账号已被禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已被禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 4. 查询订单类型(商品类型) @@ -7078,11 +7078,11 @@ class KefuGetOrderListView(APIView): laoban_ids = [o.pingtai_kuozhan.BossID for o in orders_qs if hasattr(o, 'pingtai_kuozhan') and o.pingtai_kuozhan.BossID] nickname_map = {} if laoban_ids: - users = User.query.filter(UserUID__in=laoban_ids).select_related('boss_profile').only( - 'UserUID', 'boss_profile__nickname' + users = User.query.filter(UserUID__in=laoban_ids).select_related('BossProfile').only( + 'UserUID', 'BossProfile__nickname' ) for user in users: - nickname_map[user.yonghuid] = user.boss_profile.nickname or '' if hasattr(user, 'boss_profile') else '' + nickname_map[user.UserUID] = (user.BossProfile.nickname or '') if getattr(user, 'BossProfile', None) else '' result_list = [] for order in orders_qs: @@ -7209,7 +7209,7 @@ class KefuGetShangjiaOrderListView(APIView): if shangjia_nicheng: # 通过商家扩展表关联 UserShangjia 的昵称 q_conditions &= Q(shangjia_kuozhan__MerchantID__in=User.query.filter( - shop_profile__nicheng__icontains=shangjia_nicheng + ShopProfile__nicheng__icontains=shangjia_nicheng ).values_list('UserUID', flat=True)) # ---------- 统计(仅按发单平台,非跨平台) ---------- @@ -7247,11 +7247,11 @@ class KefuGetShangjiaOrderListView(APIView): shangjia_ids = [o.shangjia_kuozhan.MerchantID for o in orders_qs if hasattr(o, 'shangjia_kuozhan') and o.shangjia_kuozhan.MerchantID] nickname_map = {} if shangjia_ids: - users = User.query.filter(UserUID__in=shangjia_ids).select_related('shop_profile').only( - 'UserUID', 'shop_profile__nicheng' + users = User.query.filter(UserUID__in=shangjia_ids).select_related('ShopProfile').only( + 'UserUID', 'ShopProfile__nicheng' ) for user in users: - nickname_map[user.yonghuid] = user.shop_profile.nicheng or '' if hasattr(user, 'shop_profile') else '' + nickname_map[user.UserUID] = user.ShopProfile.nicheng or '' if hasattr(user, 'ShopProfile') else '' result_list = [] for order in orders_qs: @@ -7327,18 +7327,18 @@ class KefuChangeDashouView(APIView): 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}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: - logger.warning(f"客服账号已禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单 @@ -7359,7 +7359,7 @@ class KefuChangeDashouView(APIView): # 6. 验证打手扩展表 try: - new_dashou_profile = new_dashou_user.dashou_profile + new_dashou_profile = new_dashou_user.DashouProfile except ObjectDoesNotExist: return Response({'code': 404, 'msg': '打手信息不完整'}, status=status.HTTP_404_NOT_FOUND) @@ -7416,7 +7416,7 @@ class KefuChangeDashouView(APIView): if old_dashou_id: try: old_dashou_user = User.query.get(UserUID=old_dashou_id) - old_dashou_profile = old_dashou_user.dashou_profile + old_dashou_profile = old_dashou_user.DashouProfile old_dashou_profile.zhuangtai = 1 # 恢复空闲 old_dashou_profile.tuikuanliang += 1 # 原打手退款总量+1(业务逻辑) old_dashou_profile.save() @@ -7429,7 +7429,7 @@ class KefuChangeDashouView(APIView): # 9.3 更新订单 order.PlayerID = new_dashou_id order.Status = 2 # 进行中 - order.AssignedCS = current_user.yonghuid # 记录处理客服ID + order.AssignedCS = current_user.UserUID # 记录处理客服ID order.save() return Response({'code': 0, 'msg': '更换打手成功'}) @@ -7465,11 +7465,11 @@ class KefuRecoverOrderView(APIView): if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.user_type != 'kefu': + if current_user.UserType not in ('kefu', 'admin'): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) @@ -7494,7 +7494,7 @@ class KefuRecoverOrderView(APIView): # 6. 更新订单 order.Status = new_status - order.AssignedCS = current_user.yonghuid + order.AssignedCS = current_user.UserUID order.save() return Response({'code': 0, 'msg': '恢复成功', 'data': {'new_status': new_status}}) @@ -7537,18 +7537,18 @@ class KefuPlatformRefundView(APIView): 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}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: - logger.warning(f"客服账号已禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单并加锁 @@ -7582,7 +7582,7 @@ class KefuPlatformRefundView(APIView): # 8. 更新订单状态为5(已退款) order.Status = 5 - order.AssignedCS = current_user.phone # 记录处理客服手机号 + order.AssignedCS = current_user.Phone # 记录处理客服手机号 if tuikuan_liyou: order.RefundReason = tuikuan_liyou order.save() @@ -7592,7 +7592,7 @@ class KefuPlatformRefundView(APIView): if jiedan_dashou_id: try: dashou_user = User.query.get(UserUID=jiedan_dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.tuikuanliang += 1 dashou_profile.zhuangtai = 1 # 打手状态设为空闲 dashou_profile.save() @@ -7817,11 +7817,11 @@ class KefuRejectRefundView(APIView): if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.user_type != 'kefu': + if current_user.UserType not in ('kefu', 'admin'): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) @@ -7848,7 +7848,7 @@ class KefuRejectRefundView(APIView): # 6. 更新订单状态为退款失败(6) order.Status = 6 - order.AssignedCS = current_user.phone # 记录处理客服手机号 + order.AssignedCS = current_user.Phone # 记录处理客服手机号 if jujue_liyou: order.RefundReason = jujue_liyou order.save() @@ -7857,7 +7857,7 @@ class KefuRejectRefundView(APIView): if jiedan_dashou_id: try: dashou_user = User.query.get(UserUID=jiedan_dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile # 增加打手相关统计数据 dashou_profile.chengjiaozongliang += 1 dashou_profile.yue += dashou_fencheng @@ -7877,7 +7877,7 @@ class KefuRejectRefundView(APIView): shangjia_id = shangjia_kuozhan.MerchantID if shangjia_id: shangjia_user = User.query.get(UserUID=shangjia_id) - shangjia_profile = shangjia_user.shop_profile + shangjia_profile = shangjia_user.ShopProfile shangjia_profile.chengjiao += 1 shangjia_profile.save() logger.info(f"拒绝退款:商家 {shangjia_id} 成交订单+1") @@ -7983,11 +7983,11 @@ class KefuMerchantRefundView(APIView): if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.user_type != 'kefu': + if current_user.UserType not in ('kefu', 'admin'): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) @@ -8025,7 +8025,7 @@ class KefuMerchantRefundView(APIView): with transaction.atomic(): # 更新订单 order.Status = 5 - order.AssignedCS = current_user.phone + order.AssignedCS = current_user.Phone if tuikuan_liyou: order.RefundReason = tuikuan_liyou order.save() @@ -8034,7 +8034,7 @@ class KefuMerchantRefundView(APIView): if jiedan_dashou_id: try: dashou_user = User.query.get(UserUID=jiedan_dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.tuikuanliang += 1 dashou_profile.zhuangtai = 1 dashou_profile.save() @@ -8044,7 +8044,7 @@ class KefuMerchantRefundView(APIView): # 更新商家 try: shangjia_user = User.query.get(UserUID=shangjia_id) - shangjia_profile = shangjia_user.shop_profile + shangjia_profile = shangjia_user.ShopProfile shangjia_profile.tuikuan += 1 shangjia_profile.yue += jine shangjia_profile.save() @@ -8082,7 +8082,7 @@ class KefuMerchantRefundView(APIView): - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile kefu.jinrichuli += 1 kefu.jinyuechuli += 1 kefu.zongchuli += 1 @@ -8122,18 +8122,18 @@ class KefuRejectSettlementView(APIView): 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}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: - logger.warning(f"客服账号已禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单,预加载平台扩展表 @@ -8171,7 +8171,7 @@ class KefuRejectSettlementView(APIView): # 5.3 更新打手扩展表 try: dashou_user = User.query.get(UserUID=jiedan_dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile dashou_profile.zhuangtai = 1 # 打手状态设为空闲 dashou_profile.tuikuanliang += 1 # 退款订单总量+1 dashou_profile.save() @@ -8233,10 +8233,10 @@ class KefuTransferHallView(APIView): 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': + if current_user.UserType not in ('kefu', 'admin'): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) except ObjectDoesNotExist: @@ -8301,10 +8301,10 @@ class KefuCancelDesignationView(APIView): 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': + if current_user.UserType not in ('kefu', 'admin'): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) except ObjectDoesNotExist: @@ -8380,18 +8380,18 @@ class KefuGetDashouListView(APIView): 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}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: - logger.warning(f"客服账号已禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 构建查询条件(只查询打手) @@ -8415,8 +8415,8 @@ class KefuGetDashouListView(APIView): for dashou in dashou_list: user = dashou.user result.append({ - 'yonghuid': user.yonghuid, - 'avatar': user.avatar or '', + 'yonghuid': user.UserUID, + 'avatar': user.Avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'nicheng': dashou.nicheng or '', @@ -8468,24 +8468,24 @@ class KefuGetDashouDetailView(APIView): 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}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: - logger.warning(f"客服账号已禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询打手主表和扩展表 try: # 使用 select_related 预加载 dashou_profile,减少数据库查询 - user_main = User.query.select_related('dashou_profile').get( + user_main = User.query.select_related('DashouProfile').get( yonghuid=uid ) except User.DoesNotExist: @@ -8494,12 +8494,12 @@ class KefuGetDashouDetailView(APIView): # 验证用户类型是否为打手(可选,但建议验证) # 如果该用户没有打手扩展表,则返回空信息(但前端会显示未注册) try: - dashou = user_main.dashou_profile + dashou = user_main.DashouProfile except ObjectDoesNotExist: # 用户存在但未注册打手身份,返回基本信息 user_info = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': 0, 'zhanghaozhuangtai': 0, 'zhuangtai': 0, @@ -8507,9 +8507,9 @@ class KefuGetDashouDetailView(APIView): 'chenghao': '', 'dianhua': '', 'wechat': '', - 'phone': user_main.phone or '', - 'create_time': user_main.create_time, - 'ip': user_main.ip or '', + 'phone': user_main.Phone or '', + 'create_time': user_main.UserCreateTime, + 'ip': user_main.IP or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, 'tuikuanliang': 0, @@ -8534,8 +8534,8 @@ class KefuGetDashouDetailView(APIView): # 4. 构建打手信息 user_info = { - 'yonghuid': user_main.yonghuid, - 'avatar': user_main.avatar or '', + 'yonghuid': user_main.UserUID, + 'avatar': user_main.Avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'zhuangtai': dashou.zhuangtai, @@ -8543,9 +8543,9 @@ class KefuGetDashouDetailView(APIView): 'chenghao': dashou.chenghao or '', 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', - 'phone': user_main.phone or '', - 'create_time': user_main.create_time, - 'ip': user_main.ip or '', + 'phone': user_main.Phone or '', + 'create_time': user_main.UserCreateTime, + 'ip': user_main.IP or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, 'tuikuanliang': dashou.tuikuanliang, @@ -8632,7 +8632,7 @@ class KefuUpdateDashouView(APIView): # 3. 查询打手是否存在 try: dashou_user = User.query.get(UserUID=dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile except User.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}) except UserDashou.DoesNotExist: @@ -8664,7 +8664,7 @@ class KefuUpdateDashouView(APIView): # 记录日志 Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, # 2=打手 xiugaitijiao=after_yue, xiugaitijiaoq=before['yue'], @@ -8680,7 +8680,7 @@ class KefuUpdateDashouView(APIView): after_yue = dashou_profile.yue Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, xiugaitijiao=after_yue, xiugaitijiaoq=before['yue'], @@ -8699,7 +8699,7 @@ class KefuUpdateDashouView(APIView): after_yajin = dashou_profile.yajin Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, yajin=after_yajin, yyajin=before['yajin'], @@ -8715,7 +8715,7 @@ class KefuUpdateDashouView(APIView): after_yajin = dashou_profile.yajin Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, yajin=after_yajin, yyajin=before['yajin'], @@ -8734,7 +8734,7 @@ class KefuUpdateDashouView(APIView): after_jifen = dashou_profile.jifen Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, jifen=after_jifen, yjifen=before['jifen'], @@ -8750,7 +8750,7 @@ class KefuUpdateDashouView(APIView): after_jifen = dashou_profile.jifen Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, jifen=after_jifen, yjifen=before['jifen'], @@ -8768,7 +8768,7 @@ class KefuUpdateDashouView(APIView): dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改账号状态为{new_status}', ) @@ -8784,7 +8784,7 @@ class KefuUpdateDashouView(APIView): dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改在线状态为{new_status}', ) @@ -8800,7 +8800,7 @@ class KefuUpdateDashouView(APIView): dashou_profile.save() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, qitashuoming=f'修改接单状态为{new_status}', ) @@ -8836,7 +8836,7 @@ class KefuUpdateDashouView(APIView): # 记录修改日志 Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, huiyuants=days, huiyuan_id=huiyuan_id, @@ -8857,7 +8857,7 @@ class KefuUpdateDashouView(APIView): record.delete() Xiugaijilu.query.create( yonghuid=dashou_id, - xiugaiid=kefu.user.phone, + xiugaiid=kefu.user.Phone, leixing=2, huiyuan_id=huiyuan_id, qitashuoming=f'移除会员 {huiyuan_id}', @@ -9009,12 +9009,12 @@ class KefuGetOrderDetailView(APIView): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 验证用户类型 - if current_user.user_type != 'kefu': + if current_user.UserType not in ('kefu', 'admin'): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 客服扩展表 try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) except Exception as e: @@ -9150,18 +9150,18 @@ class KefuPunishmentDetailView(APIView): 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}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: - logger.warning(f"客服账号已禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 查询处罚记录(按创建时间倒序取最新一条) @@ -9241,10 +9241,10 @@ class KefuPunishmentActionView(APIView): 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': + if current_user.UserType not in ('kefu', 'admin'): return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) except ObjectDoesNotExist: @@ -9369,11 +9369,11 @@ class KefuForceCompleteView(APIView): if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}) - if current_user.user_type != 'kefu': + if current_user.UserType not in ('kefu', 'admin'): return Response({'code': 401, 'msg': '认证失败'}) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}) @@ -9400,7 +9400,7 @@ class KefuForceCompleteView(APIView): # 5. 给打手结算 try: dashou_user = User.query.get(UserUID=dashou_id) - dashou_profile = dashou_user.dashou_profile + dashou_profile = dashou_user.DashouProfile # 原子更新(避免并发重复加) dashou_profile.chengjiaozongliang = F('chengjiaozongliang') + 1 dashou_profile.yue = F('yue') + dashou_fencheng @@ -9421,7 +9421,7 @@ class KefuForceCompleteView(APIView): shangjia_ext = order.shangjia_kuozhan # 假设 related_name='shangjia_kuozhan' if shangjia_ext and shangjia_ext.shangjia_id: shangjia_user = User.query.get(UserUID=shangjia_ext.shangjia_id) - shangjia_profile = shangjia_user.shop_profile + shangjia_profile = shangjia_user.ShopProfile shangjia_profile.chengjiao = F('chengjiao') + 1 shangjia_profile.save(update_fields=['chengjiao']) @@ -9841,18 +9841,18 @@ class KefuWithdrawDetailView(APIView): 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}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: - logger.warning(f"客服账号已禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 查询提现记录 @@ -9951,10 +9951,10 @@ class KefuWithdrawActionView(APIView): current_user = request.user if getattr(current_user, 'phone', '') != phone: return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.user_type != 'kefu': + if current_user.UserType not in ('kefu', 'admin'): return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: @@ -10046,25 +10046,25 @@ class KefuWithdrawActionView(APIView): sync_audit_and_jilu_status( audit, 5, bhliyou=limit_msg, - bo_hui_ren_id=kefu_user.yonghuid, + bo_hui_ren_id=kefu_user.UserUID, ) refund_balance(user, audit.leixing, audit.shijidaozhang) - tixian.shenheid = kefu_user.yonghuid + tixian.shenheid = kefu_user.UserUID tixian.bhliyou = limit_msg tixian.save(update_fields=['shenheid', 'bhliyou', 'update_time']) raise ValueError(f'提现记录{tixian_id}:{limit_msg},已自动驳回并退款') - sync_audit_and_jilu_status(audit, 6, shenhe_ren_id=kefu_user.yonghuid) - tixian.shenheid = kefu_user.yonghuid + sync_audit_and_jilu_status(audit, 6, shenhe_ren_id=kefu_user.UserUID) + tixian.shenheid = kefu_user.UserUID tixian.save(update_fields=['shenheid', 'update_time']) else: sync_audit_and_jilu_status( audit, 5, bhliyou=reason, - bo_hui_ren_id=kefu_user.yonghuid, + bo_hui_ren_id=kefu_user.UserUID, ) # 退还可到账金额,申请时扣的 shenqing_jine 中的手续费(shouxufei)不退 refund_balance(user, audit.leixing, audit.shijidaozhang) - tixian.shenheid = kefu_user.yonghuid + tixian.shenheid = kefu_user.UserUID tixian.bhliyou = reason tixian.save(update_fields=['shenheid', 'bhliyou', 'update_time']) @@ -10209,18 +10209,18 @@ class KefuPunishView(APIView): 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}") + if current_user.UserType not in ('kefu', 'admin'): + logger.warning(f"用户类型非客服: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - kefu = current_user.kefu_profile + kefu = current_user.KefuProfile except ObjectDoesNotExist: - logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") + logger.warning(f"客服扩展表不存在: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: - logger.warning(f"客服账号已禁用: {current_user.yonghuid}") + logger.warning(f"客服账号已禁用: {current_user.UserUID}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单 @@ -10237,7 +10237,7 @@ class KefuPunishView(APIView): # 5. 查询打手 try: dashou_user = User.query.get(UserUID=dashou_id) - dashou = dashou_user.dashou_profile + dashou = dashou_user.DashouProfile except User.DoesNotExist: return Response({'code': 404, 'msg': '打手用户不存在'}, status=status.HTTP_404_NOT_FOUND) except ObjectDoesNotExist: @@ -10301,10 +10301,10 @@ class AdKftjView(APIView): # 2. 管理员验证(同前) current_user = request.user - if current_user.phone != zhanghao or current_user.user_type != 'admin': + if current_user.Phone != zhanghao or current_user.UserType != 'admin': return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - admin_profile = current_user.admin_profile + admin_profile = current_user.AdminProfile except: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) @@ -10337,7 +10337,7 @@ class AdKftjView(APIView): erjimima_hashed = _bcrypt.hashpw(erjimima.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8') kefu = UserKefu.query.create( user=user, - nicheng=nicheng or f'客服{user.yonghuid}', + nicheng=nicheng or f'客服{user.UserUID}', erjimima=erjimima_hashed, zhuangtai=1, jinrichuli=0, @@ -10378,14 +10378,14 @@ class AdKfglView(APIView): # 2. 管理员身份验证 current_user = request.user - if not hasattr(current_user, 'phone') or str(current_user.phone) != zhanghao: + if not hasattr(current_user, 'Phone') or str(current_user.Phone) != zhanghao: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) - if current_user.user_type != 'admin': + if current_user.UserType != 'admin': return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: - admin_profile = current_user.admin_profile + admin_profile = current_user.AdminProfile except AttributeError: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) @@ -10404,8 +10404,8 @@ class AdKfglView(APIView): for kefu in queryset: user = kefu.user kefu_list.append({ - 'yonghuid': user.phone, - 'phone': user.phone or '', + 'yonghuid': user.Phone, + 'phone': user.Phone or '', 'nicheng': kefu.nicheng or '', 'zhuangtai': kefu.zhuangtai, 'jinrichuli': kefu.jinrichuli, @@ -10450,17 +10450,17 @@ class AdKfxgView(APIView): # 管理员验证 current_user = request.user - if current_user.phone != zhanghao or current_user.user_type != 'admin': + if current_user.Phone != zhanghao or current_user.UserType != 'admin': return Response({'code': 401, 'message': '身份验证失败'}) try: - admin_profile = current_user.admin_profile + admin_profile = current_user.AdminProfile except: return Response({'code': 401, 'message': '身份验证失败'}) # 查询客服 try: user = User.query.get(Phone=uid) - kefu = user.kefu_profile + kefu = user.KefuProfile except (User.DoesNotExist, UserKefu.DoesNotExist): return Response({'code': 404, 'message': '客服不存在'}) @@ -10547,7 +10547,7 @@ class WechatLoginAndGuanshiRegisterView(APIView): if created: UserBoss.query.create(user=user_main, nickname='微信用户') - if hasattr(user_main, 'guanshi_profile'): + if hasattr(user_main, 'GuanshiProfile'): return self._fanhui_xianyou_guanshi_info(user_main) try: @@ -10560,9 +10560,9 @@ class WechatLoginAndGuanshiRegisterView(APIView): return Response({'code': 403, 'msg': '该邀请码对应的组长账号已被禁用', 'data': None}, status=status.HTTP_403_FORBIDDEN) - zuzhang_yonghuid = zuzhang.user.yonghuid + zuzhang_yonghuid = zuzhang.user.UserUID - guanshi_yaoqingma = CreateInvitationCode(str(user_main.yonghuid)) + guanshi_yaoqingma = CreateInvitationCode(str(user_main.UserUID)) guanshi_profile = UserGuanshi.query.create( user=user_main, yaoqingma=guanshi_yaoqingma, @@ -10584,7 +10584,7 @@ class WechatLoginAndGuanshiRegisterView(APIView): logger.error(f"组长每日统计更新失败(邀请管事):{str(e)}") - if not hasattr(user_main, 'dashou_profile'): + if not hasattr(user_main, 'DashouProfile'): dashou_profile = UserDashou.query.create( user=user_main, nicheng='大神', @@ -10592,7 +10592,7 @@ class WechatLoginAndGuanshiRegisterView(APIView): yaoqingren=zuzhang_yonghuid, ) else: - dashou_profile = user_main.dashou_profile + dashou_profile = user_main.DashouProfile refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) @@ -10688,7 +10688,7 @@ class WechatLoginAndGuanshiRegisterView(APIView): } huiyuan_list = [] try: - records = Huiyuangoumai.query.filter(yonghu_id=dashou_profile.user.yonghuid) + records = Huiyuangoumai.query.filter(yonghu_id=dashou_profile.user.UserUID) for record in records: shifou_daoqi = record.jiance_shifou_daoqi() huiyuan_list.append({ @@ -10704,14 +10704,14 @@ class WechatLoginAndGuanshiRegisterView(APIView): return data def _fanhui_xianyou_guanshi_info(self, user_main): - guanshi_profile = user_main.guanshi_profile - dashou_profile = getattr(user_main, 'dashou_profile', None) + guanshi_profile = user_main.GuanshiProfile + dashou_profile = getattr(user_main, 'DashouProfile', None) group_info = self._huoqu_qun_peizhi() dashou_status = 1 if dashou_profile else 0 - shangjia_status = 1 if hasattr(user_main, 'shop_profile') else 0 + shangjia_status = 1 if hasattr(user_main, 'ShopProfile') else 0 guanshi_status = 1 try: - nicheng = user_main.boss_profile.nickname or '微信用户' + nicheng = user_main.BossProfile.nickname or '微信用户' except: nicheng = '微信用户' refresh = RefreshToken.for_user(user_main) @@ -10719,8 +10719,8 @@ class WechatLoginAndGuanshiRegisterView(APIView): data = { 'token': token, 'nicheng': nicheng, - 'uid': user_main.yonghuid, - 'touxiang': user_main.avatar or '', + 'uid': user_main.UserUID, + 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, @@ -10755,18 +10755,18 @@ class WechatLoginAndGuanshiRegisterView(APIView): def _zhuangbei_fanhui_shuju(self, user_main, dashou_profile, guanshi_profile, token): try: - nicheng = user_main.boss_profile.nickname or '微信用户' + nicheng = user_main.BossProfile.nickname or '微信用户' except: nicheng = '微信用户' dashou_status = 1 - shangjia_status = 1 if hasattr(user_main, 'shop_profile') else 0 + shangjia_status = 1 if hasattr(user_main, 'ShopProfile') else 0 guanshi_status = 1 group_info = self._huoqu_qun_peizhi() data = { 'token': token, 'nicheng': nicheng, - 'uid': user_main.yonghuid, - 'touxiang': user_main.avatar or '', + 'uid': user_main.UserUID, + 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, @@ -10812,7 +10812,7 @@ class GuanshiRegisterView(APIView): current_user = request.user # 2. 检查用户是否已是管事 - if hasattr(current_user, 'guanshi_profile'): + if hasattr(current_user, 'GuanshiProfile'): # 用户已是管事,直接返回现有信息 return self._fanhui_xianyou_guanshi_info(current_user) @@ -10827,12 +10827,12 @@ class GuanshiRegisterView(APIView): return Response({'code': 403, 'msg': '该邀请码对应的组长账号已被禁用', 'data': None}, status=status.HTTP_403_FORBIDDEN) - zuzhang_yonghuid = zuzhang.user.yonghuid + zuzhang_yonghuid = zuzhang.user.UserUID # 4. 事务内创建管事实体和打手(如果需要) with transaction.atomic(): # 创建管事扩展表 - guanshi_yaoqingma = CreateInvitationCode(str(current_user.yonghuid)) + guanshi_yaoqingma = CreateInvitationCode(str(current_user.UserUID)) guanshi_profile = UserGuanshi.query.create( user=current_user, yaoqingma=guanshi_yaoqingma, @@ -10855,7 +10855,7 @@ class GuanshiRegisterView(APIView): logger.error(f"组长每日统计更新失败(邀请管事):{str(e)}") # 检查用户是否已是打手,如果不是则创建 - if not hasattr(current_user, 'dashou_profile'): + if not hasattr(current_user, 'DashouProfile'): dashou_profile = UserDashou.query.create( user=current_user, nicheng='大手子', @@ -10863,7 +10863,7 @@ class GuanshiRegisterView(APIView): yaoqingren=zuzhang_yonghuid, ) else: - dashou_profile = current_user.dashou_profile + dashou_profile = current_user.DashouProfile # 5. 生成新token refresh = RefreshToken.for_user(current_user) @@ -10918,7 +10918,7 @@ class GuanshiRegisterView(APIView): # 会员列表 huiyuan_list = [] try: - records = Huiyuangoumai.query.filter(yonghu_id=dashou_profile.user.yonghuid) + records = Huiyuangoumai.query.filter(yonghu_id=dashou_profile.user.UserUID) for record in records: shifou_daoqi = record.jiance_shifou_daoqi() huiyuan_list.append({ @@ -10935,17 +10935,17 @@ class GuanshiRegisterView(APIView): def _fanhui_xianyou_guanshi_info(self, user_main): """用户已是管事时的返回""" - guanshi_profile = user_main.guanshi_profile - dashou_profile = getattr(user_main, 'dashou_profile', None) + guanshi_profile = user_main.GuanshiProfile + dashou_profile = getattr(user_main, 'DashouProfile', None) group_info = self._huoqu_qun_peizhi() dashou_status = 1 if dashou_profile else 0 - shangjia_status = 1 if hasattr(user_main, 'shop_profile') else 0 + shangjia_status = 1 if hasattr(user_main, 'ShopProfile') else 0 guanshi_status = 1 try: - nicheng = user_main.boss_profile.nickname or '微信用户' + nicheng = user_main.BossProfile.nickname or '微信用户' except: nicheng = '微信用户' @@ -10955,8 +10955,8 @@ class GuanshiRegisterView(APIView): data = { 'token': token, 'nicheng': nicheng, - 'uid': user_main.yonghuid, - 'touxiang': user_main.avatar or '', + 'uid': user_main.UserUID, + 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, @@ -10996,12 +10996,12 @@ class GuanshiRegisterView(APIView): def _zhuangbei_fanhui_shuju(self, user_main, dashou_profile, guanshi_profile, token): """组装完整返回数据(新注册)""" try: - nicheng = user_main.boss_profile.nickname or '微信用户' + nicheng = user_main.BossProfile.nickname or '微信用户' except: nicheng = '微信用户' dashou_status = 1 - shangjia_status = 1 if hasattr(user_main, 'shop_profile') else 0 + shangjia_status = 1 if hasattr(user_main, 'ShopProfile') else 0 guanshi_status = 1 group_info = self._huoqu_qun_peizhi() @@ -11009,8 +11009,8 @@ class GuanshiRegisterView(APIView): data = { 'token': token, 'nicheng': nicheng, - 'uid': user_main.yonghuid, - 'touxiang': user_main.avatar or '', + 'uid': user_main.UserUID, + 'touxiang': user_main.Avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, @@ -11082,7 +11082,7 @@ class ZuzhangZhuceView(APIView): return Response({'code': 400, 'msg': '邀请码无效'}, status=400) # 生成唯一组长邀请码:时间戳 + 用户ID后6位 + 随机字符 - yaoqingma = self._generate_unique_yaoqingma(user.yonghuid) + yaoqingma = self._generate_unique_yaoqingma(user.UserUID) # 创建组长扩展记录 try: @@ -11104,7 +11104,7 @@ class ZuzhangZhuceView(APIView): ) except IntegrityError: # 极低概率重复,重试一次 - yaoqingma = self._generate_unique_yaoqingma(user.yonghuid, retry=True) + yaoqingma = self._generate_unique_yaoqingma(user.UserUID, retry=True) zuzhang = UserZuzhang.query.create( user=user, yaoqingma=yaoqingma, @@ -11143,7 +11143,7 @@ class ZuzhangZhuceView(APIView): def _build_response_data(self, zuzhang, user): """组装前端所需数据""" # 计算邀请的管事数量(通过UserGuanshi表的yaoqingren字段关联到user的yonghuid) - guanshi_count = UserGuanshi.query.filter(yaoqingren=user.yonghuid).count() + guanshi_count = UserGuanshi.query.filter(yaoqingren=user.UserUID).count() return { 'ketixian': str(zuzhang.ketixian_jine), # 可提现余额 'guanshiCount': guanshi_count, # 邀请管事数 @@ -11169,7 +11169,7 @@ class ZuzhangXinxiView(APIView): except UserZuzhang.DoesNotExist: return Response({'code': 404, 'msg': '用户不是组长'}, status=404) - guanshi_count = UserGuanshi.query.filter(yaoqingren=user.yonghuid).count() + guanshi_count = UserGuanshi.query.filter(yaoqingren=user.UserUID).count() data = { 'ketixian': str(zuzhang.ketixian_jine), 'guanshiCount': guanshi_count, @@ -11206,7 +11206,7 @@ class DashouJianquanView(APIView): # ========== 2. 检查是否为已注册打手 ========== try: - dashou = user.dashou_profile # OneToOneField 反向查询 + dashou = user.DashouProfile # OneToOneField 反向查询 except UserDashou.DoesNotExist: return Response({ 'code': 0, @@ -11225,7 +11225,7 @@ class DashouJianquanView(APIView): # ========== 4. 条件二:存在未过期会员 ========== # 高效查询:利用 yonghu_id + huiyuan_zhuangtai 复合索引 huiyuan_list = Huiyuangoumai.query.filter( - yonghu_id=user.yonghuid, + yonghu_id=user.UserUID, huiyuan_zhuangtai=1 # 只查状态为"可用"的,减少内存遍历 ).only('id', 'daoqi_time', 'huiyuan_zhuangtai') @@ -11240,7 +11240,7 @@ class DashouJianquanView(APIView): # ========== 5. 条件三:存在订单状态为 2 或 8 的订单 ========== # 利用 jiedan_dashou_id + zhuangtai 复合索引,只查 1 条 has_valid_order = Order.query.filter( - PlayerID=user.yonghuid, + PlayerID=user.UserUID, Status__in=[2, 8] ).only('id').exists() # exists() 高性能判断,只发 SELECT 1 LIMIT 1 @@ -11287,7 +11287,7 @@ class LaobanJianquanView(APIView): # 利用 PlatformOrderExt.laoban_id 索引 + Order.zhuangtai 索引 # 只查 1 条,用 exists() 实现最高性能 has_valid_order = PlatformOrderExt.query.filter( - laoban_id=user.yonghuid + laoban_id=user.UserUID ).filter( dingdan__zhuangtai__in=[1, 2, 3, 4, 5, 6, 7, 8] ).only('id').exists() @@ -11322,7 +11322,7 @@ class FaKuanJiFenTongJiView(APIView): def post(self, request): try: user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID # ================= 罚款统计 ================= fadan_stats = Penalty.query.filter(PenalizedUserID=yonghuid).aggregate( @@ -11395,7 +11395,7 @@ class DaShouFaKuanLieBiaoView(APIView): def post(self, request): try: user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID # 获取分页参数 page = int(request.data.get('page', 1)) @@ -11507,7 +11507,7 @@ class FaKuanShenSuView(APIView): def post(self, request): try: user_main = request.user - yonghuid = user_main.yonghuid + yonghuid = user_main.UserUID fadan_id = request.data.get('fadan_id') shensu_liyou = request.data.get('shensu_liyou', '').strip() tupian_urls = request.data.get('shensu_tupian_urls', []) @@ -11524,8 +11524,8 @@ class FaKuanShenSuView(APIView): return Response({'code': 404, 'msg': '罚单不存在或无权操作'}, status=404) # 只允许待缴纳状态(状态1)且没有驳回理由的罚单申诉 - if fadan.Status != 1: - return Response({'code': 400, 'msg': '罚单状态不允许申诉'}, status=400) + if fadan.Status != 1: + return Response({'code': 400, 'msg': '罚单状态不允许申诉'}, status=400) if fadan.RejectReason: return Response({'code': 400, 'msg': '该罚单申诉已被驳回,不可再次申诉'}, status=400) @@ -11577,7 +11577,7 @@ class FaKuanPayView(APIView): return Response({'code': 400, 'message': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST) try: - fadan = Penalty.query.get(id=fadan_id, PenalizedUserID=request.user.yonghuid) + fadan = Penalty.query.get(id=fadan_id, PenalizedUserID=request.user.UserUID) except Penalty.DoesNotExist: return Response({'code': 404, 'message': '罚单不存在或不属于您'}, status=status.HTTP_404_NOT_FOUND) @@ -11594,7 +11594,7 @@ class FaKuanPayView(APIView): chongzhi_jilu = Czjilu.query.create( dingdan_id=dingdanid, zhuangtai=9, - yonghuid=request.user.yonghuid, + yonghuid=request.user.UserUID, jine=jine, leixing=4, shuoming=settings.FAKUAN_PAY_DESCRIPTION @@ -11604,7 +11604,7 @@ class FaKuanPayView(APIView): pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=jine, - openid=request.user.openid, + openid=request.user.OpenID, pay_type='fakuan' ) except Exception as e: @@ -11925,7 +11925,7 @@ class FaKuanPayPollView(APIView): if not dingdanid: return Response({'code': 400, 'message': '订单ID不能为空'}) - order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.yonghuid) + order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.UserUID) if order.Status == 3: return Response({'code': 200, 'message': '支付成功'}) else: @@ -11948,10 +11948,10 @@ class FaKuanPayFailView(APIView): if not dingdanid: return Response({'code': 400, 'message': '订单ID不能为空'}) - order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.yonghuid) + order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.UserUID) if order.Status == 9: # 未支付 order.delete() - logger.info(f"用户{request.user.yonghuid}删除未支付订单: {dingdanid}") + logger.info(f"用户{request.user.UserUID}删除未支付订单: {dingdanid}") return Response({'code': 200, 'message': '订单已删除'}) else: return Response({'code': 400, 'message': '订单状态不是未支付,无法删除'}) @@ -11990,20 +11990,20 @@ class KaohePayView(APIView): jilu_id = request.data.get('jilu_id') if reapply else None shenheguan_id = request.data.get('shenheguan_id', '').strip() - logger.info(f"用户 {user.yonghuid} 发起考核支付申请,标签ID: {tag_id}, 再次申请: {reapply}") + logger.info(f"用户 {user.UserUID} 发起考核支付申请,标签ID: {tag_id}, 再次申请: {reapply}") if not tag_id or not game_nick: - logger.warning(f"用户 {user.yonghuid} 参数不完整: tag_id={tag_id}, game_nick={game_nick}") + logger.warning(f"用户 {user.UserUID} 参数不完整: tag_id={tag_id}, game_nick={game_nick}") return Response({'code': 400, 'message': '标签ID和游戏昵称不能为空'}) # 1. 检查是否有未支付的考核订单 """unpaid_order = Czjilu.query.filter( - UserUID=user.yonghuid, + UserUID=user.UserUID, leixing=5, zhuangtai=9 ).exists() if unpaid_order: - logger.warning(f"用户 {user.yonghuid} 存在未支付的考核订单") + logger.warning(f"用户 {user.UserUID} 存在未支付的考核订单") return Response({'code': 400, 'message': '您有未支付的考核订单,请先完成支付或取消'})""" # 2. 验证标签 @@ -12016,33 +12016,33 @@ class KaohePayView(APIView): # 3. 检查用户是否已拥有该标签 if YonghuChenghao.query.filter(yonghu=user, chenghao=chenghao).exists(): - logger.warning(f"用户 {user.yonghuid} 已拥有标签 {chenghao.mingcheng}") + logger.warning(f"用户 {user.UserUID} 已拥有标签 {chenghao.mingcheng}") return Response({'code': 400, 'message': '您已拥有该标签,无需重复考核'}) # 4. 检查是否有未完成的申请 if ShenheJilu.query.filter( - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, chenghao=chenghao, zhuangtai__in=[0, 3] # 审核中、待审核 ).exists(): - logger.warning(f"用户 {user.yonghuid} 有未完成的考核申请") + logger.warning(f"用户 {user.UserUID} 有未完成的考核申请") return Response({'code': 400, 'message': '您有未完成的考核申请,请等待结果'}) # 5. 计算考核次数和费用 if reapply and jilu_id: last = ShenheJilu.query.filter( jilu_id=jilu_id, - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, zhuangtai=2 ).first() if not last: - logger.warning(f"用户 {user.yonghuid} 再次申请失败:原记录不存在或状态不是未通过,jilu_id={jilu_id}") + logger.warning(f"用户 {user.UserUID} 再次申请失败:原记录不存在或状态不是未通过,jilu_id={jilu_id}") return Response({'code': 400, 'message': '原记录不存在或状态不是未通过'}) new_cishu = last.cishu + 1 logger.info(f"再次申请,原记录ID: {jilu_id},新次数: {new_cishu}") else: max_cishu = ShenheJilu.query.filter( - shenqingren_id=user.yonghuid, + shenqingren_id=user.UserUID, chenghao=chenghao, zhuangtai__in=[1, 2] ).aggregate(max=models.Max('cishu'))['max'] or 0 @@ -12068,7 +12068,7 @@ class KaohePayView(APIView): order = Czjilu.query.create( dingdan_id=dingdanid, zhuangtai=9, # 未支付 - yonghuid=user.yonghuid, + yonghuid=user.UserUID, jine=float(fee), leixing=5, # 5-考核支付 shuoming=settings.KAOHE_PAY_DESCRIPTION @@ -12079,7 +12079,7 @@ class KaohePayView(APIView): try: KaohePayTemp.query.create( dingdan_id=dingdanid, - yonghuid=user.yonghuid, + yonghuid=user.UserUID, tag_id=tag_id, game_nick=game_nick, remark=remark, @@ -12105,7 +12105,7 @@ class KaohePayView(APIView): pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=float(fee), - openid=user.openid, + openid=user.OpenID, attach=attach_str, pay_type='kaohe' ) @@ -12364,7 +12364,7 @@ class KaohePayCallbackView(View): logger.info(f"找到临时表记录: {out_trade_no}, 用户ID: {order.yonghuid}") user = User.query.get(UserUID=order.yonghuid) - logger.info(f"获取用户信息成功: {user.yonghuid}") + logger.info(f"获取用户信息成功: {user.UserUID}") success, msg, record = create_shenhe_jilu_from_temp(user, temp) if success: @@ -12480,7 +12480,7 @@ class KaohePayPollView(APIView): if not dingdanid: return Response({'code': 400, 'message': '订单ID不能为空'}) try: - order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.yonghuid, leixing=5) + order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.UserUID, leixing=5) if order.Status == 3: return Response({'code': 200, 'message': '支付成功'}) return Response({'code': 400, 'message': '订单尚未支付'}) @@ -12497,7 +12497,7 @@ class KaohePayFailView(APIView): if not dingdanid: return Response({'code': 400, 'message': '订单ID不能为空'}) try: - order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.yonghuid, leixing=5) + order = Czjilu.query.get(dingdan_id=dingdanid, yonghuid=request.user.UserUID, leixing=5) if order.Status == 9: order.delete() return Response({'code': 200, 'message': '订单已删除'}) @@ -12559,7 +12559,7 @@ class TixianAssetView(APIView): # ------------------- 打手资产 ------------------- try: - dashou = user.dashou_profile + dashou = user.DashouProfile data['dashou_yue'] = str(dashou.yue) data['dashou_yajin'] = str(dashou.yajin) except ObjectDoesNotExist: @@ -12567,28 +12567,28 @@ class TixianAssetView(APIView): # ------------------- 商家资产 ------------------- try: - shangjia = user.shop_profile + shangjia = user.ShopProfile data['shangjia_yue'] = str(shangjia.yue) except ObjectDoesNotExist: pass # ------------------- 管事资产 ------------------- try: - guanshi = user.guanshi_profile + guanshi = user.GuanshiProfile data['guanshi_fenyong'] = str(guanshi.yue) except ObjectDoesNotExist: pass # ------------------- 组长资产 ------------------- try: - zuzhang = user.zuzhang_profile + zuzhang = user.ZuzhangProfile data['zuzhang_fenyong'] = str(zuzhang.ketixian_jine) except ObjectDoesNotExist: pass # ------------------- 审核官(考核官)资产 ------------------- try: - kaoheguan = user.shenheguan_profile + kaoheguan = user.ShenheguanProfile data['kaoheguan_fenyong'] = str(kaoheguan.yue) except ObjectDoesNotExist: pass @@ -12651,7 +12651,7 @@ class GuanshiContactView(APIView): # 2. 校验是否为管事身份(存在管事扩展表记录) try: - guanshi_profile = current_user.guanshi_profile + guanshi_profile = current_user.GuanshiProfile except ObjectDoesNotExist: return Response({ 'code': 403, @@ -12681,15 +12681,15 @@ class GuanshiContactView(APIView): # 5. 获取邀请人昵称(从老板扩展表取,若不存在则给默认值) try: - inviter_nicheng = inviter_user.boss_profile.nickname + inviter_nicheng = inviter_user.BossProfile.nickname except ObjectDoesNotExist: inviter_nicheng = '组长' # 兜底昵称 # 6. 构造返回数据 response_data = { - 'uid': inviter_user.yonghuid, + 'uid': inviter_user.UserUID, 'nicheng': inviter_nicheng, - 'touxiang': inviter_user.avatar or '' # 头像相对路径 + 'touxiang': inviter_user.Avatar or '' # 头像相对路径 } return Response({ @@ -12849,7 +12849,7 @@ class TixianQueRenAutoView(APIView): try: auto_record = TixianAutoRecord.objects.select_for_update().get( tixian_id=tixian_id, - yonghuid=user_main.yonghuid, + yonghuid=user_main.UserUID, ) except TixianAutoRecord.DoesNotExist: return Response({'code': 4, 'msg': '提现记录不存在'}, status=status.HTTP_404_NOT_FOUND) diff --git a/utils/chat_utils.py b/utils/chat_utils.py index 9cd4b55..58375e6 100644 --- a/utils/chat_utils.py +++ b/utils/chat_utils.py @@ -110,7 +110,7 @@ def establish_order_chat(dingdan_id): dashou_avatar_relative = '' try: dashou_user = User.objects.get(UserUID=dashou_uid) - dashou_avatar_relative = dashou_user.avatar or '' + dashou_avatar_relative = dashou_user.Avatar or '' dashou_profile = UserDashou.objects.get(user=dashou_user) if dashou_profile.nicheng: dashou_nickname = dashou_profile.nicheng @@ -167,7 +167,7 @@ def _get_local_partner_info(order): nickname = f'老板{laoban_id[:6]}' try: boss_user = User.objects.get(UserUID=laoban_id) - avatar_full = _full_local_avatar(boss_user.avatar or '') + avatar_full = _full_local_avatar(boss_user.Avatar or '') boss_profile = UserBoss.objects.get(user=boss_user) if boss_profile.nickname: nickname = boss_profile.nickname @@ -185,7 +185,7 @@ def _get_local_partner_info(order): avatar_full = _full_local_avatar('') try: sj_user = User.objects.get(UserUID=shangjia_id) - avatar_full = _full_local_avatar(sj_user.avatar or '') + avatar_full = _full_local_avatar(sj_user.Avatar or '') except Exception: pass return f"Sj{shangjia_id}", nickname, avatar_full