Procházet zdrojové kódy

产品精细化管理功能、优化注册校验+号邮箱、优化设备功能配置一键同步、优化查询电量统计列表

zhangdongming před 2 měsíci
rodič
revize
d07094bc7c

+ 361 - 0
AdminController/DataDictManageController.py

@@ -0,0 +1,361 @@
+import json
+import time
+
+from django.db import transaction
+from django.db.models import Q
+from django.views import View
+
+from Model.models import DictCategory, DictCategoryI18n, DictItemI18n, DictItem
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+
+
+class DataDictView(View):
+    def get(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        request_dict = request.GET
+        return self.validation(request_dict, request, operation)
+
+    def post(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        request_dict = request.POST
+        return self.validation(request_dict, request, operation)
+
+    def validation(self, request_dict, request, operation):
+        language = request_dict.get('language', 'en')
+        response = ResponseObject(language, 'pc')
+        tko = TokenObject(
+            request.META.get('HTTP_AUTHORIZATION'),
+            returntpye='pc')
+        response.lang = tko.lang
+        if operation == "getDictCategory":
+            return self.get_dict_category(request_dict, response)
+        elif operation == "getDictCategoryList":
+            return self.dict_categories_list(request_dict, response)
+        if tko.code != 0:
+            return response.json(tko.code)
+        else:
+            response.lang = tko.lang
+            userID = tko.userID
+            if operation == 'addDictCategory':
+                return self.add_dict_category(userID, request_dict, response)
+            elif operation == 'editDictCategory':
+                return self.edit_dict_category(userID, request_dict, response)
+            elif operation == 'deleteDictCategory':
+                return self.delete_dict_category(request_dict, response)
+            else:
+                return response.json(414)
+
+    @staticmethod
+    def dict_categories_list(request_dict, response):
+        code = request_dict.get("typeCode", None)
+        status = request_dict.get("status", None)
+        scenes = request_dict.get("scenes", None)
+        page = int(request_dict.get("page", 1))
+        page_size = int(request_dict.get("pageSize", 10))
+        try:
+            query = Q()
+            if code:
+                query &= Q(code__icontains=code)
+            if status:
+                query &= Q(status=bool(int(status)))
+            if scenes:
+                query &= Q(scenes=int(scenes))
+
+            category_qs = DictCategory.objects.filter(query).order_by("-id")
+
+            total = category_qs.count()
+            start = (page - 1) * page_size
+            end = start + page_size
+            categories = category_qs[start:end]
+
+            result = []
+            for category in categories:
+                # 类别名称国际化
+                i18n_name = (
+                        list(category.translations.values("lang_code", "name"))
+                        or []
+                )
+
+                # 子表字典项
+                items = category.items.order_by("sort", "id")
+                item_list = []
+                for item in items:
+                    item_i18n_name = (
+                            list(item.translations.values("lang_code", "name"))
+                            or []
+                    )
+                    item_list.append({
+                        "id": item.id,
+                        "code": item.code,
+                        "sort": item.sort,
+                        "status": item.status,
+                        "remark": item.remark,
+                        "createdTime": item.created_time,
+                        "createdBy": item.created_by,
+                        "updatedTime": item.updated_time,
+                        "updatedBy": item.updated_by,
+                        "itemNames": item_i18n_name
+                    })
+
+                result.append({
+                    "id": category.id,
+                    "code": category.code,
+                    "scenes": category.scenes,
+                    "status": category.status,
+                    "remark": category.remark,
+                    "extData": category.ext_data,
+                    "createdTime": category.created_time,
+                    "createdBy": category.created_by,
+                    "updatedTime": category.updated_time,
+                    "updatedBy": category.updated_by,
+                    "names": i18n_name,
+                    "items": item_list
+                })
+
+            return response.json(0, {
+                "list": result,
+                "total": total
+            })
+
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    @staticmethod
+    def get_dict_category(request_dict, response):
+        type_codes = request_dict.get("typeCodes", None)
+        lang_code = request_dict.get("langCode", "cn")
+        scenes = request_dict.get("scenes", 1)
+        try:
+            if not type_codes:
+                return response.json(444)
+            type_code_list = type_codes.split(",")
+            category_qs = DictCategory.objects.filter(code__in=type_code_list, status=True, scenes=scenes)
+
+            if not category_qs.exists():
+                return response.json(0)
+
+            result_list = []
+            for category in category_qs:
+                # 获取类别名称(国际化)
+                category_name = (
+                        category.translations.filter(lang_code=lang_code).values_list("name", flat=True).first()
+                        or ""
+                )
+
+                # 构建返回结构
+                result = {
+                    "typeCode": category.code,
+                    "scenes": category.scenes,
+                    "status": category.status,
+                    "remark": category.remark,
+                    "name": category_name,
+                    "items": []
+                }
+
+                # 获取字典项及其多语言名称
+                items = category.items.filter(status=True).order_by("sort", "id")
+                for item in items:
+                    item_name = (
+                            item.translations.filter(lang_code=lang_code).values_list("name", flat=True).first()
+                            or ""
+                    )
+                    result["items"].append({
+                        "code": item.code,
+                        "name": item_name,
+                        "remark": item.remark,
+                        "sort": item.sort
+                    })
+
+                result_list.append(result)
+            return response.json(0, result_list)
+
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+
+    @staticmethod
+    def add_dict_category(user_id, request_dict, response):
+        type_code = request_dict.get('typeCode', None)
+        scenes = request_dict.get("scenes", 1)
+        status = request_dict.get("status", 1)
+        remark = request_dict.get("remark", "")
+        names = request_dict.get("names", [])
+        items = request_dict.get("items", [])
+        now_time = int(time.time())
+
+        try:
+            if not type_code or not names:
+                return response.json(444)
+
+            names = json.loads(names)
+            items = json.loads(items)
+            if DictCategory.objects.filter(code=type_code).exists():
+                return response.json(174)
+
+            with transaction.atomic():
+                # 创建类别   数据
+                category = DictCategory.objects.create(
+                    code=type_code,
+                    scenes=scenes,
+                    status=status,
+                    remark=remark,
+                    created_time=now_time,
+                    updated_time=now_time,
+                    created_by=user_id,
+                    updated_by=user_id
+                )
+
+                # 添加多语言名称
+                for name_entry in names:
+                    DictCategoryI18n.objects.create(
+                        category=category,
+                        lang_code=name_entry["lang"],
+                        name=name_entry["text"],
+                        created_time=now_time,
+                        updated_time=now_time,
+                        created_by=user_id,
+                        updated_by=user_id
+                    )
+
+                # 创建选项表数据
+                for item in items:
+                    item_obj = DictItem.objects.create(
+                        category=category,
+                        code=item["code"],
+                        sort=item.get("sort", 0),
+                        status=item.get("status", True),
+                        remark=item.get("remark", ""),
+                        created_time=now_time,
+                        updated_time=now_time,
+                        created_by=user_id,
+                        updated_by=user_id
+                    )
+
+                    for name_entry in item.get("names", []):
+                        DictItemI18n.objects.create(
+                            item=item_obj,
+                            lang_code=name_entry["lang"],
+                            name=name_entry["text"],
+                            created_time=now_time,
+                            updated_time=now_time,
+                            created_by=user_id,
+                            updated_by=user_id
+                        )
+
+            return response.json(0)
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+
+    @staticmethod
+    def edit_dict_category(user_id, request_dict, response):
+        category_id = request_dict.get("categoryId", None)
+        scenes = request_dict.get("scenes", 1)
+        status = request_dict.get("status", 1)
+        remark = request_dict.get("remark", "")
+        names = request_dict.get("names", "[]")
+        items = request_dict.get("items", "[]")
+        now_time = int(time.time())
+
+        try:
+            if not category_id:
+                return response.json(444)
+
+            names = json.loads(names)
+            items = json.loads(items)
+
+            category = DictCategory.objects.filter(id=category_id).first()
+            if not category:
+                return response.json(404, msg="字典类别不存在")
+
+            with transaction.atomic():
+                # 更新主表字段(不允许改 typeCode)
+                category.status = status
+                category.scenes = scenes
+                category.remark = remark
+                category.updated_time = now_time
+                category.updated_by = user_id
+                category.save()
+
+                if names:
+                    # 删除原有多语言并重新插入
+                    DictCategoryI18n.objects.filter(category=category).delete()
+                    for name_entry in names:
+                        DictCategoryI18n.objects.create(
+                            category=category,
+                            lang_code=name_entry["lang"],
+                            name=name_entry["text"],
+                            created_time=now_time,
+                            updated_time=now_time,
+                            created_by=user_id,
+                            updated_by=user_id
+                        )
+
+                if not items:
+                    return response.json(0)
+
+                # 删除原有子项及多语言
+                old_items = DictItem.objects.filter(category=category)
+                DictItemI18n.objects.filter(item__in=old_items).delete()
+                old_items.delete()
+
+                # 插入新子项及其国际化
+                for item in items:
+                    item_obj = DictItem.objects.create(
+                        category=category,
+                        code=item["code"],
+                        sort=item.get("sort", 0),
+                        status=item.get("status", True),
+                        remark=item.get("remark", ""),
+                        created_time=now_time,
+                        updated_time=now_time,
+                        created_by=user_id,
+                        updated_by=user_id
+                    )
+
+                    for name_entry in item.get("names", []):
+                        DictItemI18n.objects.create(
+                            item=item_obj,
+                            lang_code=name_entry["lang"],
+                            name=name_entry["text"],
+                            created_time=now_time,
+                            updated_time=now_time,
+                            created_by=user_id,
+                            updated_by=user_id
+                        )
+
+            return response.json(0)
+
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+
+    @staticmethod
+    def delete_dict_category(request_dict, response):
+        category_id = request_dict.get("categoryId", None)
+
+        try:
+            if not category_id:
+                return response.json(444)
+
+            category = DictCategory.objects.filter(id=category_id).first()
+            if not category:
+                return response.json(173)
+
+            with transaction.atomic():
+                # 删除子项和对应的国际化
+                items = DictItem.objects.filter(category=category)
+                DictItemI18n.objects.filter(item__in=items).delete()
+                items.delete()
+
+                # 删除类别国际化和主表记录
+                DictCategoryI18n.objects.filter(category=category).delete()
+                category.delete()
+
+            return response.json(0)
+
+        except Exception as e:
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))

