瀏覽代碼

代理后台接口、获取UID加打印

zhangdongming 4 月之前
父節點
當前提交
53eb20c2d1

+ 986 - 0
AdminController/CloudServiceManage/AgentCustomerController.py

@@ -0,0 +1,986 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : AgentCustomerController.py
+@Time    : 2024/3/7 16:56
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import re
+import time
+import json
+from decimal import Decimal
+
+from django.db.models import Sum
+from django.http import QueryDict
+from django.views import View
+from django.core.paginator import Paginator
+from datetime import datetime, timedelta
+
+from AgentModel.models import AgentCustomerInfo, AgentCustomerCard, AgentCustomerPackage, AgentCloudServicePackage, \
+    AgentDeviceOrder, AgentAccountWithdraw, AgentDevice, AgentAccount, ApplyAgent, AgentDeviceOrderInstallment
+from Model.models import UnicomCombo, Store_Meal, Device_User
+
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+
+
+class AgentCustomerView(View):
+    def get(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.GET, request, operation)
+
+    def post(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.POST, request, operation)
+
+    def delete(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        delete = QueryDict(request.body)
+        if not delete:
+            delete = request.GET
+        return self.validation(delete, request, operation)
+
+    def put(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        put = QueryDict(request.body)
+        return self.validation(put, request, operation)
+
+    def validation(self, request_dict, request, operation):
+        AgentCustomerInfo.objects.filter()
+        language = request_dict.get('language', 'en')
+        response = ResponseObject(language, 'pc')
+        if operation == 'getUnicomAndIcloud':
+            return self.get_unicom_and_icloud(response)
+        elif operation == 'applyAgent':
+            return self.apply_agent(request_dict, response)
+        else:
+            tko = TokenObject(
+                request.META.get('HTTP_AUTHORIZATION'),
+                returntpye='pc')
+            if tko.code != 0:
+                return response.json(tko.code)
+            response.lang = tko.lang
+            userID = tko.userID
+            # 代理用户界面(代理顾客个人信息)
+            if operation == 'getAgentInfo':
+                return self.get_agent_info(userID, response)
+
+            # 代理云存套餐
+            elif operation == 'getAgentServicePackage':
+                return self.get_agent_service_package(request_dict, response)
+            elif operation == 'addAgentServicePackage':
+                return self.add_agent_service_package(userID, request_dict, response)
+            elif operation == 'updateAgentServicePackage':
+                return self.update_agent_service_package(userID, request_dict, response)
+            elif operation == 'delAgentServicePackage':
+                return self.del_agent_service_package(request_dict, response)
+
+            # 代理客户绑定套餐
+            elif operation == 'getCustomerList':
+                return self.get_customer_list(request_dict, response)
+            elif operation == 'getCustomerPackageList':
+                return self.get_cumstomer_package_list(request_dict, response)
+            elif operation == 'batchRebindCustomerPackage':
+                return self.batch_rebind_customer_packages(userID, request_dict, response)
+            elif operation == 'getAgentServicePackageList':
+                return self.get_agent_service_package_list(response)
+
+            elif operation == 'getAgentSettleOrders':
+                return self.get_agent_settle_order(userID, request_dict, response)
+            elif operation == 'getDataStatistics':
+                return self.get_data_statistics(userID, response)
+
+            # 代理商提现功能
+            elif operation == 'getAgentAccountWithdraw':
+                return self.get_agent_account_withdraw(userID, request_dict, response)
+            elif operation == 'getCheckBalance':
+                return self.get_check_balance(userID, response)
+            elif operation == 'agentApplyWithdraw':
+                return self.agent_apply_withdraw(userID, request_dict, response)
+            elif operation == 'getWithdrawalReview':
+                return self.get_withdrawal_review(request_dict, response)
+            elif operation == 'updateWithdrawalReview':
+                return self.update_withdrawal_review(userID, request_dict, response)
+            else:
+                return response.json(444, 'operation')
+
+    def get_unicom_and_icloud(self, response):
+        """
+        查询云存储套餐和物联网卡套餐列表
+        @param response: 响应对象
+        @return:
+        """
+        try:
+            # 云存储套餐查询,只包括is_show=0的记录
+            store_meals = Store_Meal.objects.filter(is_show=0).values('id', 'bucket__bucket').distinct()
+
+            # 联通套餐查询,只包括is_show=1且未被删除的记录
+            unicom_combos = UnicomCombo.objects.filter(is_show=1, is_del=False).values('id', 'combo_name').distinct()
+
+            # 将查询结果转换为列表
+            store_meal_list = [{'id': meal['id'], 'name': meal['bucket__bucket']} for meal in store_meals]
+            unicom_combo_list = [{'id': combo['id'], 'name': combo['combo_name']} for combo in unicom_combos]
+
+            # 合并结果并返回
+            return response.json(0, {
+                'storeMeals': store_meal_list,
+                'unicomCombos': unicom_combo_list,
+            })
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def get_agent_info(self, userID, response):
+        """
+        查询用户信息
+        @param userID: userID
+        @param response: 响应对象
+        @return:
+        """
+        try:
+            # 使用userID查询AgentCustomerInfo获取基本信息
+            agent_info = AgentCustomerInfo.objects.filter(user_id=userID, status=1).first()
+            if not agent_info:
+                return response.json(444, {'error': '没有找到这个代理用户'})
+            # userID查询用户电话
+            device_user_qs = Device_User.objects.filter(userID=userID).first()
+
+            # 代理客户ID(ac_id)查询AgentCustomerCard获取银行卡信息。
+            card_details = AgentCustomerCard.objects.filter(ac_id=agent_info.id, status=1).first()
+
+            # ac_id查询AgentCustomerPackage来找到关联的云服务套餐ID(cs_id)
+            package_ids = AgentCustomerPackage.objects.filter(ac_id=agent_info.id).values_list('cs_id',
+                                                                                               flat=True)
+            # cs_id查询AgentCloudServicePackage获取服务套餐详情。
+            service_packages = AgentCloudServicePackage.objects.filter(id__in=package_ids, status=1).values()
+
+            result = {
+                'agent_info': {
+                    'ac_id': agent_info.id,
+                    'company_name': agent_info.company_name,
+                    'phone': device_user_qs.phone,
+                    'created_time': agent_info.created_time,
+                    'service_packages': list(service_packages),
+                }
+            }
+
+            if card_details:
+                # 获取银行卡号
+                card_no = card_details.card_no
+                # 检查银行卡号字符串长度,如果小于等于8,则返回"null"
+                if len(card_no) <= 8:
+                    masked_card_no = "null"
+                else:
+                    # 保留前四位和后四位,用"*"代替中间的数字
+                    masked_card_no = card_no[:4] + '*' * (len(card_no) - 8) + card_no[-4:]
+
+                result['agent_info'].update({
+                    'card_name': card_details.name,
+                    'card_no': masked_card_no,
+                    'card_address': card_details.card_address,
+                })
+
+            return response.json(0, result)
+        except Exception as e:
+            return response.json(500, {'error': str(e)})
+
+    def get_agent_service_package(self, request_dict, response):
+        """
+        查询所有代理云服务套餐
+        @param request_dict: 请求参数
+        @request_dict page: 页码
+        @request_dict page_size: 查询分页数
+        @param response: 响应对象
+        @return:
+        """
+        page = int(request_dict.get('page', 1))
+        page_size = int(request_dict.get('page_size', 10))
+        try:
+            # 查询所有有效的代理云服务套餐
+            all_packages = AgentCloudServicePackage.objects.filter(status=1).order_by('type', '-created_time')
+            # 创建分页对象
+            paginator = Paginator(all_packages, page_size)
+            # 获取请求页的数据
+            packages_page = paginator.page(page)
+            # 准备响应数据,转换查询集为列表形式
+            agents_list = []
+            for agent_info in packages_page:
+                package_name = agent_info.package_id
+                if agent_info.type == 1:
+                    store_meals = Store_Meal.objects.filter(id=agent_info.package_id).values('bucket__bucket').first()
+                    if store_meals:
+                        package_name = store_meals['bucket__bucket']
+                else:
+                    unicom_combos = UnicomCombo.objects.filter(id=agent_info.package_id).first()
+                    if unicom_combos:
+                        package_name = unicom_combos.combo_name
+                agents = {
+                    'id': agent_info.id,
+                    'service_name': agent_info.service_name,
+                    'package_name': package_name,
+                    'type': agent_info.type,
+                    'profit_type': agent_info.profit_type,
+                    'cost': agent_info.cost,
+                    'profit': agent_info.profit,
+                    'status': agent_info.status,
+                    'created_time': agent_info.created_time
+                }
+                agents_list.append(agents)
+            # 返回分页数据
+            return response.json(0, {
+                'page': page,
+                'page_size': page_size,
+                'total': paginator.count,
+                'num_pages': paginator.num_pages,
+                'list': agents_list
+            })
+        except Exception as e:
+            # 出错时返回错误信息
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def add_agent_service_package(self, userID, request_dict, response):
+        """
+        添加代理云服务套餐
+        @param request_dict: 请求参数
+        @request_dict package_id: 代理服务id
+        @request_dict service_name: 代理服务名称
+        @request_dict package_type: 套餐类型 1:云存,2:4G
+        @response_dict profit_type: 利润分配类型 1:固定值,2:百分比
+        @response_dict cost: 成本
+        @response_dict profit: 利润值
+        @param response: 响应对象
+        @return:
+        """
+        package_id = request_dict.get('package_id', None)
+        service_name = request_dict.get('service_name', None)
+        package_type = int(request_dict.get('package_type', 0))  # 默认为0,确保类型安全
+        profit_type = int(request_dict.get('profit_type', 1))  # 默认值为1
+        profit = request_dict.get('profit', 0)
+        cost = request_dict.get('cost', 0)
+
+        try:
+            # 创建AgentCloudServicePackage实例并保存
+            if not all([package_id, service_name]):
+                return response.json(444)
+            if package_type == 1:
+                query_set = Store_Meal.objects.filter(is_show=0, id=package_id)
+            elif package_type == 2:
+                query_set = UnicomCombo.objects.filter(is_show=1, is_del=False, id=package_id)
+            else:
+                return response.json(444, 'error package_type')
+            if not query_set.exists():
+                return response.json(173)
+            AgentCloudServicePackage.objects.create(
+                service_name=service_name,
+                package_id=package_id,
+                type=package_type,
+                profit_type=profit_type,
+                profit=profit,
+                status=1,
+                cost=cost,
+                created_by=userID,
+                created_time=int(time.time()),
+                updated_by=userID,
+                updated_time=int(time.time())
+            )
+            return response.json(0)
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def update_agent_service_package(self, userID, request_dict, response):
+        """
+        更新代理云服务套餐
+        @param request_dict: 请求参数
+        @request_dict package_id: 代理服务id
+        @request_dict service_name: 代理服务名称
+        @response_dict profit_type: 利润分配类型 1:固定值,2:百分比
+        @response_dict cost: 成本
+        @response_dict profit: 利润值
+        @param response: 响应对象
+        @return:
+        """
+        id = request_dict.get('id', None)
+        service_name = request_dict.get('service_name', None)
+        profit_type = request_dict.get('profit_type', None)
+        cost = request_dict.get('cost', None)
+        profit = request_dict.get('profit', None)
+
+        if not all([id, service_name, profit_type, cost, profit]):
+            return response.json(444)
+
+        try:
+            ac_service_package = AgentCloudServicePackage.objects.get(pk=id)
+            ac_service_package.service_name = service_name
+            ac_service_package.profit_type = profit_type
+            ac_service_package.cost = cost
+            ac_service_package.profit = profit
+            ac_service_package.updated_time = int(time.time())
+            ac_service_package.updated_by = userID
+            ac_service_package.save()
+            return response.json(0)
+
+        except AgentCloudServicePackage.DoesNotExist:
+            return response.json(173)
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def del_agent_service_package(self, request_dict, response):
+        """
+        删除代理云服务套餐
+        @param userID: 用户ID(执行删除操作的用户)
+        @param request_dict: 请求参数
+        @request_dict package_id: 代理服务id
+        @param response: 响应对象
+        @return:
+        """
+        id = request_dict.get('id', None)
+        if not id:
+            return response.json(444, 'Missing package_id')
+        try:
+            ac_service_package = AgentCloudServicePackage.objects.get(pk=id)
+            # 假删除,否则查利润会出问题
+            ac_service_package.status = 0
+            ac_service_package.save()
+            return response.json(0)
+
+        except AgentCloudServicePackage.DoesNotExist:
+            return response.json(173, 'Package does not exist.')
+
+        except Exception as e:
+            return response.json(500, {'error': str(e)})
+
+    def get_customer_list(self, request_dict, response):
+        """
+        查询代理商信息,并进行分页处理
+        @param request_dict: 请求对象,用于接收分页参数
+        @param response: 响应对象
+        @return:
+        """
+        # 接收分页参数
+        page = int(request_dict.get('page', 1))
+        page_size = int(request_dict.get('page_size', 10))
+        search_query = request_dict.get('search_query', '')
+        user_id = request_dict.get('user_id', None)
+        phone = request_dict.get('phone', None)
+        userEmail = request_dict.get('userEmail', None)
+
+        try:
+            # 基础查询条件
+            agent_infos_query = AgentCustomerInfo.objects.filter(status=1)
+
+            # 精确查询条件
+            if user_id:
+                agent_infos_query = agent_infos_query.filter(user_id=user_id)
+
+            # 获取所有符合条件的AgentCustomerInfo记录
+            agent_infos = agent_infos_query.values('id', 'user_id', 'company_name').order_by('id')
+
+            # 对结果进行分页
+            paginator = Paginator(agent_infos, page_size)
+            packages_page = paginator.page(page)
+
+            # 准备最终的代理商列表
+            agents_list = []
+            for agent_info in packages_page:
+                # 模糊查询
+                if search_query:
+                    card_info_query = AgentCustomerCard.objects.filter(ac_id=agent_info['id'],
+                                                                       name__icontains=search_query)
+                    if not card_info_query.exists() and search_query not in agent_info['company_name']:
+                        continue
+                else:
+                    card_info_query = AgentCustomerCard.objects.filter(ac_id=agent_info['id'])
+
+                # 获取卡信息
+                card_info = card_info_query.values('name', 'card_no', 'card_address').first()
+
+                # 获取用户信息,根据需要进行精确查询
+                user_info_query = Device_User.objects.filter(userID=agent_info['user_id'])
+                if phone:
+                    user_info_query = user_info_query.filter(phone=phone)
+                if userEmail:
+                    user_info_query = user_info_query.filter(userEmail=userEmail)
+
+                user_info = user_info_query.values('phone', 'userEmail').first()
+
+                # 组合信息
+                agent_record = {
+                    'id': agent_info['id'],
+                    'company_name': agent_info['company_name'],
+                    'user_id': agent_info['user_id'],
+                    'phone': user_info.get('phone') if user_info else None,
+                    'userEmail': user_info.get('userEmail') if user_info else None,
+                    'card_name': card_info.get('name') if card_info else None,
+                    'card_no': card_info.get('card_no') if card_info else None,
+                    'card_address': card_info.get('card_address') if card_info else None,
+                }
+                agents_list.append(agent_record)
+
+            # 返回分页后的结果
+            return response.json(0, {
+                'page': page,
+                'page_size': page_size,
+                'total': paginator.count,
+                'num_pages': paginator.num_pages,
+                'list': agents_list
+            })
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def get_cumstomer_package_list(self, request_dict, response):
+        """
+        查询代理商服务套餐列表
+        @param response: 响应对象
+        @return:
+        """
+        ac_id = request_dict.get('id', None)
+        try:
+            if ac_id is None:
+                return response.json(444, 'Missing ac_id')
+            # ac_id查询AgentCustomerPackage来找到关联的云服务套餐ID(cs_id)
+            package_ids = AgentCustomerPackage.objects.filter(ac_id=ac_id).values_list('cs_id', flat=True)
+            # cs_id查询AgentCloudServicePackage获取服务套餐详情。
+            service_packages = AgentCloudServicePackage.objects.filter(id__in=package_ids, status=1).values(
+                'id', 'service_name', 'type'
+            )
+            service_packages_list = list(service_packages)
+            return response.json(0, {'service_packages': service_packages_list})
+
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def batch_rebind_customer_packages(self, userID, request_dict, response):
+        """
+        清空并重新绑定代理商服务套餐
+        @param userID: 操作用户的ID
+        @param request_dict: 请求参数,包含代理商ID和新的服务套餐ID列表
+        @param response: 响应对象
+        @return:
+        """
+        ac_id = request_dict.get('ac_id', None)  # 代理客户ID
+        new_cs_ids = json.loads(request_dict.get('cs_ids', '[]'))  # 新的服务套餐ID列表
+
+        if not ac_id:
+            return response.json(444, 'Missing agent customer ID.')
+        if not new_cs_ids:
+            return response.json(444, 'Service package IDs are required.')
+
+        try:
+            # 删除该代理客户的所有现有绑定
+            AgentCustomerPackage.objects.filter(ac_id=ac_id).delete()
+
+            # 过滤出存在且状态为有效的套餐ID
+            valid_new_cs_ids = AgentCloudServicePackage.objects.filter(id__in=new_cs_ids, status=1).values_list('id',
+                                                                                                                flat=True)
+            # 准备批量创建的数据
+            packages_to_bind = [
+                AgentCustomerPackage(
+                    ac_id=ac_id,
+                    cs_id=cs_id,
+                    created_by=userID,
+                    updated_by=userID,
+                    created_time=int(time.time()),
+                    updated_time=int(time.time())
+                )
+                for cs_id in valid_new_cs_ids
+            ]
+
+            # 批量创建新的绑定关系
+            AgentCustomerPackage.objects.bulk_create(packages_to_bind)
+
+            return response.json(0)
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def get_agent_service_package_list(self, response):
+        """
+        查询云服务套餐列表 id + service_name
+        @param response: 响应对象
+        @return:
+        """
+        try:
+            # 查询所有有效的代理云服务套餐
+            all_packages = AgentCloudServicePackage.objects.filter(status=1).order_by('-created_time').values('id',
+                                                                                                              'service_name')
+            # 转换查询集为列表形式
+            packages_list = list(all_packages)
+            # 返回数据
+            return response.json(0, {'packages': packages_list})
+        except Exception as e:
+            # 出错时返回错误信息
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def get_agent_settle_order(self, userID, request_dict, response):
+        """
+        查询结算明细
+        @param userID: userID
+        @param request_dict: 请求参数
+        @param request_dict status: 结算状态
+        @param request_dict time: 年-季度 2023-1
+        @param response: 响应对象
+        @return:
+        """
+
+        status = request_dict.get('status', None)
+        time_str = request_dict.get('time', None)
+        package_type = request_dict.get('package_type', None)
+        startTime = int(request_dict.get('start_time', 1))
+        endTime = int(request_dict.get('end_time', 0))
+
+        page = int(request_dict.get('page', 1))  # 默认为第一页
+        page_size = int(request_dict.get('page_size', 10))  # 默认每页10条记录
+
+        try:
+            agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
+            if agent_customer_info is None:
+                agent_device_orders_qs = AgentDeviceOrder.objects.filter(is_del=False)
+            else:
+                ac_id = agent_customer_info.id
+                agent_device_orders_qs = AgentDeviceOrder.objects.filter(ac_id=ac_id, is_del=False)
+
+            if time_str:
+                year, quarter = map(int, time_str.split('-'))
+                start_month = 3 * (quarter - 1) + 1
+                end_month = start_month + 2
+
+                start_date = datetime(year, start_month, 1)
+                end_date = datetime(year + 1, 1, 1) if end_month == 12 else datetime(year, end_month + 1, 1)
+
+                start_time = int(start_date.timestamp())
+                end_time = int(end_date.timestamp()) - 1
+                agent_device_orders_qs = agent_device_orders_qs.filter(created_time__gte=start_time,
+                                                                       created_time__lte=end_time)
+            if startTime < endTime:
+                agent_device_orders_qs = agent_device_orders_qs.filter(created_time__gte=startTime,
+                                                                       created_time__lte=endTime)
+            if package_type:
+                csp_ids = list(
+                    AgentCloudServicePackage.objects.filter(type=int(package_type)).values_list('id', flat=True))
+                agent_device_orders_qs = agent_device_orders_qs.filter(csp_id__in=csp_ids)
+
+            if status is None:
+                total_profit = agent_device_orders_qs.aggregate(Sum('profit'))['profit__sum'] or 0
+            else:
+                # 计算特定状态的device_orders总额
+                full_profit = agent_device_orders_qs.filter(status=status).aggregate(Sum('profit'))[
+                                  'profit__sum'] or 0
+
+                # 初始化total_profit
+                total_profit = full_profit
+
+                # 对于状态1和2,计算部分结算的利润
+                if status in ["1", "2"]:
+                    partial_settled_profit = AgentDeviceOrderInstallment.objects.filter(
+                        ado_id__in=agent_device_orders_qs.filter(status=1).values_list('id', flat=True),
+                        status=2
+                    ).aggregate(total=Sum('amount'))['total'] or 0
+
+                    # 根据状态调整total_profit
+                    if status == "1":
+                        total_profit -= partial_settled_profit
+                    else:  # 当status为"2"时,添加partial_settled_profit(根据业务逻辑,这可能需要调整)
+                        total_profit += partial_settled_profit
+
+                agent_device_orders_qs = agent_device_orders_qs.filter(status=status)
+
+            # 应用分页
+            agent_device_orders_qs = agent_device_orders_qs.order_by('-created_time')
+            paginator = Paginator(agent_device_orders_qs, page_size)
+            current_page = paginator.get_page(page)
+
+            orders = []
+            for order in current_page:
+                csp = AgentCloudServicePackage.objects.filter(id=order.csp_id).first()
+                service_name = csp.service_name if csp else order.csp_id
+                agent_customer_info = AgentCustomerInfo.objects.filter(id=order.ac_id).first()
+                company_name = agent_customer_info.company_name if agent_customer_info else order.ac_id
+                ado_id = order.id
+                settled_profit = \
+                    AgentDeviceOrderInstallment.objects.filter(ado_id=ado_id, status=2).aggregate(Sum('amount'))[
+                        'amount__sum'] or 0
+                orders.append({
+                    'id': order.id,
+                    'company_name': company_name,
+                    'serial_number': order.serial_number,
+                    'status': order.status,
+                    'service_name': service_name,
+                    'profit_amount': order.profit_amount,
+                    'profit': order.profit,
+                    'settled_profit': settled_profit,
+                    'settlement_time': order.settlement_time,
+                    'remark': order.remark,
+                    'created_time': order.created_time
+                })
+
+            response_data = {
+                'list': orders,
+                'total_profit': total_profit,
+                'total': paginator.count,
+                'page': current_page.number,
+                'page_size': page_size,
+                'num_pages': paginator.num_pages,
+            }
+
+            return response.json(0, response_data)
+
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def get_agent_account_withdraw(self, userID, request_dict, response):
+        """
+        查询提现明细
+        @param userID: userID
+        @param request_dict: 请求参数
+        @param response: 响应对象
+        @return:
+        """
+        agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
+        if not agent_customer_info:
+            return response.json(104, 'Agent customer not found')
+
+        ac_id = agent_customer_info.id
+        try:
+            agent_account_withdraw_qs = AgentAccountWithdraw.objects.filter(ac_id=ac_id).order_by('-updated_time')
+
+            page = int(request_dict.get('page', 1))  # 默认为第一页
+            page_size = int(request_dict.get('page_size', 10))  # 默认每页10条记录
+
+            # 应用分页
+            paginator = Paginator(agent_account_withdraw_qs, page_size)
+            current_page = paginator.get_page(page)
+
+            withdraw_list = []
+
+            for withdraw in current_page:
+                agent_customer_card = AgentCustomerCard.objects.filter(ac_id=ac_id).first()
+                card_no = agent_customer_card.card_no
+                masked_card_no = card_no[:4] + '*' * (len(card_no) - 8) + card_no[-4:]
+                withdraw_list.append({
+                    'id': withdraw.id,
+                    'amount': withdraw.amount,
+                    'created_time': withdraw.created_time,
+                    'card_no': masked_card_no,
+                    'status': withdraw.status,
+                    'remark': withdraw.remark,
+                    'arrival_time': withdraw.arrival_time
+                })
+
+            response_data = {
+                'list': withdraw_list,
+                'total': paginator.count,
+                'page': current_page.number,
+                'page_size': page_size,
+                'num_pages': paginator.num_pages,
+            }
+            return response.json(0, response_data)
+
+        except Exception as e:
+            # 出错时返回错误信息
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def get_check_balance(self, userID, response):
+        """
+        查询余额
+        @param userID: userID
+        @param response: 响应对象
+        @return:
+        """
+
+        agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
+        if not agent_customer_info:
+            return response.json(104, 'Agent customer not found')
+
+        ac_id = agent_customer_info.id
+
+        try:
+            # 计算冻结金额
+            frozen_amount_qs = AgentAccountWithdraw.objects.filter(ac_id=ac_id, status__in=[1, 2, 3])
+            frozen_amount = frozen_amount_qs.aggregate(total=Sum('amount'))['total'] or Decimal('0.00')
+
+            # 计算余额:已结算 - (退款+已打款)
+            incomes_qs = AgentAccount.objects.filter(ac_id=ac_id, status=1)
+            expense_qs = AgentAccount.objects.filter(ac_id=ac_id, status__in=[2, 3])
+            incomes_all = incomes_qs.aggregate(total=Sum('amount'))['total'] or Decimal('0.00')
+            expense_all = expense_qs.aggregate(total=Sum('amount'))['total'] or Decimal('0.00')
+            total_profit = incomes_all - expense_all
+
+            # 可用余额 = 总余额 - 冻结金额
+            available_balance = total_profit - frozen_amount
+
+            # 构造返回数据
+            balance_data = {
+                'frozen_amount': frozen_amount,  # 冻结金额
+                'total_profit': total_profit,  # 总余额
+                'available_balance': available_balance  # 可用余额
+            }
+
+            return response.json(0, balance_data)
+
+        except Exception as e:
+            # 出错时返回错误信息
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def get_data_statistics(self, userID, response):
+        """
+        首页总览
+        @param userID: userID
+        @param response: 响应对象
+        @return:
+        """
+        agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
+        if agent_customer_info is None:
+            agent_device_orders_qs = AgentDeviceOrder.objects.filter(is_del=False)
+            agent_device_qs = AgentDevice.objects.filter()
+        else:
+            ac_id = agent_customer_info.id
+            agent_device_orders_qs = AgentDeviceOrder.objects.filter(ac_id=ac_id, is_del=False)
+            agent_device_qs = AgentDevice.objects.filter(ac_id=ac_id)
+
+        now = datetime.now()
+        today_start = datetime(now.year, now.month, now.day)
+        yesterday_start = today_start - timedelta(days=1)
+        tomorrow_start = today_start + timedelta(days=1)
+
+        try:
+            # 计算AgentDeviceOrderInstallment表 结算金额
+            partial_settled_profit = AgentDeviceOrderInstallment.objects.filter(
+                ado_id__in=agent_device_orders_qs.filter(status=1).values_list('id', flat=True),
+                status=2
+            ).aggregate(total=Sum('amount'))['total'] or 0
+
+            # 总利润
+            total_profit_all = \
+                agent_device_orders_qs.filter(status__in=[1, 2], is_del=False).aggregate(
+                    total=Sum('profit'))[
+                    'total'] or Decimal('0.00')
+            total_profit_no = \
+                agent_device_orders_qs.filter(status=1, is_del=False).aggregate(total=Sum('profit'))[
+                    'total'] or Decimal('0.00')
+            total_profit_no = total_profit_no - partial_settled_profit
+
+            total_profit_yes = \
+                agent_device_orders_qs.filter(status=2, is_del=False).aggregate(total=Sum('profit'))[
+                    'total'] or Decimal('0.00')
+            total_profit_yes = total_profit_yes + partial_settled_profit
+
+            # 总营业额
+            profit_amount_all = agent_device_orders_qs.filter(status__in=[1, 2], is_del=False).aggregate(
+                total=Sum('profit_amount'))['total'] or Decimal('0.00')
+
+            # 今日总营业额
+            profit_amount_today_all = agent_device_orders_qs.filter(
+                status__in=[1, 2],
+                is_del=False,
+                created_time__gte=int(today_start.timestamp()),
+                created_time__lt=int(tomorrow_start.timestamp())
+            ).aggregate(total=Sum('profit_amount'))['total'] or Decimal('0.00')
+
+            # 昨日总营业额
+            profit_amount_yesterday_all = agent_device_orders_qs.filter(
+                status__in=[1, 2],
+                is_del=False,
+                created_time__gte=int(yesterday_start.timestamp()),
+                created_time__lt=int(today_start.timestamp())
+            ).aggregate(total=Sum('profit_amount'))['total'] or Decimal('0.00')
+
+            # 激活设备数
+            active_device_count_today = agent_device_qs.filter(
+                status=1,
+                created_time__gte=int(today_start.timestamp()),
+                created_time__lt=int(tomorrow_start.timestamp())
+            ).count()
+            active_device_count_yesterday = agent_device_qs.filter(
+                status=1,
+                created_time__gte=int(yesterday_start.timestamp()),
+                created_time__lt=int(today_start.timestamp())
+            ).count()
+            # 总设备数
+            active_device_count = agent_device_qs.filter(status=1).count()
+            inactive_device_count = agent_device_qs.filter(status=0).count()
+
+            return response.json(0, {
+                'total_profit_all': total_profit_all,
+                'total_profit_no': total_profit_no,
+                'total_profit_yes': total_profit_yes,
+                'profit_amount_all': profit_amount_all,
+                'profit_amount_today_all': profit_amount_today_all,
+                'profit_amount_yesterday_all': profit_amount_yesterday_all,
+                'active_device_count_today': active_device_count_today,
+                'active_device_count_yesterday': active_device_count_yesterday,
+                'active_device_count': active_device_count,
+                'inactive_device_count': inactive_device_count,
+                'all_device_count': active_device_count + inactive_device_count
+            })
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def agent_apply_withdraw(self, userID, request_dict, response):
+        """
+        用户提现申请
+        @param userID: userID
+        @param request_dict: 请求参数
+        @param request_dict amount: 金额
+        @param response: 响应对象
+        @return:
+        """
+        amount = Decimal(request_dict.get('amount', '0.00'))
+
+        if amount == Decimal('0.00'):
+            return response.json(444)
+
+        agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
+        if not agent_customer_info:
+            return response.json(104, 'Agent customer not found')
+
+        ac_id = agent_customer_info.id
+        try:
+            # 计算余额:已结算 - (退款+已打款)
+            incomes_qs = AgentAccount.objects.filter(ac_id=ac_id, status=1)
+            expense_qs = AgentAccount.objects.filter(ac_id=ac_id, status__in=[2, 3])
+            incomes_all = incomes_qs.aggregate(total=Sum('amount'))['total'] or Decimal('0.00')
+            expense_all = expense_qs.aggregate(total=Sum('amount'))['total'] or Decimal('0.00')
+            total_profit = incomes_all - expense_all
+
+            # 冻结余额
+            frozen_amount_qs = AgentAccountWithdraw.objects.filter(ac_id=ac_id, status__in=[1, 2, 3])
+            frozen_amount = frozen_amount_qs.aggregate(total=Sum('amount'))['total'] or Decimal('0.00')
+
+            # 可提现余额 = 总余额 - 冻结金额
+            available_balance = total_profit - frozen_amount
+
+            if amount < Decimal('1000.00'):
+                return response.json(10, '每次提现最少为1000元')
+            if amount > available_balance:
+                return response.json(10, '余额不足,无法提现')
+
+            # 提交提现申请
+            acc = AgentCustomerCard.objects.filter(ac_id=ac_id).first()
+            AgentAccountWithdraw.objects.create(ac_id=ac_id, status=1, card_id=acc.id, amount=amount,
+                                                created_time=int(time.time()), updated_time=int(time.time()))
+            return response.json(0)
+
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def get_withdrawal_review(self, request_dict, response):
+        """
+        后台用户提现申请审核列表
+        @param request_dict: 请求参数
+        @param request_dict company_name: 公司名称
+        @param request_dict status: 审核状态
+        @param response: 响应对象
+        @return:
+        """
+        company_name = request_dict.get('company_name', None)
+        status = request_dict.get('status', None)
+        page = int(request_dict.get('page', 1))  # 默认为第一页
+        page_size = int(request_dict.get('page_size', 10))  # 默认每页10条记录
+        try:
+            agent_account_withdraw_qs = AgentAccountWithdraw.objects.filter().order_by('updated_time')
+            if company_name:
+                agent_customer_info = AgentCustomerInfo.objects.filter(company_name=company_name).first()
+                ac_id = agent_customer_info.id
+                agent_account_withdraw_qs = agent_account_withdraw_qs.filter(ac_id=ac_id)
+
+            if status:
+                agent_account_withdraw_qs = agent_account_withdraw_qs.filter(status=status)
+
+            # 提现id 公司名 审核状态 提现金额 余额
+            paginator = Paginator(agent_account_withdraw_qs, page_size)
+            current_page = paginator.get_page(page)
+
+            review_list = []
+
+            for review in current_page:
+                ac_id = review.ac_id
+                agent_customer_info = AgentCustomerInfo.objects.filter(id=ac_id).first()
+                company_name = agent_customer_info.company_name
+                review_list.append({
+                    "id": review.id,
+                    "company_name": company_name,
+                    "status": review.status,
+                    "amount": review.amount,
+                    "remark": review.remark,
+                })
+
+            response_data = {
+                'list': review_list,
+                'total': paginator.count,
+                'page': current_page.number,
+                'page_size': page_size,
+                'num_pages': paginator.num_pages,
+            }
+            return response.json(0, response_data)
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def update_withdrawal_review(self, userID, request_dict, response):
+        """
+        后台提现审核
+        @param request_dict: 请求参数
+        @param request_dict id: 提现单id
+        @param request_dict status: 提现单状态
+        @param request_dict remark: 提现单备注
+        @param response: 响应对象
+        @return:
+        """
+        id = request_dict.get('id', None)
+        status = request_dict.get('status', None)
+        remark = request_dict.get('remark', "")
+
+        if not all([id, status]):
+            return response.json(444)
+
+        try:
+            agent_account_withdraw = AgentAccountWithdraw.objects.get(pk=id)
+            agent_account_withdraw.status = status
+            agent_account_withdraw.remark = remark
+            agent_account_withdraw.save()
+            if int(status) == 4:
+                AgentAccount.objects.create(ac_id=agent_account_withdraw.ac_id,
+                                            amount=agent_account_withdraw.amount,
+                                            status=3,
+                                            created_time=int(time.time()),
+                                            updated_time=int(time.time()),
+                                            remark=f"{userID}修改为已提现")
+            return response.json(0)
+
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def apply_agent(self, request_dict, response):
+        """
+        代理申请
+        @param request_dict: 请求参数
+        @param request_dict name: 名字
+        @param request_dict phone: 电话
+        @param request_dict regin: 地区
+        @param request_dict remark: 备注
+        @param response: 响应对象
+        @return:
+        """
+        name = request_dict.get('name', None)
+        phone = request_dict.get('phone', None)
+        regin = request_dict.get('regin', None)
+        remark = request_dict.get('remark', "")
+        if not all([name, phone, regin]):
+            return response.json(444)
+
+        # 去除非数字字符
+        clean_phone = re.sub(r'\D', '', phone)
+
+        if ApplyAgent.objects.filter(phone=clean_phone).exists():
+            return response.json(174, 'Phone number already exists')
+
+        ApplyAgent.objects.create(name=name, phone=clean_phone, regin=regin, remark=remark)
+
+        return response.json(0, '申请已提交')

+ 369 - 0
AdminController/CloudServiceManage/AgentDeviceController.py

@@ -0,0 +1,369 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : AgentDeviceController.py
+@Time    : 2024/3/8 13:55
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import os
+import csv
+import time
+from datetime import datetime
+import calendar
+from dateutil.relativedelta import relativedelta
+from collections import defaultdict
+from decimal import Decimal
+import traceback
+import threading
+
+from django.db import transaction
+from django.http import QueryDict
+from django.views import View
+from django.core.paginator import Paginator
+
+from AgentModel.models import AgentCustomerInfo, AgentDeviceOrder, AgentDevice, AgentCloudServicePackage
+from Model.models import DeviceTypeModel
+
+from Object.ResponseObject import ResponseObject
+from Ansjer.config import LOGGER
+from Object.TokenObject import TokenObject
+
+
+class AgentDeviceView(View):
+    def get(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.GET, request, operation)
+
+    def post(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.POST, request, operation)
+
+    def delete(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        delete = QueryDict(request.body)
+        if not delete:
+            delete = request.GET
+        return self.validation(delete, request, operation)
+
+    def put(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        put = QueryDict(request.body)
+        return self.validation(put, request, operation)
+
+    def validation(self, request_dict, request, operation):
+        language = request_dict.get('language', 'en')
+        response = ResponseObject(language, 'pc')
+        # 订单结算界面
+        if operation == 'XXXXX':
+            pass
+        else:
+            tko = TokenObject(
+                request.META.get('HTTP_AUTHORIZATION'),
+                returntpye='pc')
+            if tko.code != 0:
+                return response.json(tko.code)
+            response.lang = tko.lang
+            userID = tko.userID
+            if operation == 'getAgentDevice':
+                return self.get_agent_device(userID, request_dict, response)
+            elif operation == 'getAgentDeviceOrder':
+                return self.get_agent_device_order(userID, request_dict, response)
+            elif operation == 'batchBandDevice':
+                return self.batch_band_device(userID, request, request_dict, response)
+            else:
+                return response.json(444, 'operation')
+
+    def get_agent_device(self, userID, request_dict, response):
+        """
+        查询设备明细
+        @param userID: userID
+        @param request_dict: 请求参数
+        @param request_dict ac_id: 代理商id
+        @param request_dict device_name: 设备名字
+        @param request_dict status: 设备类型
+        @param request_dict serial_number: 设备9位序列号
+        @param response: 响应对象
+        @return:
+        """
+        device_name = request_dict.get('device_name', None)
+        status = request_dict.get('status', None)
+        serial_number = request_dict.get('serial_number', None)
+
+        page = int(request_dict.get('page', 1))  # 默认为第一页
+        page_size = int(request_dict.get('page_size', 10))  # 默认每页10条记录
+
+        try:
+            agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
+            if agent_customer_info is None:
+                agent_device_qs = AgentDevice.objects.order_by('ac_id', '-created_time')
+            else:
+                ac_id = agent_customer_info.id
+                agent_device_qs = AgentDevice.objects.filter(ac_id=ac_id).order_by('ac_id', '-created_time')
+
+            if device_name:
+                # 根据device_name查询对应的type值
+                device_types = list(DeviceTypeModel.objects.filter(name=device_name).values_list('type', flat=True))
+                agent_device_qs = agent_device_qs.filter(type__in=device_types)
+
+            if status:
+                agent_device_qs = agent_device_qs.filter(status=status)
+
+            if serial_number:
+                agent_device_qs = agent_device_qs.filter(serial_number=serial_number)
+
+            # 应用分页
+            paginator = Paginator(agent_device_qs, page_size)
+            current_page = paginator.get_page(page)
+
+            # 构造返回列表
+            device_list = []
+            for device in current_page:
+                device_type = DeviceTypeModel.objects.filter(type=device.type).first()
+                device_name = device_type.name if device_type else device.type
+                agent_customer_info = AgentCustomerInfo.objects.filter(id=device.ac_id).first()
+                company_name = agent_customer_info.company_name if agent_customer_info else device.ac_id
+                device_list.append({
+                    'id': device.id,
+                    'ac_id': device.ac_id,
+                    'company_name': company_name,
+                    'status': device.status,
+                    'serial_number': device.serial_number,
+                    'device_name': device_name,
+                    'at_time': device.at_time,
+                })
+
+            # 包含分页信息的响应
+            response_data = {
+                'list': device_list,
+                'total': paginator.count,
+                'page': current_page.number,
+                'page_size': page_size,
+                'num_pages': paginator.num_pages,
+            }
+
+            return response.json(0, response_data)
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def calculate_profit_or_revenue(self, agent_device_orders, package_details, time_unit, metric_type, start_time,
+                                    end_time):
+        """
+        计算利润或者营业额
+        @param agent_device_orders: 代理设备订单
+        @param package_details: 代理套餐详情
+        @param time_unit: 时间单位
+        @param metric_type: 利润或者营业额
+        @param start_time: 开始时间
+        @param end_time: 结束时间
+        @return:
+        """
+        summary = defaultdict(lambda: {"云存": Decimal('0.00'), "4G": Decimal('0.00'), "all": Decimal('0.00')})
+        time_format = {
+            "month": "%Y-%m",
+            "year": "%Y",
+            "quarter": lambda x: f"{x.year}年{(x.month - 1) // 3 + 1}季度"
+        }
+
+        for order in agent_device_orders:
+            package = package_details.get(order.csp_id)
+            if not package:
+                continue
+
+            # 根据利润类型计算利润或者直接使用营业额
+            if metric_type == 1:  # 利润
+                profit = order.profit
+            else:  # 营业额
+                profit = order.profit_amount
+
+            # 区分云服务 + 4G套餐并加入 summary
+            service_type = "云存" if package.type == 1 else "4G"
+            time_key = datetime.fromtimestamp(order.created_time).strftime(
+                time_format[time_unit]) if time_unit != "quarter" else time_format[time_unit](
+                datetime.fromtimestamp(order.created_time))
+
+            summary[time_key][service_type] += profit
+            summary[time_key]["all"] += profit
+
+        # 补全时间段内所有可能的时间单位
+        current_time = start_time
+
+        while current_time < end_time:
+            time_key = current_time.strftime(time_format[time_unit]) if time_unit != "quarter" else time_format[
+                time_unit](current_time)
+            if time_key not in summary:
+                summary[time_key] = {"云存": Decimal('0.00'), "4G": Decimal('0.00'), "all": Decimal('0.00')}
+            current_time += relativedelta(months=1) if time_unit == "month" else relativedelta(
+                years=1) if time_unit == "year" else relativedelta(months=3)
+
+        return [{"time": time, **data} for time, data in sorted(summary.items())]
+
+    def get_agent_device_order(self, userID, request_dict, response):
+        """
+        查询设备订单明细
+        @param userID: userID
+        @param request_dict: 请求参数
+        @param request_dict startTime: 开始时间
+        @param request_dict endTime: 结束时间
+        @param request_dict timeUnit: 时间单位
+        @param request_dict metric_type: 利润或者营业额
+        @param response: 响应对象
+        @return:
+        """
+        try:
+            startTime = int(request_dict.get('startTime', 1704038400))
+            endTime = int(request_dict.get('endTime', 1732982400))
+            timeUnit = request_dict.get('timeUnit', 'month')
+            metric_type = int(request_dict.get('metric_type', 0))
+
+            # endTime变成每个月最后一天
+            end_datetime = datetime.fromtimestamp(endTime)
+            month_str = end_datetime.strftime('%Y-%m')
+            year, month = int(month_str.split('-')[0]), int(month_str.split('-')[1])
+            end = calendar.monthrange(year, month)[1]
+            end_timestamp = datetime(year, month, end, 23, 59, 59).timestamp()
+            endTime = int(end_timestamp)
+
+            agent_customer_info = AgentCustomerInfo.objects.filter(user_id=userID).first()
+            if not agent_customer_info:
+                return response.json(104, 'Agent customer not found')
+
+            agent_device_orders = AgentDeviceOrder.objects.filter(
+                ac_id=agent_customer_info.id, created_time__gte=startTime, created_time__lte=endTime, status__in=[1, 2]
+            )
+
+            # 获取代理套餐包id
+            package_ids = agent_device_orders.values_list('csp_id', flat=True).distinct()
+            package_details = {pkg.id: pkg for pkg in AgentCloudServicePackage.objects.filter(id__in=package_ids)}
+
+            start_time = datetime.fromtimestamp(startTime)
+            end_time = datetime.fromtimestamp(endTime)
+
+            result = self.calculate_profit_or_revenue(agent_device_orders, package_details, timeUnit, metric_type,
+                                                      start_time, end_time)
+            total_4G = Decimal('0.00')
+            total_cloud = Decimal('0.00')
+            # 遍历result列表来累加4G和云存的值
+            for item in result:
+                total_4G = item['4G'] + total_4G
+                total_cloud = item['云存'] + total_cloud
+            response_data = {
+                "list": result,
+                "total_4G": total_4G,  # 4G的总和
+                "total_云存": total_cloud,  # 云存的总和
+            }
+
+            return response.json(0, response_data)
+
+        except Exception as e:
+            error_msg = f"error_line:{traceback.format_exc()}, error_msg:{str(e)}"
+            return response.json(500, error_msg)
+
+    def agent_devices_from_csv(self, ac_id, device_type, userID, file_path):
+        """
+        异步批量绑定设备
+        """
+        try:
+            with open(file_path, 'r') as file:
+                reader = csv.DictReader(file)
+                devices_to_create = []
+                # 先收集所有CSV中的序列号
+                csv_serial_numbers = [row.get('serial_number') for row in reader]
+                # 去重
+                unique_serial_numbers = set(csv_serial_numbers)
+
+                existing = set(AgentDevice.objects.filter(
+                    serial_number__in=unique_serial_numbers
+                ).values_list('serial_number', flat=True))
+
+                for row in unique_serial_numbers:
+                    serial_number = row.get('serial_number')
+                    if serial_number not in existing:
+                        device = AgentDevice(
+                            ac_id=ac_id,
+                            serial_number=serial_number,
+                            type=device_type,
+                            status=0,
+                            created_time=int(time.time()),
+                            created_by=userID,
+                            updated_time=int(time.time()),
+                            updated_by=userID
+                        )
+                        devices_to_create.append(device)
+                # 使用Django的bulk_create来批量创建对象
+                if devices_to_create:
+                    with transaction.atomic():
+                        AgentDevice.objects.bulk_create(devices_to_create, batch_size=200)
+
+            # 删除文件
+            os.remove(file_path)
+        except Exception as e:
+            LOGGER.info('errLine:{}, errMsg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    def batch_band_device(self, userID, request, request_dict, response):
+        """
+        批量绑定设备
+        @param ac_id: ac_id 代理商id
+        @param userID: userID
+        @param csv_file_path: 文件路径
+        @param response: 响应对象
+        @return:
+        """
+        ac_id = request_dict.get('ac_id', None)
+        device_name = request_dict.get('device_name', None)
+        csv_file = request.FILES['file']
+        upload_dir = os.path.join('static', 'uploaded_files')
+
+        if not all([ac_id, device_name]):
+            return response.json(444)
+
+        try:
+            device_type_dict = DeviceTypeModel.objects.filter(name=device_name).values('type').first()
+            device_type = device_type_dict['type']
+
+            if not os.path.exists(upload_dir):
+                os.makedirs(upload_dir)
+
+            file_path = os.path.join(upload_dir, csv_file.name)
+            with open(file_path, 'wb+') as destination:
+                for chunk in csv_file.chunks():
+                    destination.write(chunk)
+
+            # 创建并启动线程来异步执行任务
+            thread = threading.Thread(target=self.agent_devices_from_csv, args=(ac_id, device_type, userID, file_path))
+            thread.start()
+
+            return response.json(0)
+
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    @classmethod
+    def device_binding_or_unbinding(cls, serial_number, bind_type):
+        """
+        设备绑定或解绑
+        @param serial_number: 设备序列号
+        @param bind_type: 绑定类型 1:绑定 2:解绑
+        @return: 无返回值
+        """
+        try:
+            # 获取设备信息
+            device_info = AgentDevice.objects.filter(serial_number=serial_number)
+            if not device_info.exists():
+                return
+            n_time = int(time.time())
+            if bind_type == 1:
+                # 绑定设备
+                device_info.update(status=1, updated_time=n_time)
+            elif bind_type == 2:
+                # 解绑设备
+                device_info.update(status=0, updated_time=n_time)
+        except Exception as e:
+            LOGGER.info('*****AgentDeviceView.device_binding_or_unbinding:errLine:{}, errMsg:{}'
+                        .format(e.__traceback__.tb_lineno, repr(e)))

+ 370 - 0
AdminController/CloudServiceManage/AgentOrderController.py

@@ -0,0 +1,370 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : AgentOrderController.py
+@Time    : 2024/3/14 10:53
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import threading
+import time
+from datetime import datetime, timedelta
+from decimal import Decimal, ROUND_DOWN
+
+from django.http import QueryDict
+from django.views import View
+
+from AgentModel.models import AgentDevice, AgentCloudServicePackage, AgentCustomerPackage, AgentDeviceOrder, \
+    AgentDeviceOrderInstallment, AgentAccount
+from Ansjer.config import LOGGER
+from Model.models import Order_Model, Store_Meal, UnicomCombo
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+from Service.CommonService import CommonService
+
+
+class AgentOrderView(View):
+    def get(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.GET, request, operation)
+
+    def post(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        return self.validation(request.POST, request, operation)
+
+    def delete(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        delete = QueryDict(request.body)
+        if not delete:
+            delete = request.GET
+        return self.validation(delete, request, operation)
+
+    def put(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        put = QueryDict(request.body)
+        return self.validation(put, request, operation)
+
+    def validation(self, request_dict, request, operation):
+        response = ResponseObject()
+        tko = TokenObject(
+            request.META.get('HTTP_AUTHORIZATION'),
+            returntpye='pc')
+        if operation == 'addOrder':  # 添加代理商订单
+            order_id = request_dict.get('order_id', None)
+            uid = request_dict.get('uid', None)
+            order_type = request_dict.get('order_type', None)
+            package_id = request_dict.get('package_id', None)
+
+            self.check_agent_service_package(order_id, uid, int(package_id))
+            return response.json(0)
+
+    @classmethod
+    def check_agent_service_package(cls, order_id, uid, package_id):
+        """
+        检查是否代理服务套餐
+        @param package_id: 套餐id
+        @param order_id: 订单ID
+        @param uid: UID
+        @return: True | False
+        """
+        try:
+            serial_number = CommonService.get_serial_number_by_uid(uid)
+            a_device_qs = AgentDevice.objects.filter(serial_number=serial_number) \
+                .values('ac_id', 'type', 'status')
+            LOGGER.info(f'******check_agent_service_package检查是否代理*****orderId:{order_id}')
+
+            if not a_device_qs.exists():
+                return False
+            LOGGER.info(f'******check_agent_service_package当前设备属于代理商*****serial_number:{serial_number}')
+
+            asy = threading.Thread(target=cls.save_agent_package,
+                                   args=(order_id, serial_number, a_device_qs[0]['ac_id'], package_id))
+            asy.start()
+            return True
+        except Exception as e:
+            LOGGER.info('*****AgentOrderView.check_agent_service_package:errLine:{}, errMsg:{}'
+                        .format(e.__traceback__.tb_lineno, repr(e)))
+        return False
+
+    @classmethod
+    def save_agent_package(cls, order_id, serial_number, ac_id, package_id):
+        """
+        保存代理套餐
+        """
+        try:
+            order_qs = Order_Model.objects.filter(orderID=order_id, status=1).values('price', 'payTime', 'order_type')
+            if not order_qs.exists():
+                LOGGER.info(f'******save_agent_package当前代理客户未添加此套餐******ac_id:{ac_id},package_id:{package_id}')
+                return
+
+            order_type = order_qs[0]['order_type']
+
+            package_type = 2 if order_type in [2, 3, 5] else 1  # 判断订单信息是云存还是4G
+
+            package_id = int(package_id)
+            agent_package_qs = AgentCloudServicePackage.objects.filter(type=package_type, package_id=package_id,
+                                                                       status=1)
+            if not agent_package_qs.exists():
+                LOGGER.info(f'******save_agent_package当前套餐未设置代理******order_id:{order_id},serial_number:{serial_number}')
+                return
+
+            agent_package = agent_package_qs.first()  # 代理云服务套餐
+            LOGGER.info(f'******save_agent_package代理套餐******service_name:{agent_package_qs.first().service_name}')
+            acp_qs = AgentCustomerPackage.objects.filter(ac_id=ac_id, cs_id=agent_package.id).values('id')
+            if not acp_qs.exists():
+                LOGGER.info(f'******save_agent_package当前代理客户未添加此套餐******ac_id:{ac_id},package_id:{package_id}')
+                return
+
+            # 组装数据
+            now_time = int(time.time())
+
+            pay_price = Decimal(order_qs[0]['price']).quantize(Decimal('0.00'))
+            profit = cls.calculate_order_profit(agent_package, pay_price)
+
+            dict_data = {'ac_id': ac_id, 'serial_number': serial_number, 'csp_id': agent_package.id,
+                         'order_id': order_id, 'status': 1, 'profit_amount': pay_price, 'profit': profit,
+                         'pay_time': order_qs[0]['payTime'], 'created_time': now_time, 'updated_time': now_time}
+            agent_order_obj = AgentDeviceOrder.objects.create(**dict_data)
+
+            # 保存分期结算记录
+            cls.save_order_installment(agent_order_obj.id, package_type, package_id, profit, ac_id)
+
+            LOGGER.info(f'******save_agent_package代理订单存表结束:{dict_data}')
+        except Exception as e:
+            LOGGER.info('*****AgentOrderView.save_agent_package:errLine:{}, errMsg:{}'
+                        .format(e.__traceback__.tb_lineno, repr(e)))
+
+    @classmethod
+    def calculate_order_profit(cls, agent_package, price):
+        """
+        计算利润
+        @param agent_package: 套餐配置
+        @param price: 支付价格
+        @return: 利润
+        """
+        profit = 0
+        price = Decimal(price).quantize(Decimal('0.00'))
+        if agent_package.profit_type == 1:
+            profit = agent_package.profit
+        elif agent_package.profit_type == 2:
+            profit_value = Decimal(agent_package.profit).quantize(Decimal('0.00'))
+            cost = Decimal(agent_package.cost).quantize(Decimal('0.00'))
+            profit = (price - cost) * (profit_value / 100)
+            profit = profit.quantize(Decimal('0.00'))
+        return profit
+
+    @classmethod
+    def get_settlement_interval(cls, package_type, package_id):
+        try:
+            if package_type == 1:  # 云存
+                store_qs = Store_Meal.objects.filter(id=package_id).values('day', 'bucket_id', 'expire',
+                                                                           'icloud_store_meal_id')
+                if not store_qs.exists():
+                    return []
+
+                # 根据套餐周期计算往后每个月26号作为结算时间
+                return cls.get_future_timestamps(store_qs[0]['expire'])
+
+            elif package_type == 2:  # 4G
+                combo4g_qs = UnicomCombo.objects.filter(id=package_id).values('expiration_days', 'expiration_type')
+                if not combo4g_qs.exists():
+                    return []
+
+                # 目前4G套餐都是基于按天类型创建
+                if combo4g_qs[0]['expiration_type'] == 0 and combo4g_qs[0]['expiration_days'] > 0:
+                    months = int(combo4g_qs[0]['expiration_days'] / 30)
+
+                    # 根据套餐周期计算往后每个月26号作为结算时间
+                    return cls.get_future_timestamps(months)
+        except Exception as e:
+            LOGGER.info('*****AgentOrderView.get_settlement_interval:errLine:{}, errMsg:{}'
+                        .format(e.__traceback__.tb_lineno, repr(e)))
+            return []
+
+    @staticmethod
+    def get_future_timestamps(months):
+        """
+        生成未来若干个月的第一个月的26号11点的timestamp列表。
+
+        参数:
+        months -- 未来需要生成timestamp的月份数量
+
+        返回值:
+        timestamps -- 包含未来months个月第一个月的26号11点的timestamp的列表
+        """
+        current_time = datetime.now()  # 获取当前时间,注意这会是系统当前时区的时间
+        current_month = current_time.month
+        current_year = current_time.year
+
+        timestamps = []
+        for _ in range(months):
+            # 如果当前月已经是需要生成的月份,则年份和月份不变
+            if current_month == 1 and _ == 0:
+                next_year = current_year
+                next_month = current_month + 1
+            else:
+                # 计算下一个月的年和月
+                if current_month == 12:
+                    next_month = 1
+                    next_year = current_year + 1
+                else:
+                    next_month = current_month + 1
+                    next_year = current_year
+
+                    # 生成下个月的26号11点的时间点
+            next_date = datetime(next_year, next_month, 26, 11, 0, 0)
+
+            # 如果生成的日期超过了当月的实际天数(比如2月没有26号),则需要回退到当月的最后一天
+            last_day_of_month = (datetime(next_year, next_month, 1) + timedelta(days=31)).replace(day=1) - timedelta(
+                days=1)
+            if next_date > last_day_of_month:
+                next_date = last_day_of_month.replace(hour=11, minute=0, second=0, microsecond=0)
+
+            timestamps.append(int(next_date.timestamp()))
+
+            # 更新当前月份和年份为下一次循环使用
+            current_month = next_month
+            current_year = next_year
+
+        return timestamps
+
+    @staticmethod
+    def distribute_commission(commission, periods):
+        # 转换佣金和期数为Decimal类型,并设置精度
+        commission = Decimal(str(commission)).quantize(Decimal('0.01'))
+        periods = Decimal(periods)
+
+        # 每期基础金额(向下取整到最接近的0.01)
+        base_amount = (commission / periods).quantize(Decimal('0.01'), rounding=ROUND_DOWN)
+
+        # 初始化每期分配的金额列表
+        distributed_amounts = [base_amount] * int(periods)
+
+        # 计算按照基础金额分配后的总和
+        total_distributed = sum(distributed_amounts)
+
+        # 计算剩余需要分配的金额
+        remainder = commission - total_distributed
+
+        # 分配剩余金额
+        if remainder > Decimal('0'):
+            # 从第一期开始分配剩余金额
+            for i in range(len(distributed_amounts)):
+                if remainder >= Decimal('0.01'):
+                    distributed_amounts[i] += Decimal('0.01')
+                    remainder -= Decimal('0.01')
+                else:
+                    # 如果剩余金额不足0.01,则将其全部加到当前期
+                    distributed_amounts[i] += remainder
+                    break
+
+        return distributed_amounts
+
+    @classmethod
+    def save_order_installment(cls, agent_order_id, package_type, package_id, profit, ac_id=None):
+        """
+        保存代理订单分期信息
+
+        :param cls: 类方法的约定参数
+        :param agent_order_id: 代理订单ID
+        :param package_type: 套餐类型
+        :param package_id: 套餐ID
+        :param profit: 利润总额
+        :return: 无返回值
+        :param ac_id: 代理客户ID
+        """
+        try:
+            # 根据包裹类型和ID获取结算时间间隔列表
+            time_list = cls.get_settlement_interval(package_type, package_id)
+            period_number = len(time_list)  # 计算分期总数
+
+            # 输入合理性检查
+            if period_number == 0 or profit <= 0:
+                LOGGER.info(f'Invalid input parameters: period_number={period_number}, profit={profit}')
+                return
+
+            n_time = int(datetime.now().timestamp())  # 获取当前时间戳
+            # 利润总额按分期数平均分配
+            amount_list = cls.distribute_commission(profit, period_number)
+
+            installment_list = []
+            # 遍历分期数,生成分期记录列表
+            for time_point in range(period_number):
+                installment_list.append(AgentDeviceOrderInstallment(ado_id=agent_order_id,
+                                                                    period_number=period_number,
+                                                                    ac_id=ac_id,
+                                                                    amount=amount_list[time_point],
+                                                                    due_date=time_list[time_point],
+                                                                    status=1,
+                                                                    created_time=n_time,
+                                                                    updated_time=n_time))
+
+            # 分批处理大量数据,避免数据库压力过大
+            batch_size = 100
+            for i in range(0, len(installment_list), batch_size):
+                AgentDeviceOrderInstallment.objects.bulk_create(
+                    installment_list[i:i + batch_size]
+                )
+
+            # 记录创建完成的日志
+            LOGGER.info(f'*****AgentOrderView.save_OrderInstallment分期结算记录创建完成:{len(installment_list)} records')
+
+        except Exception as e:
+            # 记录异常信息
+            LOGGER.error('*****AgentOrderView.save_OrderInstallment:errLine:{}, errMsg:{}'
+                         .format(e.__traceback__.tb_lineno, repr(e)))
+
+    @staticmethod
+    def update_periodic_settlement():
+        """
+        更新周期结算信息
+
+        返回值:
+        - 无返回值
+        """
+        try:
+            # 根据条件查询需要更新结算信息的订单分期记录
+            adoi_qs = AgentDeviceOrderInstallment.objects.filter(status=1, due_date__lte=int(time.time()))
+            if not adoi_qs:
+                # 如果没有找到符合条件的记录,直接返回
+                return
+
+            ids = []
+            a_account_list = []
+            adoi_set = set()
+            n_time = int(time.time())
+            for item in adoi_qs:
+                # 准备分期记录的id列表和账户记录列表
+                ids.append(item.id)
+                adoi_set.add(item.ado_id)
+                a_account_list.append(AgentAccount(ac_id=item.ac_id, amount=item.amount,
+                                                   remark=f'周期结算',
+                                                   status=1, created_time=n_time,
+                                                   updated_time=n_time))
+
+            batch_size = 100
+
+            # 分批更新分期记录状态
+            for i in range(0, len(ids), batch_size):
+                AgentDeviceOrderInstallment.objects.filter(id__in=ids[i:i + batch_size]) \
+                    .update(status=2, settlement_time=n_time, updated_time=n_time)
+
+            # 分批创建账户记录
+            for i in range(0, len(a_account_list), batch_size):
+                AgentAccount.objects.bulk_create(a_account_list[i:i + batch_size])
+
+            # 检查是否所有分期都已结算,如果是,则更新订单状态为已结算
+            for ado in adoi_set:
+                adoi_qs = AgentDeviceOrderInstallment.objects.filter(ado_id=ado, status=1)
+                if not adoi_qs.exists():
+                    AgentDeviceOrder.objects.filter(id=ado, status=1) \
+                        .update(status=2, settlement_time=n_time, updated_time=n_time)
+
+        except Exception as e:
+            # 记录异常信息
+            LOGGER.error(
+                f'*****AgentOrderView.update_periodic_settlement:errLine:{e.__traceback__.tb_lineno}, errMsg:{str(e)}')

+ 5 - 0
Ansjer/urls.py

@@ -6,6 +6,7 @@ from AdminController import UserManageController, RoleController, MenuController
     ServeManagementController, LogManagementController, DeviceManagementController, VersionManagementController, \
     AiServeController, SurveysManageController, SerialManageController, MessageMangementController, \
     EvaluationActivityController, CampaignController
+from AdminController.CloudServiceManage import AgentDeviceController, AgentCustomerController, AgentOrderController
 from Controller import FeedBack, EquipmentOTA, EquipmentInfo, AdminManage, AppInfo, \
     Test, MealManage, DeviceManage, EquipmentStatus, SysManage, DeviceLog, LogAccess, \
     AppColophon, DateController, \
@@ -422,6 +423,10 @@ urlpatterns = [
     re_path(r'serial/(?P<operation>.*)', SerialManageController.SerialView.as_view()),
     # 数据系统模块
     re_path(r'^dataManagement/', include("Ansjer.server_urls.datasystem_url")),
+    # 代理平台模块
+    re_path(r'^agent/customer/manage/(?P<operation>.*)', AgentCustomerController.AgentCustomerView.as_view()),
+    re_path(r'^agent/device/manage/(?P<operation>.*)', AgentDeviceController.AgentDeviceView.as_view()),
+    re_path(r'^agent/order/manage/(?P<operation>.*)', AgentOrderController.AgentOrderView.as_view()),
     # APP广告模块
     re_path('campaign/(?P<operation>.*)', CampaignController.CampaignView.as_view()),
     # 查询消息推送模块

+ 2 - 0
Controller/SerialNumberController.py

@@ -119,6 +119,7 @@ class SerialNumberView(View):
 
         # 时间戳token校验
         if not CommonService.check_time_stamp_token(token, time_stamp):
+            LOGGER.error(f'{serial_number}时间戳校验失败time:{time_stamp},tk:{token}')
             return response.json(13)
 
         now_time = int(time.time())
@@ -648,6 +649,7 @@ class SerialNumberView(View):
         try:
             # 添加或更新扫码记录
             ip = CommonService.get_ip_address(request)
+            LOGGER.info('请求获取UID{},userId:{},ip:{}'.format(serial_number, user_id, ip))
             # 查询用户国家id
             region_country = 0
             # ios请求头没传token,user_id为None