+ 18 - 17
AdminController/DeviceManagementController.py

@@ -14,9 +14,8 @@ from django.db.models import Q, F, Sum, OuterRef, Min, Subquery
 from django.forms.models import model_to_dict
 from django.views.generic.base import View
 
-from Ansjer.cn_config.config_test import CONFIG_INFO
 from Ansjer.config import LOGGER, SERIAL_DOMAIN_NAME, \
-    CONFIG_TEST, CONFIG_CN, CONFIG_US, CONFIG_EUR
+    CONFIG_INFO, CONFIG_TEST, CONFIG_CN, CONFIG_US, CONFIG_EUR
 from Ansjer.config import OSS_STS_ACCESS_KEY, OSS_STS_ACCESS_SECRET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, \
     AWS_SES_ACCESS_REGION
 from Ansjer.config import SERVER_DOMAIN_TEST, SERVER_DOMAIN_CN, SERVER_DOMAIN_US, SERVER_DOMAIN_EUR
@@ -1728,7 +1727,9 @@ class DeviceManagement(View):
 
             # 获取第一条匹配的记录(通常应该只有一条)
             version_config = version_config_qs.first()
-
+            other_features = ''
+            if version_config.other_features:
+                other_features = json.loads(version_config.other_features)
             # 将数据库字段映射为驼峰命名的响应数据
             req_data = {
                 'dCode': version_config.d_code,
@@ -1736,15 +1737,17 @@ class DeviceManagement(View):
                 'firmwareVer': version_config.firmware_ver,
                 'videoCode': version_config.video_code,
                 'regionAlexa': version_config.region_alexa,
-                'supportsHumanTracking': version_config.supports_human_tracking,
-                'supportsCustomVoice': version_config.supports_custom_voice,
-                'supportsDualBandWifi': version_config.supports_dual_band_wifi,
-                'supportsFourPoint': version_config.supports_four_point,
-                'supports4g': version_config.supports_4g,
-                'supportsPTZ': version_config.supports_ptz,
-                'supportsAi': version_config.supports_ai,
-                'supportsCloudStorage': version_config.supports_cloud_storage,
-                'supportsAlexa': version_config.supports_alexa,
+                # 布尔字段转换为int
+                'supportsHumanTracking': int(version_config.supports_human_tracking),
+                'supportsCustomVoice': int(version_config.supports_custom_voice),
+                'supportsDualBandWifi': int(version_config.supports_dual_band_wifi),
+                'supportsFourPoint': int(version_config.supports_four_point),
+                'supports4g': int(version_config.supports_4g),
+                'supportsPTZ': int(version_config.supports_ptz),
+                'supportsAi': int(version_config.supports_ai),
+                'supportsCloudStorage': int(version_config.supports_cloud_storage),
+                'supportsAlexa': int(version_config.supports_alexa),
+                # 其他非布尔字段保持不变
                 'deviceType': version_config.device_type,
                 'resolution': version_config.resolution,
                 'aiType': version_config.ai_type,
@@ -1752,7 +1755,7 @@ class DeviceManagement(View):
                 'supportsNightVision': version_config.supports_night_vision,
                 'screenChannels': version_config.screen_channels,
                 'networkType': version_config.network_type,
-                'otherFeatures': version_config.other_features,
+                'otherFeatures': other_features,
                 'electricityStatistics': version_config.electricity_statistics,
                 'supportsPetTracking': version_config.supports_pet_tracking
             }
@@ -1801,11 +1804,9 @@ class DeviceManagement(View):
             for server_url in target_servers:
                 try:
                     # 发送请求并设置超时(10秒)
-                    headers = {'Content-Type': 'application/json'}
                     resp = requests.post(
-                        server_url,
-                        json=req_data,
-                        headers=headers,
+                        url=server_url,
+                        data=req_data,
                         timeout=10  # 超时时间
                     )
                     resp.raise_for_status()  # 抛出HTTP错误(如400/500)

+ 303 - 0
AdminController/ProblemEntryManagementController.py

@@ -0,0 +1,303 @@
+import json
+import time
+
+from django.core.paginator import Paginator
+from django.db import transaction
+from django.views import View
+
+from Ansjer.test.util.email_log import response
+from Model.models import DeviceScheme, ProductTroubleshoot, AbnormalEventCode, ProductsScheme, AbnormalEvent
+from Object.ResponseObject import ResponseObject
+from Service.CommonService import CommonService
+
+
+class ProblemEntryView(View):
+    def get(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        request_dict = request.GET
+        return self.validation(request, request_dict, operation)
+
+    def post(self, request, *args, **kwargs):
+        request.encoding = 'utf-8'
+        operation = kwargs.get('operation')
+        request_dict = request.POST
+        return self.validation(request, request_dict, operation)
+
+    def validation(self, request, request_dict, operation):
+        language = request_dict.get('language', 'en')
+        response = ResponseObject(language, 'pc')
+        if operation == 'addProductProblem':
+            return self.add_product_problem(request_dict, response)
+        elif operation == 'queryEventSubcategory':
+            return self.query_event_subcategory(request_dict, response)
+        elif operation == 'queryDeviceScheme':
+            return self.query_device_scheme(request_dict, response)
+        elif operation == 'ProductProblemList':
+            return self.product_problem_list(request_dict, response)
+        elif operation == 'editProductProblemStatus':
+            return self.edit_product_problem_status(request_dict, response)
+        else:
+            return response.json(414)
+
+    @staticmethod
+    def add_product_problem(request_dict, response):
+        try:
+            # 解析请求数据
+            date_times = json.loads(request_dict.get('dateTimes', '[]'))
+            deivce_ids = json.loads(request_dict.get('deviceIds', '[]'))
+            device_types = json.loads(request_dict.get('deviceTypes', '[]'))
+            storage_codes = json.loads(request_dict.get('storageCodes', '[]'))
+            event_codes = json.loads(request_dict.get('eventCodes', '[]'))
+            remarks = json.loads(request_dict.get('remarks', '[]'))
+
+            # 基础数据校验
+            list_lengths = {
+                "dateTimes": len(date_times),
+                "serialNumbers": len(deivce_ids),
+                "deviceTypes": len(device_types),
+                "storageCodes": len(storage_codes),
+                "eventCodes": len(event_codes),
+                "remarks": len(remarks)
+            }
+
+            # 检查各字段数据长度一致性
+            if len(set(list_lengths.values())) != 1:
+                return response.json(400, f'参数长度不一致: {", ".join([f"{k}({v})" for k, v in list_lengths.items()])}')
+
+            current_time = int(time.time())
+            products = []
+
+            # 数据预处理
+            for idx in range(len(date_times)):
+                # 字段值提取
+                date_time = date_times[idx]
+                deivce_id = deivce_ids[idx].strip()  # 去除前后空格
+                device_type = device_types[idx]
+                storage_code = storage_codes[idx]
+                event_code = event_codes[idx].strip()
+                remark = remarks[idx].strip()
+
+                # 构建对象
+                products.append(
+                    ProductTroubleshoot(
+                        date_time=date_time,
+                        device_id=deivce_id,
+                        device_type=device_type,
+                        event_code=event_code,
+                        storage_code=storage_code,
+                        status=0,
+                        remark=remark,
+                        created_time=current_time,
+                        updated_time=current_time
+                    )
+                )
+
+            # 批量写入数据库
+            with transaction.atomic():
+                ProductTroubleshoot.objects.bulk_create(products)
+
+            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)))
+
+    @staticmethod
+    def query_event_subcategory(request_dict, response):
+        event_type_code = request_dict.get('eventTypeCode', None)
+        abnormal_event_code_qs = AbnormalEventCode.objects.all()
+
+        if event_type_code:
+            abnormal_event_code_qs = abnormal_event_code_qs.filter(event_code__istartswith=event_type_code)
+
+        data = []
+        for abnormal_event_code in abnormal_event_code_qs:
+            # 第0个是大类 第一个是细分
+            event_list = abnormal_event_code.event.split("-")
+            data.append(
+                {
+                    "eventTypeCode": abnormal_event_code.event_code[0:2],
+                    "eventType": abnormal_event_code.event_type,
+                    "eventSegCode": abnormal_event_code.event_code[2:4],
+                    "eventSeg": event_list[1] if len(event_list) > 1 else "",
+                    "eventDescriptionCode": abnormal_event_code.event_code[4:],
+                    "eventDescription": event_list[2] if len(event_list) > 2 else "",
+
+                }
+            )
+
+        return response.json(0, data)
+
+    @staticmethod
+    def query_device_scheme(request_dict, response):
+        device_id = request_dict.get('deviceId', None)
+        if not device_id:
+            return response.json(0, msg="设备ID不能为空")
+
+        try:
+            # 获取设备信息
+            device_scheme = DeviceScheme.objects.filter(serial_number=device_id).values("device_type",
+                                                                                        "storage_code").first()
+            if not device_scheme:
+                return response.json(0)
+
+            # 获取产品方案信息
+            product_scheme = ProductsScheme.objects.filter(
+                storage_code=device_scheme["storage_code"]
+            ).values(
+                "order_quantity", "created_time", "storage_code", "flash",
+                "ddr", "main_controller", "wifi", "four_g", "ad", "sensor", "phy"
+            ).first()
+
+            if not product_scheme:
+                return response.json(0, {"deviceType": device_scheme["device_type"]})
+
+            # 拼接方案字符串,过滤空值
+            scheme_parts = [
+                product_scheme["flash"],
+                product_scheme["ddr"],
+                product_scheme["main_controller"],
+                product_scheme["wifi"],
+                product_scheme["four_g"],
+                product_scheme["ad"],
+                product_scheme["sensor"],
+                product_scheme["phy"]
+            ]
+            scheme = " + ".join(part for part in scheme_parts if part)
+
+            return response.json(0, {
+                "deviceType": device_scheme["device_type"],
+                "quantity": product_scheme["order_quantity"],
+                "createdTime": product_scheme["created_time"],
+                "storageCode": product_scheme["storage_code"],
+                "scheme": scheme,
+            })
+
+
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+
+    @staticmethod
+    def product_problem_list(request_dict, response):
+        device_id = request_dict.get("deviceId", None)
+        storage_code = request_dict.get("storageCode", None)
+        event_code = request_dict.get("eventCode", None)
+        status = request_dict.get("status", None)
+        page = request_dict.get("page", 1)
+        pageSize = request_dict.get("pageSize", 10)
+
+        # 1. 先查询ProductTroubleshoot表
+        product_troubleshoot_qs = ProductTroubleshoot.objects.all()
+        if device_id:
+            product_troubleshoot_qs = product_troubleshoot_qs.filter(device_id=device_id)
+        if storage_code:
+            product_troubleshoot_qs = product_troubleshoot_qs.filter(storage_code__contains=storage_code)
+        if event_code:
+            product_troubleshoot_qs = product_troubleshoot_qs.filter(event_code__contains=event_code)
+        if status:
+            product_troubleshoot_qs = product_troubleshoot_qs.filter(status=status)
+
+        paginator = Paginator(product_troubleshoot_qs.order_by('status', '-id'), pageSize)
+        product_trouble_page = paginator.page(page)
+
+        # 获取当前页的数据列表
+        product_trouble_list = list(product_trouble_page.object_list.values(
+            'id',
+            'date_time',
+            'device_type',
+            'device_id',
+            'storage_code',
+            'event_code',
+            'status',
+            'remark',
+        ))
+
+        # 2. 收集所有storage_code用于批量查询ProductsScheme表
+        storage_codes = [item['storage_code'] for item in product_trouble_list if item['storage_code']]
+        product_schemes = ProductsScheme.objects.filter(storage_code__in=storage_codes)
+
+        # 3. 创建映射字典并拼接字段
+        scheme_info_dict = {}
+        for scheme in product_schemes:
+            # 拼接需要的字段,过滤掉空值
+            fields_to_join = [
+                scheme.flash,
+                scheme.ddr,
+                scheme.main_controller,
+                scheme.wifi,
+                scheme.four_g,
+                scheme.ad,
+                scheme.sensor,
+                scheme.customer_code,
+                scheme.phy,
+            ]
+
+            # 过滤掉None和空字符串,然后用逗号连接
+            joined_info = '+'.join(filter(None, fields_to_join))
+            scheme_info_dict[scheme.storage_code] = joined_info
+
+        # 4. 将拼接后的信息添加到原数据中
+        for item in product_trouble_list:
+            storage_code = item['storage_code']
+            item['product_scheme_info'] = scheme_info_dict.get(storage_code, '')
+
+        data = {
+            "list": product_trouble_list,
+            "total": paginator.count,
+        }
+        return response.json(0, data)
+
+
+    @staticmethod
+    def edit_product_problem_status(request_dict, response):
+        try:
+            record_id = request_dict.get("id", None)
+            new_status = request_dict.get("status", None)
+
+            if not all([record_id, new_status]):
+                return response.json(444)
+
+            new_status = int(new_status)
+            try:
+                troubleshoot_record = ProductTroubleshoot.objects.get(id=record_id)
+            except ProductTroubleshoot.DoesNotExist:
+                return response.json(173)
+
+            # 更新状态
+            troubleshoot_record.status = new_status
+            updated_time = int(time.time())
+            troubleshoot_record.save()
+
+            # 如果是审批通过状态(1),创建异常事件
+            if new_status == 1:
+                device_id = troubleshoot_record.device_id
+                device_type = troubleshoot_record.device_type
+
+                # 获取UID
+                uid = device_id if device_type == 2 else CommonService.get_uid_by_serial_number(device_id)
+
+                # 获取设备型号
+                device_model = 0
+                device_scheme = DeviceScheme.objects.filter(serial_number=device_id).first()
+                if device_scheme:
+                    device_model = device_scheme.device_type
+
+                # 创建异常事件
+                AbnormalEvent.objects.create(
+                    uid=uid,
+                    event_code=troubleshoot_record.event_code,
+                    device_type=device_model,
+                    content=troubleshoot_record.remark,
+                    report_type=1,
+                    created_time=int(time.time()),
+                    event_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)))
+

+ 550 - 0
AdminController/ProductsSchemeManageController.py

@@ -0,0 +1,550 @@
+# -*- encoding: utf-8 -*-
+"""
+@File    : ProductsSchemeManageController.py
+@Time    : 2025/5/13 11:52
+@Author  : stephen
+@Email   : zhangdongming@asj6.wecom.work
+@Software: PyCharm
+"""
+import datetime
+import json
+import secrets
+import string
+import time
+from io import BytesIO
+
+import qrcode
+from django.core.paginator import Paginator, EmptyPage
+from django.db import transaction, IntegrityError
+from django.db.models import Q
+from django.http import QueryDict, HttpResponse
+from django.views import View
+
+from Ansjer.config import LOGGER
+from Model.models import ProductsScheme, DeviceScheme, DeviceTypeModel, CompanySerialModel
+from Object.ResponseObject import ResponseObject
+from Object.TokenObject import TokenObject
+
+
+class ProductsSchemeManageView(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', 'cn')
+        response = ResponseObject(language, 'pc')
+
+        # Token验证
+        try:
+            tko = TokenObject(
+                request.META.get('HTTP_AUTHORIZATION'),
+                returntpye='pc')
+            if tko.code != 0:
+                return response.json(tko.code)
+            response.lang = tko.lang
+            user_id = tko.userID
+
+        except Exception as e:
+            LOGGER.error(f"Token验证失败: {str(e)}")
+            return response.json(444)
+
+        # 操作路由映射
+        operation_handlers = {
+            'queryList': self.query_list,  # 查询列表
+            'add': self.add_scheme,  # 添加方案
+            'edit': self.edit_scheme,  # 编辑方案
+            'delete': self.delete_scheme,  # 删除方案
+            'generateQR': self.generate_qr_code,  # 生成二维码
+            'deviceSchemeList': self.device_scheme_list
+        }
+
+        handler = operation_handlers.get(operation)
+        if not handler:
+            return response.json(444, 'operation')
+
+        try:
+            return handler(user_id, request_dict, response)
+        except Exception as e:
+            LOGGER.error(f"操作{operation}执行异常:{repr(e)}")
+            return response.json(500, "服务器内部错误")
+
+    def query_list(self, user_id, request_dict, response):
+        """查询方案列表(优化点:分页封装/字段映射)"""
+        # 参数处理与验证
+        try:
+            page = int(request_dict.get('page', 1))
+            page_size = min(int(request_dict.get('pageSize', 10)), 100)  # 限制最大分页大小
+        except ValueError:
+            return response.json(444, "分页参数错误")
+
+        # 构建查询条件
+        query = Q(deleted=False)
+
+        # 特殊字段处理示例(如果需要)
+        if order_number := request_dict.get('orderNumber'):
+            query &= Q(order_number__icontains=order_number)
+        if storage_code := request_dict.get('storageCode'):
+            query &= Q(storage_code__icontains=storage_code)
+        if device_type := request_dict.get('deviceType'):
+            query &= Q(device_type=int(device_type))
+        if flash := request_dict.get('flash'):
+            query &= Q(flash__icontains=flash)
+        if ddr := request_dict.get('ddr'):
+            query &= Q(ddr__icontains=ddr)
+        if main_controller := request_dict.get('mainController'):
+            query &= Q(main_controller__icontains=main_controller)
+        if wifi := request_dict.get('wifi'):
+            query &= Q(wifi__icontains=wifi)
+        if four_g := request_dict.get('fourG'):
+            query &= Q(four_g__icontains=four_g)
+        if ad := request_dict.get('ad'):
+            query &= Q(ad__icontains=ad)
+        if sensor := request_dict.get('sensor'):
+            query &= Q(sensor__icontains=sensor)
+
+        # 使用分页器
+        queryset = ProductsScheme.objects.filter(query).order_by('-created_time')
+        paginator = Paginator(queryset, page_size)
+
+        try:
+            page_obj = paginator.page(page)
+        except EmptyPage:
+            return response.json(444, "页码超出范围")
+
+        # 序列化数据
+        data = [self._scheme_to_dict(scheme) for scheme in page_obj]
+
+        return response.json(0, {
+            'list': data,
+            'total': paginator.count,
+            'currentPage': page_obj.number,
+            'totalPages': paginator.num_pages
+        })
+
+    @staticmethod
+    def generate_timestamp_code(device_type: int = 0) -> str:
+        """
+        方案1:基于时间戳的编码
+        格式:PC+设备类型+年月日+时分秒+6位随机大写字符
+        示例:PC020250519143022-ABCDEF
+        """
+        try:
+            current_time = datetime.datetime.now()
+            date_part = current_time.strftime("%Y%m%d%H%M%S")
+            # 使用string.ascii_uppercase生成大写随机字符
+            random_part = ''.join(secrets.choice(string.ascii_uppercase) for _ in range(3))
+            return f"{date_part}-{device_type}-{random_part}"
+        except Exception as e:
+            LOGGER.error(f"生成编码失败: {repr(e)}")
+            raise
+
+    def add_scheme(self, user_id, request_dict, response):
+        """新增方案(集成自动编码生成)"""
+        try:
+            device_type = int(request_dict.get('deviceType', 0))
+
+            # 生成唯一storage_code
+            storage_code = self.generate_timestamp_code(device_type)
+
+            # 构造方案数据
+            scheme_data = {
+                'storage_code': storage_code,  # 使用生成的编码
+                'order_number': request_dict.get('orderNumber', ''),
+                'device_type': device_type,
+                'flash': request_dict.get('flash', ''),
+                'ddr': request_dict.get('ddr', ''),
+                'main_controller': request_dict.get('mainController', ''),
+                'wifi': request_dict.get('wifi', ''),
+                'four_g': request_dict.get('fourG', ''),
+                'ad': request_dict.get('ad', ''),
+                'sensor': request_dict.get('sensor', ''),
+                'order_quantity': int(request_dict.get('orderQuantity', 0)),
+                'customer_code': request_dict.get('customerCode', ''),
+                'phy': request_dict.get('phy', ''),
+                'remark': request_dict.get('remark', ''),
+                'created_time': int(time.time()),
+                'updated_time': int(time.time()),
+                'created_by': user_id,
+                'updated_by': user_id
+            }
+
+            scheme = ProductsScheme.objects.create(**scheme_data)
+            return response.json(0, {
+                'id': scheme.id,
+                'storageCode': storage_code  # 返回生成的编码
+            })
+
+        except IntegrityError as e:
+            LOGGER.error(f"唯一性冲突: {str(e)}")
+            return response.json(173, "数据已存在")
+        except ValueError as e:
+            return response.json(444, "参数类型错误")
+        except Exception as e:
+            LOGGER.exception(f"添加方案异常:{repr(e)}")
+            return response.json(500, "生成编码失败")
+
+        # 可编辑字段白名单(前端字段名: 模型字段名)
+
+    EDITABLE_FIELDS = {
+        'orderNumber': 'order_number',
+        'deviceType': 'device_type',
+        'flash': 'flash',
+        'ddr': 'ddr',
+        'mainController': 'main_controller',
+        'wifi': 'wifi',
+        'fourG': 'four_g',
+        'ad': 'ad',
+        'sensor': 'sensor',
+        'orderQuantity': 'order_quantity',
+        'customerCode': 'customer_code',
+        'phy': 'phy',
+        'remark': 'remark'
+    }
+
+    # 需要类型转换的字段配置(字段名: 转换函数)
+    FIELD_CONVERTERS = {
+        'deviceType': int,
+        'orderQuantity': int
+    }
+
+    @transaction.atomic
+    def edit_scheme(self, user_id, request_dict, response):
+        """
+        方案编辑接口(事务原子性保证)
+        优化特性:
+        1. 前端字段到模型字段的自动映射
+        2. 字段级白名单过滤
+        3. 智能类型转换
+        4. 最小化更新字段
+        5. 并发安全控制
+        """
+        # ================= 参数校验阶段 =================
+        if 'id' not in request_dict:
+            return response.json(444, "缺少方案ID")
+
+        try:
+            scheme_id = int(request_dict['id'])
+        except (ValueError, TypeError):
+            LOGGER.warning(f"非法方案ID: {request_dict.get('id')}")
+            return response.json(444, "方案ID格式错误")
+
+        # ================= 数据获取阶段 =================
+        try:
+            # 使用select_for_update锁定记录,防止并发修改
+            scheme = ProductsScheme.objects.select_for_update().get(
+                id=scheme_id,
+                deleted=False
+            )
+        except ProductsScheme.DoesNotExist:
+            LOGGER.info(f"方案不存在: {scheme_id}")
+            return response.json(173, "方案不存在")
+        except Exception as e:
+            LOGGER.error(f"数据库查询异常: {str(e)}")
+            return response.json(500, "系统错误")
+
+        # ================= 数据处理阶段 =================
+        update_fields = []
+        update_data = {'updated_time': int(time.time()), 'updated_by': user_id}
+
+        # 遍历所有请求参数
+        for frontend_field, value in request_dict.items():
+            # 1. 白名单校验
+            if frontend_field not in self.EDITABLE_FIELDS:
+                continue
+
+            # 2. 获取对应的模型字段名
+            model_field = self.EDITABLE_FIELDS[frontend_field]
+
+            # 3. 类型转换处理
+            if frontend_field in self.FIELD_CONVERTERS:
+                try:
+                    value = self.FIELD_CONVERTERS[frontend_field](value)
+                except (ValueError, TypeError):
+                    LOGGER.warning(f"字段类型错误 {frontend_field}={value}")
+                    return response.json(444, f"{frontend_field}类型不合法")
+
+            # 4. 值变更检查(避免无意义更新)
+            if getattr(scheme, model_field) != value:
+                update_data[model_field] = value
+                update_fields.append(model_field)
+
+        # 无实际修改时快速返回
+        if not update_fields:
+            LOGGER.debug("无字段变更")
+            return response.json(0)
+
+        # ================= 数据持久化阶段 =================
+        try:
+            # 动态生成更新SQL:UPDATE ... SET field1=%s, field2=%s
+            ProductsScheme.objects.filter(id=scheme_id).update(**update_data)
+
+            LOGGER.info(f"方案更新成功: {scheme_id} 修改字段: {update_fields}")
+            return response.json(0)
+        except IntegrityError as e:
+            LOGGER.error(f"数据唯一性冲突: {str(e)}")
+            return response.json(177)
+        except Exception as e:
+            LOGGER.exception("方案更新异常")
+            return response.json(500, "系统错误")
+
+    def delete_scheme(self, user_id, request_dict, response):
+        """删除方案(优化点:逻辑删除优化)"""
+        # 参数校验
+        if 'id' not in request_dict:
+            return response.json(444, "缺少方案ID")
+
+        try:
+            # 使用update直接进行逻辑删除,提高效率
+            rows = ProductsScheme.objects.filter(
+                id=request_dict['id'],
+                deleted=False
+            ).update(
+                deleted=True,
+                updated_by=user_id,
+                updated_time=int(time.time())
+            )
+
+            if rows == 0:
+                return response.json(173, "方案不存在")
+
+            return response.json(0)
+        except ValueError:
+            return response.json(500, "方案ID格式错误")
+
+    def _scheme_to_dict(self, scheme):
+        """方案对象序列化(优化点:集中管理序列化逻辑)"""
+        return {
+            'id': scheme.id,
+            'orderNumber': scheme.order_number,
+            'storageCode': scheme.storage_code,
+            'deviceType': scheme.device_type,
+            'flash': scheme.flash,
+            'ddr': scheme.ddr,
+            'mainController': scheme.main_controller,
+            'wifi': scheme.wifi,
+            'fourG': scheme.four_g,
+            'ad': scheme.ad,
+            'sensor': scheme.sensor,
+            'orderQuantity': scheme.order_quantity,
+            'customerCode': scheme.customer_code,
+            'phy': scheme.phy,
+            'remark': scheme.remark,
+            'createdTime': scheme.created_time,
+            'createdBy': scheme.created_by
+        }
+
+    def generate_qr_code(self, user_id, request_dict, response):
+        """生成方案二维码"""
+        try:
+            scheme_id = int(request_dict.get('scheme_id'))
+            scheme = ProductsScheme.objects.get(id=scheme_id, deleted=False)
+
+            # 创建二维码(示例生成包含方案ID+名称)
+            qr = qrcode.QRCode(
+                version=1,
+                error_correction=qrcode.constants.ERROR_CORRECT_L,
+                box_size=10,
+                border=4,
+            )
+
+            # 组织二维码内容(根据业务需求自定义)
+            qr_data = json.dumps({
+                "sc": scheme.storage_code,
+                "t": 'NVR' if scheme.device_type == 1 else 'IPC',
+                "f": scheme.flash,
+                "ddr": scheme.ddr,
+                "mc": scheme.main_controller,
+                "wifi": scheme.wifi,
+                "mode4G": scheme.four_g,
+                "ad": scheme.ad,
+                "s": scheme.sensor,
+                'uc': scheme.customer_code,
+                'phy': scheme.phy,
+                "num": scheme.order_quantity,
+                "ct": scheme.created_time
+            })
+            qr.add_data(qr_data)
+            qr.make(fit=True)
+
+            # 生成图片
+            img = qr.make_image(fill_color="black", back_color="white")
+
+            # 将图片转为字节流
+            buffer = BytesIO()
+            img.save(buffer, format="PNG")
+
+            # 返回文件响应
+            return HttpResponse(
+                buffer.getvalue(),
+                content_type="image/png",
+                headers={
+                    'Content-Disposition': f'attachment; filename="scheme_{scheme.id}_qr.png"'
+                }
+            )
+
+        except ProductsScheme.DoesNotExist:
+            return response.json(173, "方案不存在")
+        except Exception as e:
+            LOGGER.error(f"生成二维码失败: {str(e)}")
+            return response.json(500, "生成失败")
+
+    @staticmethod
+    def device_scheme_list(user_id, request_dict, response):
+        """
+        查询设备方案列表
+        @param request_dict: 请求参数
+        @param response: 响应对象
+        @return: 响应对象包含设备方案列表
+        """
+        # 提取请求参数并提供默认值
+        filters = {
+            'serial_number': request_dict.get("serialNumber"),
+            'storage_code': request_dict.get("storageCode"),
+            'device_type': request_dict.get("deviceType"),
+            'phone_model': request_dict.get("phoneModel"),
+        }
+        page = int(request_dict.get("page", 1))
+        page_size = int(request_dict.get("pageSize", 20))
+
+        try:
+            # 构建基础查询集
+            queryset = DeviceScheme.objects.all().order_by('-created_time')
+
+            # 应用过滤条件
+            if filters['serial_number']:
+                queryset = queryset.filter(serial_number=filters['serial_number'])
+            if filters['storage_code']:
+                queryset = queryset.filter(storage_code__icontains=filters['storage_code'])
+            if filters['device_type']:
+                queryset = queryset.filter(device_type=filters['device_type'])
+            if filters['phone_model']:
+                queryset = queryset.filter(phone_model__icontains=filters['phone_model'])
+
+            # 分页处理
+            paginator = Paginator(queryset, page_size)
+            try:
+                current_page = paginator.page(page)
+            except EmptyPage:
+                current_page = paginator.page(paginator.num_pages)
+
+            # 批量预取关联数据
+            device_list = []
+            if current_page.object_list:
+                # 准备批量查询所需数据
+                storage_codes = {device.storage_code for device in current_page}
+                serial_prefixes = {device.serial_number[:6] for device in current_page}
+                device_types = {device.device_type for device in current_page}
+
+                # 批量获取关联数据
+                product_schemes_map = {
+                    ps.storage_code: ps
+                    for ps in ProductsScheme.objects.filter(storage_code__in=storage_codes)
+                }
+                device_type_names = {
+                    dt['type']: dt['name']
+                    for dt in DeviceTypeModel.objects.filter(type__in=device_types).values('type', 'name')
+                }
+                serial_statuses = {
+                    cs.serial_number: cs
+                    for cs in CompanySerialModel.objects.filter(serial_number__in=serial_prefixes)
+                }
+
+                # 构建设备数据
+                device_list = [
+                    ProductsSchemeManageView.build_device_data(
+                        device,
+                        product_schemes_map.get(device.storage_code),
+                        device_type_names.get(device.device_type, device.device_type),
+                        serial_statuses.get(device.serial_number[:6])
+                    )
+                    for device in current_page
+                ]
+
+            return response.json(0, {
+                'list': device_list,
+                'total': paginator.count
+            })
+
+        except Exception as e:
+            LOGGER.error("设备方案列表查询异常")
+            return response.json(500, f'服务器错误: {str(e)}')
+
+    @staticmethod
+    def build_device_data(device, product_scheme, device_type_name, serial_status):
+        """构建单个设备数据字典"""
+        # 设备基础数据
+        device_data = {
+            'id': device.id,
+            'serialNumber': device.serial_number,
+            'deviceType': device_type_name,
+            'phoneModel': device.phone_model,
+            'storageCode': device.storage_code,
+            'createdTime': device.created_time,
+            'updatedTime': device.updated_time,
+            'snStatus': 'N/A',
+            'aTime': 'N/A',
+        }
+
+        # 序列号激活信息
+        if device.device_type != 0 and serial_status:
+            device_data['snStatus'] = '已激活' if serial_status.status > 1 else '未激活'
+            device_data['aTime'] = datetime.datetime.fromtimestamp(
+                serial_status.update_time
+            ).strftime('%Y-%m-%d %H:%M:%S')
+
+        # 添加产品方案信息
+        if product_scheme:
+            # 构建方案数据字符串
+            scheme_parts = [
+                product_scheme.flash,
+                product_scheme.ddr,
+                product_scheme.main_controller,
+                product_scheme.wifi,
+                product_scheme.four_g,
+                product_scheme.ad,
+                product_scheme.phy,
+                product_scheme.sensor
+            ]
+            scheme_data = '+'.join(filter(None, map(str, scheme_parts)))
+
+            device_data.update({
+                'orderNumber': product_scheme.order_number or '',
+                'type': 'NVR' if product_scheme.device_type == 1 else 'IPC',
+                'flash': product_scheme.flash or '',
+                'ddr': product_scheme.ddr or '',
+                'mainController': product_scheme.main_controller or '',
+                'wifi': product_scheme.wifi or '',
+                'fourG': product_scheme.four_g or '',
+                'ad': product_scheme.ad or '',
+                'phy': product_scheme.phy or '',
+                'sensor': product_scheme.sensor or '',
+                'orderQuantity': product_scheme.order_quantity or '',
+                'customerCode': product_scheme.customer_code or '',
+                'schemeData': scheme_data,
+                'remark': product_scheme.remark or ''
+            })
+
+        return device_data

+ 10 - 1
Ansjer/urls.py

@@ -5,7 +5,8 @@ from django.urls import path, re_path
 from AdminController import UserManageController, RoleController, MenuController, TestServeController, \
     ServeManagementController, LogManagementController, DeviceManagementController, VersionManagementController, \
     AiServeController, SurveysManageController, SerialManageController, MessageMangementController, \
-    EvaluationActivityController, CampaignController
+    EvaluationActivityController, CampaignController, DataDictManageController, ProductsSchemeManageController, \
+    ProblemEntryManagementController
 from AdminController.CloudServiceManage import AgentDeviceController, AgentCustomerController, AgentOrderController
 from Controller import FeedBack, EquipmentOTA, EquipmentInfo, AdminManage, AppInfo, \
     Test, MealManage, DeviceManage, EquipmentStatus, SysManage, DeviceLog, LogAccess, \
@@ -438,6 +439,14 @@ urlpatterns = [
     re_path('activityManagement/(?P<operation>.*)', EvaluationActivityController.EvaluationActivityView.as_view()),
     # 活动中心
     re_path('activityCenter/(?P<operation>.*)', ActivityCenterController.ActivityCenterView.as_view()),
+    # 数据字典
+    re_path('dataDictView/(?P<operation>.*)', DataDictManageController.DataDictView.as_view()),
+    # 产品方案管理
+    re_path('productsSchemeManage/(?P<operation>.*)',
+            ProductsSchemeManageController.ProductsSchemeManageView.as_view()),
+    # 产品问题录入
+    re_path('problemEntryManage/(?P<operation>.*)',
+            ProblemEntryManagementController.ProblemEntryView.as_view()),
     # 后台界面接口 -------------------------------------------------------------------------------------------------------
 
     # 定时任务接口

+ 1 - 1
Controller/CheckUserData.py

@@ -45,7 +45,7 @@ class DataValid:
         # 手机号码正则
         self.re_mobile = re.compile(r'^\d{1,16}$')
         # 邮箱地址正则
-        self.re_email = re.compile(r'^[A-Za-z0-9\u4e00-\u9fa5\.\_\-]+@[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)+$')
+        self.re_email = re.compile(r'^[A-Za-z0-9\u4e00-\u9fa5\.\_\-\+]+@[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)+$')
 
     def name_validate(self, value):
         if self.re_name.match(value):

+ 90 - 0
Controller/IncomeProductsController.py

@@ -0,0 +1,90 @@
+import time
+
+from django.views import View
+
+from Model.models import DeviceScheme
+from Object.RedisObject import RedisObject
+from Service.CommonService import CommonService
+from Ansjer.config import LOGGER
+
+class IncomeProductsView(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 validation(self, request_dict, request, operation):
+        token_code, user_id, response = CommonService.verify_token_get_user_id(request_dict, request)
+        if operation == 'income':  # 设备关联产品方案
+            return self.income_device_scheme(request_dict, response)
+        elif operation == 'statistics':
+            return self.statistics_device_scheme(request_dict, response)
+        else:
+            return response.json(414)
+
+    @staticmethod
+    def income_device_scheme(request_dict, response):
+        """
+        设备关联产品方案
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        storage_code = request_dict.get("storageCode", None)
+        serial_number = request_dict.get("serialNumber", None)
+        device_type = request_dict.get("deviceType", None)
+        phone_model = request_dict.get("phoneModel", None)
+
+        if not all([storage_code, serial_number, phone_model, device_type]):
+            return response.json(444)
+
+        full_serial_number = serial_number
+        # device_type = 0 时表示传的是uid
+        if device_type != "0":
+            serial_number = serial_number[0:9]
+
+        try:
+            LOGGER.info(f"设备关联产品方案, storageCode: {storage_code}, serialNumber: {serial_number}")
+            device_scheme_qs = DeviceScheme.objects.filter(serial_number=serial_number)
+            if device_scheme_qs.exists():
+                storage_code = device_scheme_qs.first().storage_code
+                LOGGER.info(f"设备关联产品方案, serialNumber已存在, storageCode: {storage_code}, serialNumber: {serial_number}")
+                return response.json(174, f"数据已存在, storage_code为: {storage_code}")
+
+            now_time = int(time.time())
+
+            DeviceScheme.objects.create(storage_code=storage_code, serial_number=serial_number,
+                                        full_serial_number=full_serial_number, device_type=device_type,
+                                        phone_model=phone_model, created_time=now_time,
+                                        updated_time=now_time)
+
+            LOGGER.info(f"设备关联产品方案, serialNumber录入成功, storageCode: {storage_code}, serialNumber: {serial_number}")
+
+            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)))
+
+    @staticmethod
+    def statistics_device_scheme(request_dict, response):
+        """
+        统计设备关联产品方案
+        @param request_dict:
+        @param response:
+        @return:
+        """
+        try:
+            storage_code = request_dict.get("storageCode", None)
+            if not storage_code:
+                return response.json(444)
+            device_num = DeviceScheme.objects.filter(storage_code=storage_code).count()
+            return response.json(0, {"storageCode": storage_code,"deviceNum": device_num})
+        except Exception as e:
+            print(e)
+            return response.json(500, 'error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))

+ 28 - 21
Controller/UserDevice/DeviceReportController.py

@@ -134,51 +134,58 @@ class DeviceReportView(View):
             device_id = request_dict.get('device_id')
             if not device_id:
                 return response.json(444, "设备ID不能为空")
-            tz_offset = float(request_dict.get('tz', 0))  # 允许小数时区如5.5
+
+            tz_offset = float(request_dict.get('tz', 0))
             if not (-12 <= tz_offset <= 14):
-                raise ValueError
-            tz = pytz.FixedOffset(int(tz_offset * 60))  # 转换为分钟偏移
+                raise ValueError("时区偏移超出范围")
+            tz = pytz.FixedOffset(int(tz_offset * 60))
 
-            # 计算日期范围(关键修改:end_time取昨日23:59:59)
+            # 计算日期范围
             query_days = int(request_dict.get('days', 7))
             client_now = datetime.now(tz)
-            end_time = client_now.replace(
-                hour=23, minute=59, second=59
-            ) - timedelta(days=1)  # 截止到客户端昨日
 
-            start_time = end_time - timedelta(days=query_days - 1)
-            start_time = start_time.replace(hour=0, minute=0, second=0)
+            # 数据日期范围 = [今天 - query_days, 昨天]
+            end_date = (client_now - timedelta(days=1)).date()  # 昨天
+            start_date = end_date - timedelta(days=query_days - 1)  # 起始日期
+
+            # 转换查询范围到UTC时间戳
+            start_utc = int(tz.localize(
+                datetime.combine(start_date, datetime.min.time())
+            ).astimezone(pytz.utc).timestamp())
 
-            # 转换时间范围到UTC时间戳
-            start_utc_timestamp = int(start_time.astimezone(pytz.utc).timestamp())
-            end_utc_timestamp = int(end_time.astimezone(pytz.utc).timestamp())
+            end_utc = int(tz.localize(
+                datetime.combine(end_date + timedelta(days=1), datetime.min.time())
+            ).astimezone(pytz.utc).timestamp())
 
             # 查询数据库
             records = DeviceDailyReport.objects.filter(
                 device_id=device_id,
                 type=1,
-                report_time__gte=start_utc_timestamp,
-                report_time__lte=end_utc_timestamp
+                report_time__gte=start_utc,
+                report_time__lt=end_utc
             ).order_by('report_time')
 
-            # 构建日期-电量映射(按客户端时区
+            # 构建日期-电量映射(直接使用上报日期
             date_battery_map = {}
             for record in records:
-                local_date = datetime.fromtimestamp(record.report_time, tz).date()
-                date_str = local_date.strftime("%Y-%m-%d")
-                date_battery_map[date_str] = record.battery_level
+                # 关键变更:直接使用上报日期作为数据日期
+                record_date = datetime.fromtimestamp(record.report_time, tz).date()
+
+                # 只记录查询范围内的数据
+                if start_date <= record_date <= end_date:
+                    date_battery_map[record_date.isoformat()] = record.battery_level
 
             # 生成完整日期序列
             report_data = []
             for day_offset in range(query_days):
-                current_date = (end_time.date() - timedelta(days=query_days - 1 - day_offset))
+                current_date = start_date + timedelta(days=day_offset)
                 report_data.append({
                     "index": current_date.day - 1,  # 日期下标从0开始
-                    "battery": date_battery_map.get(current_date.strftime("%Y-%m-%d"), 0),
+                    "battery": date_battery_map.get(current_date.isoformat(), 0),
                     "time": current_date.strftime("%Y-%m-%d")
                 })
 
             return response.json(0, report_data)
         except Exception as e:
-            LOGGER.error('查询设备电量上报列表异常error_line:{}, error_msg:{}'.format(e.__traceback__.tb_lineno, repr(e)))
+            LOGGER.error(f'查询设备电量上报列表异常: {e}')
             return response.json(0, {})