Sfoglia il codice sorgente

任务界面更换

flower_mr 3 settimane fa
parent
commit
62f076d54a

+ 31 - 9
bin/queries.py

@@ -50,8 +50,7 @@ class LocationQueries:
         container = ContainerListModel.objects.filter(
             container_code=container_code
         ).first()
-        if not container:
-            return None
+
 
         detail = ContainerDetailModel.objects.filter(
             container=container.id
@@ -63,13 +62,36 @@ class LocationQueries:
                 'container': container,
                 'class': 2 ,
             }
-            
-        return {
-            'status': detail.batch.status if detail.batch else None,
-            'number': detail.batch.bound_number if detail.batch else None,
-            'container': container,
-            'class': detail.goods_class if detail else None,
-        }
+        elif detail.batch is None:
+            return {
+                'status': None,
+                'number': None,
+                'container': container,
+                'class': 2,
+            }
+        else:
+            detail_all = ContainerDetailModel.objects.filter(
+                container=container.id
+                ).exclude(status=3).all()
+            # 统计该拖盘上的不同批次数目
+            batch_count =  len(set([item.batch.id for item in detail_all]))
+            if batch_count > 1:
+                for item in detail_all:
+                    item.goods_class = 3
+                    item.save()
+                return {
+                    'status': None,
+                    'number': None,
+                    'container': container,
+                    'class': 3,
+                }
+            else:
+                return {
+                    'status': detail.batch.status ,
+                    'number': detail.batch.bound_number ,
+                    'container': container,
+                    'class': 1,
+                }
 
     @staticmethod
     def get_group_capacity():

+ 21 - 1
bin/services.py

@@ -22,7 +22,10 @@ class AllocationService:
         if not batch_info :
             raise ValueError("无效的容器或批次信息")
         if not batch_info['number']:
-            return cls._container_group_allocation(container_code,batch_info, start_point)
+            if batch_info['class'] == 2:
+                return cls._container_group_allocation(container_code,batch_info, start_point)
+            else:
+                return cls._container_scattered_allocation(container_code,batch_info, start_point)
         if batch_info['status'] == 1 or batch_info['status'] == 0:
             return cls._first_allocation(container_code, batch_info, start_point)
         elif batch_info['status'] == 2:
@@ -93,6 +96,23 @@ class AllocationService:
         locations = AllocationAlgorithm.allocation_plan_left_right(formatted,batch_info['number'],start_point, container_code)
         return cls._execute_allocation_no_batch(locations, start_point,container_code,batch_info)
     
+    @classmethod
+    def _container_scattered_allocation(cls,container_code, batch_info, start_point):
+        total = 1
+        batch_info['number'] = 'ContainerScattered'+str(container_code)+str(timezone.now().strftime('%Y%m%d'))
+        layer_cap = LocationQueries.get_group_capacity()
+        pressure = LocationQueries.get_current_pressure()
+
+        solution, new_pressure = AllocationAlgorithm.generate_plan(total, layer_cap, pressure)
+        if not solution:
+            raise RuntimeError("无法生成有效分配方案")
+
+        formatted = json.loads(AllocationAlgorithm.format_solution(solution))
+        LocationUpdates.save_allocation_plan(batch_info['number'], formatted, new_pressure)
+        # 使用距离最近的库位,增加切换
+        locations = AllocationAlgorithm.allocation_plan_left_right(formatted,batch_info['number'],start_point, container_code)
+        return cls._execute_allocation(locations, start_point,container_code,batch_info)
+
     @classmethod
     def _execute_allocation_no_batch(cls, solution, start_point,container_code,batch_info):
         LocationUpdates.update_group_status_reserved(solution)

+ 40 - 0
bin/views.py

@@ -530,6 +530,46 @@ class LocationAllocation:
             logger.error(f"更新批次状态失败:{str(e)}")
             print(f"更新批次状态失败:{str(e)}")
             return False  
+        
+    def update_batch_goods_in_location_qty(self,container_code,taskworking):
+        """
+        更新批次库位入库数量
+        :param container_code: 托盘码
+        :param goods_in_location_qty: 库位入库数量
+        :return:
+        """
+        try:
+            # 1. 通过托盘码获取托盘详情
+            container = ContainerListModel.objects.filter(
+                container_code=container_code
+            ).first()
+
+            if not container:
+                logger.error(f"托盘 {container_code} 不存在")
+                print(f"托盘 {container_code} 不存在")
+                return None
+            # 2. 获取关联的批次明细
+            container_detail = ContainerDetailModel.objects.filter(
+                container=container.id
+            ).exclude(status=3).all()
+            if not container_detail:
+                print (f"托盘 {container_code} 未组盘")
+                logger.error(f"托盘 {container_code} 未组盘_from update_batch_goods_in_location_qty")
+                return None
+            for item in container_detail:
+                if item.goods_class == 2:
+                    continue
+                item.batch.goods_in_location_qty += item.goods_qty * taskworking
+                item.batch.save()
+                print(f"更新批次库位入库数量成功!")
+            return True
+        except Exception as e:       
+            logger.error(f"更新批次库位入库数量失败:{str(e)}")
+            print(f"更新批次库位入库数量失败:{str(e)}")
+            return False  
+
+
+    
     def get_batch_status(self,container_code):
         """
         获取批次状态

+ 1 - 0
bound/urls.py

@@ -29,6 +29,7 @@ re_path(r'^outdetail/(?P<pk>\d+)/$', views.OutBoundDetailViewSet.as_view({
 }), name="outbounddetail_1"),
 
 path(r'batch/', views.BoundBatchViewSet.as_view({"get": "list", "post": "create" }), name="boundbatch"), 
+path(r'batch/container/', views.BatchContainerAPIView.as_view(), name="batchcontainer"), 
 
 re_path(r'^batch/(?P<pk>\d+)/$', views.BoundBatchViewSet.as_view({
     'get': 'retrieve',

+ 38 - 1
bound/views.py

@@ -20,7 +20,7 @@ from .filter import OutBatchFilter,OutBoundDetailFilter,BatchlogFilter
 from warehouse.models import ListModel as warehouse
 from staff.models import ListModel as staff
 from rest_framework.permissions import AllowAny
-
+from rest_framework.views import APIView
 
 class BoundListViewSet(viewsets.ModelViewSet):
     """
@@ -572,3 +572,40 @@ class BoundBatchLogViewSet(viewsets.ModelViewSet):
         serializer = self.get_serializer(qs, many=False)
         headers = self.get_success_headers(serializer.data)
         return Response(serializer.data, status=200, headers=headers)  
+
+
+class BatchContainerAPIView(APIView):
+    """
+        post:
+            返回批次对应的container列表
+            """
+    # authentication_classes = []  # 禁用所有认证类
+    # permission_classes = [AllowAny]  # 允许任意访问
+    def post(self, request):
+        data = request.data
+        batch_id = data.get('batch_id')
+        from container.models import ContainerDetailModel
+
+
+        container_detail_all = ContainerDetailModel.objects.filter(batch_id=batch_id, is_delete=False).exclude(status=3).all()
+        container_dict = {}
+
+        for container_detail in container_detail_all:
+            container_id = container_detail.container_id
+
+            if container_id not in container_dict:
+                container_dict[container_id] = {
+                    'id': container_id,
+                    'goods_code': container_detail.goods_code,
+                    'goods_desc': container_detail.goods_desc,
+             
+                    'container_code': container_detail.container.container_code,
+                    'current_location': container_detail.container.current_location,
+                    'goods_qty': container_detail.goods_qty-container_detail.goods_out_qty,
+                    'class':container_detail.goods_class
+                }
+            else:
+                container_dict[container_id]['goods_qty'] += container_detail.goods_qty-container_detail.goods_out_qty
+        container_dict = list(container_dict.values())
+        return_data = {'code': 200,'msg': 'Success Create', 'data': container_dict}
+        return Response(return_data)

+ 29 - 2
container/filter.py

@@ -1,5 +1,5 @@
 from django_filters import FilterSet, NumberFilter, CharFilter
-from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel
+from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel,ContainerWCSModel
 
 class ContainerListFilter(FilterSet):
 
@@ -54,6 +54,32 @@ class ContainerOperationFilter(FilterSet):
             "memo": ['exact', 'icontains'],
         }
 
+class WCSTaskFilter(FilterSet):
+     class Meta:
+        model = ContainerWCSModel
+        fields = {
+            "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "taskid": ['exact', 'icontains'],
+            "batch": ['exact'],
+            "batch_out": ['exact'],
+            "bound_list": ['exact'],
+            "batch_number": ['exact', 'icontains'],
+            "sequence": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "priority": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "month": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "tasktype": ['exact', 'icontains'],
+            "tasknumber": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "order_number": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "container": ['exact', 'icontains'],
+            "current_location": ['exact', 'icontains'],
+            "target_location": ['exact', 'icontains'],
+            "message": ['exact', 'icontains'],
+            "working": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "status": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "create_time": ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
+            "is_delete": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+        }
+
 class TaskFilter(FilterSet):
      class Meta:
         model = TaskModel
@@ -64,4 +90,5 @@ class TaskFilter(FilterSet):
             "batch_detail": ['exact'],
             "container_detail__goods_code": ['exact', 'icontains'],
             "container_detail__goods_desc": ['exact', 'icontains'],  
-            }
+            }
+        

+ 18 - 0
container/migrations/0017_alter_containerdetailmodel_goods_class.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2025-05-17 15:16
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0016_containerwcsmodel_batch_number_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='containerdetailmodel',
+            name='goods_class',
+            field=models.IntegerField(choices=[(1, '成品'), (2, '空盘'), (3, '散盘')], default=1, verbose_name='货品类别'),
+        ),
+    ]

+ 2 - 2
container/models.py

@@ -38,7 +38,7 @@ class ContainerDetailModel(models.Model):
     BATCH_CLASS = (
         (1, '成品'),
         (2, '空盘'),
-        (3, '成品'),
+        (3, '散盘'),
     )
     month = models.IntegerField(verbose_name='月份')
     container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='details')
@@ -131,7 +131,7 @@ class ContainerWCSModel(models.Model):
         db_table = 'container_wcs'
         verbose_name = 'ContainerWCS'
         verbose_name_plural = "ContainerWCS"
-        ordering = ['sequence']
+        ordering = ['-create_time']
     
     def to_dict(self):
         return {

+ 8 - 0
container/serializers.py

@@ -5,6 +5,14 @@ from bound.models import BoundBatchModel,BoundDetailModel
 
 from utils import datasolve
 
+class WCSTaskGetSerializer(serializers.ModelSerializer):
+    class Meta:
+        # 指定模型和排除字段
+        model = ContainerWCSModel
+        fields= '__all__'
+        read_only_fields = ['id']
+
+
 class ContainerListGetSerializer(serializers.ModelSerializer):
     # 定义主单列表的序列化器,用于获取操作,字段只读
     container_code = serializers.IntegerField(read_only=True, required=False)

+ 3 - 0
container/urls.py

@@ -23,6 +23,9 @@ re_path(r'^operate/(?P<pk>\d+)/$', views.ContainerOperateViewSet.as_view({
     'patch': 'partial_update',
 }), name="ContainerDetail_1"),
 
+path(r'wcs_task/', views.WCSTaskViewSet.as_view({"get": "list", "post": "create"}), name="Task"),
+
+
 path(r'task/', views.TaskViewSet.as_view({"get": "list", "post": "create"}), name="Task"),
 re_path(r'^task/(?P<pk>\d+)/$', views.TaskViewSet.as_view({
     'get': 'retrieve',

+ 55 - 4
container/views.py

@@ -23,7 +23,8 @@ from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSeriali
 from .serializers import ContainerListGetSerializer,ContainerListPostSerializer
 from .serializers import ContainerOperationGetSerializer,ContainerOperationPostSerializer
 from .serializers import TaskGetSerializer,TaskPostSerializer
-from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter
+from .serializers import WCSTaskGetSerializer
+from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter,WCSTaskFilter
 
 from rest_framework.permissions import AllowAny
 import threading
@@ -106,6 +107,51 @@ class ContainerListViewSet(viewsets.ModelViewSet):
         headers = self.get_success_headers(serializer.data)
         return Response(serializer.data, status=200, headers=headers)
 
+class WCSTaskViewSet(viewsets.ModelViewSet):
+    """
+        retrieve:
+            Response a data list(get)
+            list:
+            Response a data list(all)
+        create:
+            Create a data line(post)
+
+        delete:
+            Delete a data line(delete)
+
+    """
+    # authentication_classes = []  # 禁用所有认证类
+    # permission_classes = [AllowAny]  # 允许任意访问
+    pagination_class = MyPageNumberPagination   
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['-id', "-create_time", "update_time", ]
+    filter_class = WCSTaskFilter
+ 
+    def get_project(self):
+        try:
+            id = self.kwargs.get('pk')
+            return id
+        except:
+            return None
+        
+    def get_queryset(self):
+        id = self.get_project()
+        if self.request.user:
+            if id is None:
+                return ContainerWCSModel.objects.filter()
+            else:
+                return ContainerWCSModel.objects.filter(id=id)
+        else:
+            return ContainerWCSModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list', 'destroy','retrieve']:
+            return WCSTaskGetSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+
+
 class TaskViewSet(viewsets.ModelViewSet):
     """
         retrieve:
@@ -315,6 +361,9 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
                     if batch_info['class'] == 2:
                         self.generate_task_no_batch(container, current_location, allocation_target_location,batch_id,location_min_value.c_number) 
                         self.generate_container_operate_no_batch(container_obj, batch_id, allocation_target_location)
+                    elif batch_info['class'] == 3:
+                        self.generate_task_no_batch(container, current_location, allocation_target_location,batch_id,location_min_value.c_number) 
+                        self.generate_container_operate_no_batch(container_obj, batch_id, allocation_target_location)
                     else:   
                         self.generate_task(container, current_location, allocation_target_location,batch_id,location_min_value.c_number)  # 生成任务
                         self.generate_container_operate(container_obj, batch_id, allocation_target_location)
@@ -331,7 +380,7 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
                     }
                     container_obj.target_location = allocation_target_location
                     container_obj.save()
-                    if batch_info['class'] == 1:
+                    if batch_info['class'] == 1 or batch_info['class'] == 3:
                         self.inport_update_task(current_task.id, container_obj.id)
 
 
@@ -488,7 +537,6 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
             if self.is_already_at_target(container_obj, data.get('current_location')):
                 return self.handle_target_reached(container_obj, data)
             elif task:
-               
                 data_return = {
                     'code': '200',
                     'message': '任务已存在,重新下发',
@@ -539,6 +587,9 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
         logger.info(f"托盘 {container_obj.container_code} 已在目标位置")
         task = self.get_task_by_tasknumber(data)
         self.update_pressure_values(task, container_obj)
+        if task.working == 1:
+            alloca = LocationAllocation()
+            alloca.update_batch_goods_in_location_qty(container_obj.container_code, 1)
         task = self.process_task_completion(data)
         if not task:
             return Response({'code': '400', 'message': '任务不存在', 'data': data},
@@ -596,7 +647,7 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
         """更新仓储系统状态"""
         allocator = LocationAllocation()
         location_code = self.get_location_code(container_obj.target_location)
-        
+    
         # 链式更新操作
         update_operations = [
             (allocator.update_location_status, location_code, 'occupied'),

+ 167 - 0
logs/error.log

@@ -7939,3 +7939,170 @@ django.db.utils.OperationalError: database is locked
 [2025-05-16 16:49:34,749][django.server.log_message():187] [ERROR] "GET /container/container_wcs/ HTTP/1.1" 500 60
 [2025-05-16 16:52:04,083][django.request.log_response():241] [ERROR] Internal Server Error: /container/container_wcs/
 [2025-05-16 16:52:04,086][django.server.log_message():187] [ERROR] "GET /container/container_wcs/ HTTP/1.1" 500 60
+[2025-05-16 21:13:52,195][django.request.log_response():241] [ERROR] Internal Server Error: /wms/inboundBills/
+Traceback (most recent call last):
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\backends\utils.py", line 89, in _execute
+    return self.cursor.execute(sql, params)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\backends\sqlite3\base.py", line 357, in execute
+    return Database.Cursor.execute(self, query, params)
+sqlite3.OperationalError: database is locked
+
+The above exception was the direct cause of the following exception:
+
+Traceback (most recent call last):
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
+    response = get_response(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 497, in dispatch
+    self.initial(request, *args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 416, in initial
+    self.check_throttles(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 359, in check_throttles
+    if not throttle.allow_request(request, self):
+  File "D:\Document\code\vue\greater_wms\utils\throttle.py", line 27, in allow_request
+    i.delete()
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\models\base.py", line 1118, in delete
+    return collector.delete()
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\models\deletion.py", line 448, in delete
+    count = sql.DeleteQuery(model).delete_batch(
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\models\sql\subqueries.py", line 42, in delete_batch
+    num_deleted += self.do_query(
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\models\sql\subqueries.py", line 20, in do_query
+    cursor = self.get_compiler(using).execute_sql(CURSOR)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\models\sql\compiler.py", line 1398, in execute_sql
+    cursor.execute(sql, params)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\backends\utils.py", line 103, in execute
+    return super().execute(sql, params)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\backends\utils.py", line 67, in execute
+    return self._execute_with_wrappers(
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\backends\utils.py", line 80, in _execute_with_wrappers
+    return executor(sql, params, many, context)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\backends\utils.py", line 89, in _execute
+    return self.cursor.execute(sql, params)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\utils.py", line 91, in __exit__
+    raise dj_exc_value.with_traceback(traceback) from exc_value
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\backends\utils.py", line 89, in _execute
+    return self.cursor.execute(sql, params)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\db\backends\sqlite3\base.py", line 357, in execute
+    return Database.Cursor.execute(self, query, params)
+django.db.utils.OperationalError: database is locked
+[2025-05-16 21:13:52,207][django.server.log_message():187] [ERROR] "GET /wms/inboundBills/?bound_status=0 HTTP/1.1" 500 184620
+[2025-05-16 21:14:06,227][django.request.log_response():241] [ERROR] Internal Server Error: /container/container_wcs/
+[2025-05-16 21:14:06,228][django.server.log_message():187] [ERROR] "GET /container/container_wcs/ HTTP/1.1" 500 60
+[2025-05-16 21:15:34,993][django.request.log_response():241] [ERROR] Internal Server Error: /container/container_wcs/
+[2025-05-16 21:15:34,995][django.server.log_message():187] [ERROR] "GET /container/container_wcs/ HTTP/1.1" 500 60
+[2025-05-16 21:15:42,144][django.request.log_response():241] [ERROR] Internal Server Error: /container/container_wcs/
+[2025-05-16 21:15:42,145][django.server.log_message():187] [ERROR] "GET /container/container_wcs/ HTTP/1.1" 500 60
+[2025-05-16 21:15:54,709][django.request.log_response():241] [ERROR] Internal Server Error: /container/container_wcs/
+[2025-05-16 21:15:54,711][django.server.log_message():187] [ERROR] "GET /container/container_wcs/ HTTP/1.1" 500 60
+[2025-05-17 16:44:08,979][django.request.log_response():241] [ERROR] Internal Server Error: /bound/batch/container/
+Traceback (most recent call last):
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
+    response = get_response(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\base.py", line 220, in _get_response
+    response = response.render()
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\template\response.py", line 114, in render
+    self.content = self.rendered_content
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\response.py", line 70, in rendered_content
+    ret = renderer.render(self.data, accepted_media_type, context)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\renderers.py", line 99, in render
+    ret = json.dumps(
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\utils\json.py", line 25, in dumps
+    return json.dumps(*args, **kwargs)
+  File "D:\language\python38\lib\json\__init__.py", line 234, in dumps
+    return cls(
+  File "D:\language\python38\lib\json\encoder.py", line 199, in encode
+    chunks = self.iterencode(o, _one_shot=True)
+  File "D:\language\python38\lib\json\encoder.py", line 257, in iterencode
+    return _iterencode(o, 0)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\utils\encoders.py", line 67, in default
+    return super().default(obj)
+  File "D:\language\python38\lib\json\encoder.py", line 179, in default
+    raise TypeError(f'Object of type {o.__class__.__name__} '
+TypeError: Object of type ContainerListModel is not JSON serializable
+[2025-05-17 16:44:08,987][django.server.log_message():187] [ERROR] "POST /bound/batch/container/ HTTP/1.1" 500 120679
+[2025-05-17 17:03:55,582][django.request.log_response():241] [ERROR] Internal Server Error: /bound/batch/container/
+Traceback (most recent call last):
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
+    response = get_response(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\views\generic\base.py", line 103, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 506, in dispatch
+    response = handler(request, *args, **kwargs)
+  File "D:\Document\code\vue\greater_wms\bound\views.py", line 599, in post
+    'container_code': container_detail.container_container_code,
+AttributeError: 'ContainerDetailModel' object has no attribute 'container_container_code'
+[2025-05-17 17:03:55,584][django.server.log_message():187] [ERROR] "POST /bound/batch/container/ HTTP/1.1" 500 113603
+[2025-05-18 01:57:12,670][django.request.log_response():241] [ERROR] Internal Server Error: /container/wcs_task/
+Traceback (most recent call last):
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
+    response = get_response(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 497, in dispatch
+    self.initial(request, *args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 416, in initial
+    self.check_throttles(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 359, in check_throttles
+    if not throttle.allow_request(request, self):
+  File "D:\Document\code\vue\greater_wms\utils\throttle.py", line 20, in allow_request
+    openid = request.auth.openid
+AttributeError: 'NoneType' object has no attribute 'openid'
+[2025-05-18 01:57:12,673][django.server.log_message():187] [ERROR] "GET /container/wcs_task/ HTTP/1.1" 500 119779
+[2025-05-18 01:57:36,772][django.request.log_response():241] [ERROR] Internal Server Error: /container/wcs_task/
+Traceback (most recent call last):
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
+    response = get_response(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
+    response = wrapped_callback(request, *callback_args, **callback_kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\django\views\decorators\csrf.py", line 54, in wrapped_view
+    return view_func(*args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\viewsets.py", line 125, in view
+    return self.dispatch(request, *args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 509, in dispatch
+    response = self.handle_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 469, in handle_exception
+    self.raise_uncaught_exception(exc)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 480, in raise_uncaught_exception
+    raise exc
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 497, in dispatch
+    self.initial(request, *args, **kwargs)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 416, in initial
+    self.check_throttles(request)
+  File "d:\Document\code\vue\greater_wms\.venv\lib\site-packages\rest_framework\views.py", line 359, in check_throttles
+    if not throttle.allow_request(request, self):
+  File "D:\Document\code\vue\greater_wms\utils\throttle.py", line 20, in allow_request
+    openid = request.auth.openid
+AttributeError: 'NoneType' object has no attribute 'openid'
+[2025-05-18 01:57:36,774][django.server.log_message():187] [ERROR] "GET /container/wcs_task/ HTTP/1.1" 500 119764

File diff suppressed because it is too large
+ 1771 - 0
logs/server.log


+ 225 - 225
templates/src/layouts/MainLayout.vue

@@ -644,280 +644,280 @@
   </q-layout>
 </template>
 <script>
-import { get, getauth, post, baseurl } from "boot/axios_request";
-import { LocalStorage, SessionStorage, openURL } from "quasar";
-import Bus from "boot/bus.js";
-import LottieWebCimo from "components/lottie-web-cimo";
-import Screenfull from "components/Screenfull";
+import { get, getauth, post, baseurl } from 'boot/axios_request'
+import { LocalStorage, SessionStorage, openURL } from 'quasar'
+import Bus from 'boot/bus.js'
+import LottieWebCimo from 'components/lottie-web-cimo'
+import Screenfull from 'components/Screenfull'
 
 export default {
   components: {
     LottieWebCimo,
-    Screenfull,
+    Screenfull
   },
-  data() {
+  data () {
     return {
-      device: LocalStorage.getItem("device"),
-      device_name: LocalStorage.getItem("device_name"),
-      lang: "zh-hans",
-      container_height: this.$q.screen.height + "" + "px",
-      warehouse_name: "",
+      device: LocalStorage.getItem('device'),
+      device_name: LocalStorage.getItem('device_name'),
+      lang: 'zh-hans',
+      container_height: this.$q.screen.height + '' + 'px',
+      warehouse_name: '',
       warehouseOptions: [],
       warehouseOptions_openid: [],
-      langOptions: [{ value: "zh-hans", label: "中文简体" }],
-      title: this.$t("index.webtitle"),
+      langOptions: [{ value: 'zh-hans', label: '中文简体' }],
+      title: this.$t('index.webtitle'),
       admin: true,
       adminlogin: {
-        name: "",
-        password: "",
+        name: '',
+        password: ''
       },
-      openid: "",
-      appid: "",
+      openid: '',
+      appid: '',
       switch_state: false,
       switch_warehouse: false,
       isPwd: true,
       isPwd2: true,
-      authin: "0",
+      authin: '0',
       authid: false,
       left: false,
       drawerleft: false,
-      tab: "",
+      tab: '',
       login: false,
-      link: "",
-      login_name: "",
+      link: '',
+      login_name: '',
       login_id: 0,
-      check_code: "",
+      check_code: '',
       register: false,
       registerform: {
-        name: "",
-        password1: "",
-        password2: "",
+        name: '',
+        password1: '',
+        password2: ''
       },
-      needLogin: "",
-      activeTab: "",
+      needLogin: '',
+      activeTab: '',
       ERPTasks: 0,
       ERPOutTasks: 0,
       pendingTasks: 0,
       pollInterval: null,
-      timer: null,
-    };
+      timer: null
+    }
   },
   methods: {
-    linkChange(e) {
-      var _this = this;
-      localStorage.removeItem("menulink");
-      localStorage.setItem("menulink", e);
-      _this.link = e;
-      console.log(_this.link);
+    linkChange (e) {
+      var _this = this
+      localStorage.removeItem('menulink')
+      localStorage.setItem('menulink', e)
+      _this.link = e
+      console.log(_this.link)
     },
-    drawerClick(e) {
-      var _this = this;
+    drawerClick (e) {
+      var _this = this
       if (_this.miniState) {
-        _this.miniState = false;
-        e.stopPropagation();
+        _this.miniState = false
+        e.stopPropagation()
       }
     },
-    brownlink(e) {
-      openURL(e);
+    brownlink (e) {
+      openURL(e)
     },
-    apiLink() {
-      openURL(baseurl + "/api/docs/");
+    apiLink () {
+      openURL(baseurl + '/api/docs/')
     },
 
-    adminLogin() {
-      var _this = this;
+    adminLogin () {
+      var _this = this
       if (!_this.adminlogin.name) {
         _this.$q.notify({
-          message: "请输入用户名",
-          color: "negative",
-          icon: "close",
-        });
+          message: '请输入用户名',
+          color: 'negative',
+          icon: 'close'
+        })
       } else {
         if (!_this.adminlogin.password) {
           _this.$q.notify({
-            message: "请输入密码",
-            icon: "close",
-            color: "negative",
-          });
+            message: '请输入密码',
+            icon: 'close',
+            color: 'negative'
+          })
         } else {
-          SessionStorage.set("axios_check", "false");
-          post("login/", _this.adminlogin)
+          SessionStorage.set('axios_check', 'false')
+          post('login/', _this.adminlogin)
             .then((res) => {
-              if (res.code === "200") {
-                _this.authin = "1";
-                _this.login = false;
-                _this.admin = false;
-                _this.openid = res.data.openid;
-                _this.appid = res.data.appid;
+              if (res.code === '200') {
+                _this.authin = '1'
+                _this.login = false
+                _this.admin = false
+                _this.openid = res.data.openid
+                _this.appid = res.data.appid
 
-                _this.login_name = res.data.name;
-                _this.login_id = res.data.user_id;
-                LocalStorage.set("auth", "1");
-                LocalStorage.set("openid", res.data.openid);
-                LocalStorage.set("appid", res.data.appid);
-                LocalStorage.set("login_name", _this.login_name);
-                LocalStorage.set("login_id", _this.login_id);
-                LocalStorage.set("login_mode", res.data.staff_type);
+                _this.login_name = res.data.name
+                _this.login_id = res.data.user_id
+                LocalStorage.set('auth', '1')
+                LocalStorage.set('openid', res.data.openid)
+                LocalStorage.set('appid', res.data.appid)
+                LocalStorage.set('login_name', _this.login_name)
+                LocalStorage.set('login_id', _this.login_id)
+                LocalStorage.set('login_mode', res.data.staff_type)
                 _this.$q.notify({
-                  message: "Success Login",
-                  icon: "check",
-                  color: "green",
-                });
-                localStorage.removeItem("menulink");
-                _this.link = "";
-                _this.$router.push({ name: "web_index" });
+                  message: 'Success Login',
+                  icon: 'check',
+                  color: 'green'
+                })
+                localStorage.removeItem('menulink')
+                _this.link = ''
+                _this.$router.push({ name: 'web_index' })
                 window.setTimeout(() => {
-                  location.reload();
-                }, 1);
+                  location.reload()
+                }, 1)
               } else {
                 _this.$q.notify({
                   message: res.msg,
-                  icon: "close",
-                  color: "negative",
-                });
+                  icon: 'close',
+                  color: 'negative'
+                })
               }
             })
             .catch((err) => {
               _this.$q.notify({
                 message: err.detail,
-                icon: "close",
-                color: "negative",
-              });
-            });
+                icon: 'close',
+                color: 'negative'
+              })
+            })
         }
       }
     },
-    Logout() {
-      var _this = this;
-      _this.authin = "0";
-      _this.login_name = "";
-      LocalStorage.remove("auth");
-      SessionStorage.remove("axios_check");
-      LocalStorage.set("login_name", "");
-      LocalStorage.set("login_id", "");
-      LocalStorage.set("appid", "");
+    Logout () {
+      var _this = this
+      _this.authin = '0'
+      _this.login_name = ''
+      LocalStorage.remove('auth')
+      SessionStorage.remove('axios_check')
+      LocalStorage.set('login_name', '')
+      LocalStorage.set('login_id', '')
+      LocalStorage.set('appid', '')
 
       _this.$q.notify({
-        message: "Success Logout",
-        icon: "check",
-        color: "negative",
-      });
+        message: 'Success Logout',
+        icon: 'check',
+        color: 'negative'
+      })
       // _this.staffType();
-      localStorage.removeItem("menulink");
-      _this.link = "";
-      _this.$router.push({ name: "web_index" });
+      localStorage.removeItem('menulink')
+      _this.link = ''
+      _this.$router.push({ name: 'web_index' })
       window.setTimeout(() => {
-        location.reload();
-      }, 1);
+        location.reload()
+      }, 1)
     },
-    Register() {
-      var _this = this;
-      SessionStorage.set("axios_check", "false");
-      post("register/", _this.registerform)
+    Register () {
+      var _this = this
+      SessionStorage.set('axios_check', 'false')
+      post('register/', _this.registerform)
         .then((res) => {
-          if (res.code === "200") {
-            _this.register = false;
-            _this.openid = res.data.openid;
-            _this.login_name = _this.registerform.name;
-            _this.login_id = res.data.user_id;
-            _this.authin = "1";
+          if (res.code === '200') {
+            _this.register = false
+            _this.openid = res.data.openid
+            _this.login_name = _this.registerform.name
+            _this.login_id = res.data.user_id
+            _this.authin = '1'
 
-            LocalStorage.set("auth", "1");
-            LocalStorage.set("appid", res.data.appid);
-            LocalStorage.set("login_name", res.data.name);
-            LocalStorage.set("login_id", res.data.user_id);
-            LocalStorage.set("openid", res.data.openid);
-            LocalStorage.set("login_mode", "Admin");
+            LocalStorage.set('auth', '1')
+            LocalStorage.set('appid', res.data.appid)
+            LocalStorage.set('login_name', res.data.name)
+            LocalStorage.set('login_id', res.data.user_id)
+            LocalStorage.set('openid', res.data.openid)
+            LocalStorage.set('login_mode', 'Admin')
 
             _this.registerform = {
-              name: "",
-              password1: "",
-              password2: "",
-            };
+              name: '',
+              password1: '',
+              password2: ''
+            }
             _this.$q.notify({
               message: res.msg,
-              icon: "check",
-              color: "green",
-            });
-            _this.staffType();
-            localStorage.removeItem("menulink");
-            _this.link = "";
-            _this.$router.push({ name: "web_index" });
+              icon: 'check',
+              color: 'green'
+            })
+            _this.staffType()
+            localStorage.removeItem('menulink')
+            _this.link = ''
+            _this.$router.push({ name: 'web_index' })
             window.setTimeout(() => {
-              location.reload();
-            }, 1);
+              location.reload()
+            }, 1)
           } else {
             _this.$q.notify({
               message: res.msg,
-              icon: "close",
-              color: "negative",
-            });
+              icon: 'close',
+              color: 'negative'
+            })
           }
         })
         .catch((err) => {
           _this.$q.notify({
             message: err.detail,
-            icon: "close",
-            color: "negative",
-          });
-        });
+            icon: 'close',
+            color: 'negative'
+          })
+        })
     },
-    staffType() {
-      var _this = this;
-      getauth("staff/?staff_name=" + _this.login_name).then((res) => {
-        LocalStorage.set("staff_type", res.results[0].staff_type);
-      });
+    staffType () {
+      var _this = this
+      getauth('staff/?staff_name=' + _this.login_name).then((res) => {
+        LocalStorage.set('staff_type', res.results[0].staff_type)
+      })
     },
-    warehouseOptionsGet() {
-      var _this = this;
-      get("warehouse/multiple/?max_page=30")
+    warehouseOptionsGet () {
+      var _this = this
+      get('warehouse/multiple/?max_page=30')
         .then((res) => {
           // if (res.count === 1) {
           //   _this.openid = res.results[0].openid
           //   _this.warehouse_name = res.results[0].warehouse_name
           //   LocalStorage.set('openid', _this.openid)
           // } else {
-          _this.warehouseOptions = res.results;
-          if (LocalStorage.has("openid")) {
+          _this.warehouseOptions = res.results
+          if (LocalStorage.has('openid')) {
             _this.warehouseOptions.forEach((item, index) => {
-              if (item.openid === LocalStorage.getItem("openid")) {
-                _this.warehouseOptions_openid.push(item);
+              if (item.openid === LocalStorage.getItem('openid')) {
+                _this.warehouseOptions_openid.push(item)
               }
-            });
+            })
           }
-          _this.warehouse_name = res.results[0].warehouse_name;
-          localStorage.setItem("warehouse_name", res.results[0].warehouse_name);
-          localStorage.setItem("warehouse_code", res.results[0].warehouse_code);
+          _this.warehouse_name = res.results[0].warehouse_name
+          localStorage.setItem('warehouse_name', res.results[0].warehouse_name)
+          localStorage.setItem('warehouse_code', res.results[0].warehouse_code)
           // }
         })
         .catch((err) => {
-          console.log(err);
+          console.log(err)
           _this.$q.notify({
             message: err.detail,
-            icon: "close",
-            color: "negative",
-          });
-        });
+            icon: 'close',
+            color: 'negative'
+          })
+        })
     },
-    warehouseChange(e) {
-      var _this = this;
-      _this.openid = _this.warehouseOptions[e].openid;
-      if (_this.openid === LocalStorage.getItem("openid")) {
-        _this.warehouse_name = _this.warehouseOptions[e].warehouse_name;
+    warehouseChange (e) {
+      var _this = this
+      _this.openid = _this.warehouseOptions[e].openid
+      if (_this.openid === LocalStorage.getItem('openid')) {
+        _this.warehouse_name = _this.warehouseOptions[e].warehouse_name
         localStorage.setItem(
-          "warehouse_name",
+          'warehouse_name',
           _this.warehouseOptions[e].warehouse_name
-        );
+        )
         localStorage.setItem(
-          "warehouse_code",
+          'warehouse_code',
           _this.warehouseOptions[e].warehouse_code
-        );
+        )
 
-        _this.switch_state = true;
+        _this.switch_state = true
       } else {
-        _this.switch_state = false;
+        _this.switch_state = false
       }
-      _this.switch_warehouse = true;
+      _this.switch_warehouse = true
 
       // LocalStorage.set('openid', _this.openid)
       // LocalStorage.set('staff_type', 'Admin')
@@ -935,79 +935,79 @@ export default {
     //     location.reload()
     //   }, 1)
     // },
-    isLoggedIn() {
-      if (this.$q.localStorage.getItem("openid")) {
-        this.login = true;
+    isLoggedIn () {
+      if (this.$q.localStorage.getItem('openid')) {
+        this.login = true
       } else {
-        this.register = true;
+        this.register = true
       }
     },
-    handleTimer() {
-      getauth("/wms/inboundBills/?bound_status=0").then((res) => {
-        this.ERPTasks = res.count;
-      });
-      getauth("/wms/outboundBills/?bound_status=0").then((res) => {
-        this.ERPOutTasks = res.count;
-      });
-    },
+    handleTimer () {
+      getauth('/wms/inboundBills/?bound_status=0').then((res) => {
+        this.ERPTasks = res.count
+      })
+      getauth('/wms/outboundBills/?bound_status=0').then((res) => {
+        this.ERPOutTasks = res.count
+      })
+    }
   },
-  created() {
-    var _this = this;
-    _this.$i18n.locale = "zh-hans";
+  created () {
+    var _this = this
+    _this.$i18n.locale = 'zh-hans'
 
-    if (LocalStorage.has("openid")) {
-      _this.openid = LocalStorage.getItem("openid");
-      _this.activeTab = "admin";
+    if (LocalStorage.has('openid')) {
+      _this.openid = LocalStorage.getItem('openid')
+      _this.activeTab = 'admin'
     } else {
-      _this.openid = "";
-      LocalStorage.set("openid", "");
+      _this.openid = ''
+      LocalStorage.set('openid', '')
     }
-    if (LocalStorage.has("login_name")) {
-      _this.login_name = LocalStorage.getItem("login_name");
+    if (LocalStorage.has('login_name')) {
+      _this.login_name = LocalStorage.getItem('login_name')
     } else {
-      _this.login_name = "";
-      LocalStorage.set("login_name", "");
+      _this.login_name = ''
+      LocalStorage.set('login_name', '')
     }
-    if (LocalStorage.has("auth")) {
-      _this.authin = "1";
-      _this.staffType();
+    if (LocalStorage.has('auth')) {
+      _this.authin = '1'
+      _this.staffType()
     } else {
-      LocalStorage.set("staff_type", "Admin");
-      _this.authin = "0";
-      _this.isLoggedIn();
+      LocalStorage.set('staff_type', 'Admin')
+      _this.authin = '0'
+      _this.isLoggedIn()
     }
   },
-  mounted() {
-    var _this = this;
-    _this.warehouseOptionsGet();
-    _this.handleTimer();
-    _this.link = localStorage.getItem("menulink");
-    Bus.$on("needLogin", (val) => {
-      _this.isLoggedIn();
-    });
+  mounted () {
+    var _this = this
+    _this.warehouseOptionsGet()
+    // _this.handleTimer()
+    _this.link = localStorage.getItem('menulink')
+    Bus.$on('needLogin', (val) => {
+      _this.isLoggedIn()
+    })
 
-    _this.timer = setInterval(() => {
-      _this.handleTimer();
-    }, 100000);
+    // _this.timer = setInterval(() => {
+    //   _this.handleTimer();
+    // }, 100000);
   },
   // 修改时间 :10000
-  updated() {},
-  beforeDestroy() {
-    Bus.$off("needLogin");
+  updated () {},
+  beforeDestroy () {
+    Bus.$off('needLogin')
   },
-  destroyed() {},
+  destroyed () {},
   watch: {
-    login(val) {
+    login (val) {
       if (val) {
-        if (this.activeTab === "admin") {
-          this.admin = true;
+        if (this.activeTab === 'admin') {
+          this.admin = true
         } else {
-          this.admin = false;
+          this.admin = false
         }
       }
-    },
-  },
-};
+    }
+  }
+}
 </script>
 <style>
 .tabs .q-tab__indicator {

+ 0 - 2
templates/src/pages/inbound/asn.vue

@@ -776,7 +776,6 @@
                   hide-pagination
                   class="my-sticky-table"
                 >
-                  <!-- 自定义单元格内容 -->
                   <template v-slot:body-cell-actions="props">
                     <q-td :props="props">
                       <q-btn
@@ -789,7 +788,6 @@
                       </q-btn>
                     </q-td>
                   </template>
-
                 </q-table>
               </template>
             </q-card-section>

+ 326 - 141
templates/src/pages/inbound/sortstock.vue

@@ -1,148 +1,203 @@
 <template>
-  <div >
-
+  <div>
     <transition appear enter-active-class="animated fadeIn">
-      <q-table class="my-sticky-header-column-table shadow-24" :data="table_list" row-key="id" :separator="separator"
-        :loading="loading" :columns="columns" hide-bottom :pagination.sync="pagination" no-data-label="No data"
-        no-results-label="No data you want" :table-style="{ height: height }" flat bordered>
+      <q-table
+        class="my-sticky-header-column-table shadow-24"
+        :data="table_list"
+        row-key="id"
+        :separator="separator"
+        :loading="loading"
+        :columns="columns"
+        hide-bottom
+        :pagination.sync="pagination"
+        no-data-label="No data"
+        no-results-label="No data you want"
+        :table-style="{ height: height }"
+        flat
+        bordered
+      >
         <template v-slot:top>
           <q-btn-group push>
-
             <q-btn :label="$t('refresh')" icon="refresh" @click="reFresh()">
-              <q-tooltip content-class="bg-amber text-black shadow-4" :offset="[10, 10]"
-                content-style="font-size: 12px">{{ $t('refreshtip') }}</q-tooltip>
+              <q-tooltip
+                content-class="bg-amber text-black shadow-4"
+                :offset="[10, 10]"
+                content-style="font-size: 12px"
+                >{{ $t("refreshtip") }}</q-tooltip
+              >
             </q-btn>
-
+            <!-- <q-btn :label="'日志'" icon="logout" @click="getlog()"> </q-btn> -->
           </q-btn-group>
 
           <q-space />
 
           <div class="flex items-center">
             <div class="q-mr-md">{{ $t("download_center.createTime") }}</div>
-            <q-input readonly outlined dense v-model="createDate2" :placeholder="interval">
+            <q-input
+              readonly
+              outlined
+              dense
+              v-model="createDate2"
+              :placeholder="interval"
+            >
               <template v-slot:append>
                 <q-icon name="event" class="cursor-pointer">
-                  <q-popup-proxy ref="qDateProxy" transition-show="scale" transition-hide="scale">
+                  <q-popup-proxy
+                    ref="qDateProxy"
+                    transition-show="scale"
+                    transition-hide="scale"
+                  >
                     <q-date v-model="createDate1" range>
                       <div class="row items-center justify-end q-gutter-sm">
-                        <q-btn :label="$t('index.cancel')" color="primary" flat v-close-popup />
-                        <q-btn :label="$t('index.clear')" color="primary" @click="createDate2 = ''; createDate1 = '';"
-                          v-close-popup />
-
+                        <q-btn
+                          :label="$t('index.cancel')"
+                          color="primary"
+                          flat
+                          v-close-popup
+                        />
+                        <q-btn
+                          :label="$t('index.clear')"
+                          color="primary"
+                          @click="
+                            createDate2 = '';
+                            createDate1 = '';
+                          "
+                          v-close-popup
+                        />
                       </div>
                     </q-date>
                   </q-popup-proxy>
                 </q-icon>
               </template>
             </q-input>
-            <q-btn-group push class="q-ml-md">
-            </q-btn-group>
-            <q-input outlined rounded dense debounce="300" color="primary" v-model="filter" :placeholder="$t('search')"
-              @input="getSearchList()" @keyup.enter="getSearchList()">
+            <q-btn-group push class="q-ml-md"> </q-btn-group>
+            <q-input
+              outlined
+              rounded
+              dense
+              debounce="300"
+              color="primary"
+              v-model="filter"
+              :placeholder="$t('search')"
+              @input="getSearchList()"
+              @keyup.enter="getSearchList()"
+            >
               <template v-slot:append>
                 <q-icon name="search" @click="getSearchList()" />
               </template>
             </q-input>
           </div>
         </template>
+
         <template v-slot:body="props">
           <q-tr :props="props">
-            <template >
-              <q-td key="document_date" :props="props">{{ props.row.batch_detail.bound_list.bound_date }}</q-td>
-            </template>            
-            <template >
-              <q-td key="document_number" :props="props">{{ props.row.batch_detail.bound_list.bound_code }}</q-td>
-            </template>
-            <template >
-              <q-td key="document_type" :props="props">{{ props.row.batch_detail.bound_list.bound_code_type }}</q-td>
-            </template>            
-            <template >
-              <q-td key="business_type" :props="props">{{ props.row.batch_detail.bound_list.bound_bs_type }}</q-td>
-            </template>
-            <template >
-              <q-td key="iout_type" :props="props">{{ props.row.batch_detail.bound_list.bound_type  }}</q-td>
-            </template>            
-            <template >
-              <q-td key="department" :props="props">{{ props.row.batch_detail.bound_list.bound_department }}</q-td>
-            </template>            
-            <template >
-              <q-td key="warehouse_code" :props="props">{{ props.row.batch_detail.bound_batch.warehouse_code }}</q-td>
-            </template>            
-            <template >
-              <q-td key="warehouse_name" :props="props">{{ props.row.batch_detail.bound_batch.warehouse_name }}</q-td>
-            </template>            
-            <template >
-              <q-td key="goods_code" :props="props">{{ props.row.batch_detail.bound_batch.goods_code }}</q-td>
-            </template>            
-            <template >
-              <q-td key="goods_desc" :props="props">{{ props.row.batch_detail.bound_batch.goods_desc }}</q-td>
-            </template>            
-            
-            <template >
-              <q-td key="goods_std" :props="props">{{ props.row.batch_detail.bound_batch.goods_std }}</q-td>
-            </template>            
-            <template >
-              <q-td key="goods_batch" :props="props">{{ props.row.container_detail.batch.bound_number }}</q-td>
-            </template>            
-            <template >
-              <q-td key="in_batch" :props="props">{{ props.row.container_detail.batch.goods_code}}</q-td>
-            </template>            
-            <template >
-              <q-td key="out_batch" :props="props">{{ props.row.batch_detail.bound_batch.out_batch }}</q-td>
-            </template>            
-            <template >
-              <q-td key="goods_unit" :props="props">{{ props.row.batch_detail.bound_batch.goods_unit }}</q-td>
-            </template>            
-            <template >
-              <q-td key="goods_in" :props="props">{{ props.row.container_detail.goods_qty }}</q-td>
-            </template>            
-            <template >
-              <q-td key="container_number" :props="props">{{ props.row.container_detail.container.container_code }}</q-td>
-            </template>            
-            <template >
-              <q-td key="goods_notes" :props="props">{{ props.row.task_wcs.message}}</q-td>
-            </template>            
-            <template >
-              <q-td key="creator" :props="props">{{ props.row.container_detail.creater}}</q-td>
-            </template>            
- 
-
-
-
-
-
+            <q-td auto-width>
+              <q-btn
+                size="sm"
+                round
+                :icon="props.row.expand ? 'remove' : 'ballot'"
+                @click="handle_row_expand(props.row)"
+              />
+            </q-td>
+            <q-td
+              v-for="col in columns.filter((c) => c.name !== 'expand')"
+              :key="col.name"
+              :props="props"
+            >
+              {{ col.field ? props.row[col.field] : props.row[col.name] }}
+            </q-td>
+          </q-tr>
 
+          <!-- 第二级:时间轴 -->
+          <q-tr v-show="props.row.expand" :props="props" class="expanded-row">
+            <q-td colspan="100%">
+              <div class="q-pa-md timeline-wrapper">
+                <q-timeline
+                  color="#e0e0e0"
+                  v-if="props.row.containers?.length"
+                >
+                  <q-timeline-entry
+                    v-for="(container, index) in props.row.containers"
+                    :key="index"
+                    class="custom-node"
+                  >
+                    <template v-slot:title>
+                      <span>
+                        <div>托盘 {{ container.container_code }}</div>
+                        <div class="row">
+                          <div class="col">
+                            <div class="custom-title">
+                              {{ container.goods_desc }}
+                            </div>
+                          </div>
+                          <div class="col">
+                            <div class="custom-title">
+                              容纳数量:{{ container.goods_qty }}
+                            </div>
+                          </div>
+                          <div class="col">
+                            <div class="custom-title">
+                              当前位置:{{ container.current_location }}
+                            </div>
+                          </div>
+                          <div class="col">
+                            <div class="custom-title">
+                              托盘属性:{{ class_to_name(container.class) }}
+                            </div>
+                          </div>
+                        </div>
+                      </span>
+                    </template>
+                  </q-timeline-entry>
+                </q-timeline>
+                <div v-else-if="props.row.loading" class="text-center q-pa-md">
+                  <q-spinner color="primary" size="2em" />
+                  <div class="q-mt-sm">正在加载托盘信息...</div>
+                </div>
+              </div>
+            </q-td>
           </q-tr>
         </template>
       </q-table>
     </transition>
     <template>
       <div v-show="max !== 0" class="q-pa-lg flex flex-center">
-        <div>{{ total }} </div>
-        <q-pagination v-model="current" color="black" :max="max" :max-pages="6" boundary-links
-          @click="getSearchList(current); paginationIpt = current" />
+        <div>{{ total }}</div>
+        <q-pagination
+          v-model="current"
+          color="black"
+          :max="max"
+          :max-pages="6"
+          boundary-links
+          @click="
+            getSearchList(current);
+            paginationIpt = current;
+          "
+        />
         <div>
-          <input v-model="paginationIpt" @blur="changePageEnter" @keyup.enter="changePageEnter"
-            style="width: 60px; text-align: center" />
+          <input
+            v-model="paginationIpt"
+            @blur="changePageEnter"
+            @keyup.enter="changePageEnter"
+            style="width: 60px; text-align: center"
+          />
         </div>
       </div>
       <div v-show="max === 0" class="q-pa-lg flex flex-center">
         <q-btn flat push color="dark" :label="$t('no_data')"></q-btn>
       </div>
     </template>
-
   </div>
 </template>
 <router-view />
 
 <script>
 import { getauth, postauth, putauth, deleteauth } from 'boot/axios_request'
-import { date, exportFile, LocalStorage } from 'quasar'
-
+import { date, LocalStorage } from 'quasar'
 
 export default {
   name: 'PageTask',
-  data() {
+  data () {
     return {
       createDate1: '',
       createDate2: '',
@@ -155,8 +210,7 @@ export default {
       login_name: '',
       authin: '0',
       searchUrl: '',
-      pathname: 'container/task/',
-
+      pathname: 'bound/batch/',
       pathname_previous: '',
       pathname_next: '',
       separator: 'cell',
@@ -166,23 +220,77 @@ export default {
 
       table_list: [],
       columns: [
-          { name: 'document_date', required: true, label: '入库时间', align: 'center', field: 'document_date' },
-          { name: 'document_number', label: '单据编号', field: 'document_number', align: 'center' },
-          { name: 'department', label: '部门', field: 'department', align: 'center' },
-
-          { name: 'goods_code', label: '存货编码', field: 'goods_code', align: 'center' },
-          { name: 'goods_desc', label: '存货', field: 'goods_desc', align: 'center' },
-          { name: 'goods_std', label: '规格型号', field: 'goods_std', align: 'center' },
-          { name: 'goods_batch', label: '入库批号', field: 'goods_batch', align: 'center' },
-
-
-          { name: 'goods_in', label: '入库数目', field: 'goods_in', align: 'center' },
-          { name: 'container_number', label: '托盘编码', field: 'container_number', align: 'center' },
-
-          // { name: 'goods_notes', label: '备注', field: 'goods_notes', align: 'center' },
-          { name: 'creator', label: '创建人', field: 'creator', align: 'center' },
-
-        ],
+        {
+          name: 'expand',
+          label: '',
+          align: 'left',
+          headerStyle: 'width: 50px'
+        },
+        {
+          name: 'bound_number',
+          label: '管理批次',
+          align: 'center',
+          field: 'bound_number'
+        },
+        {
+          name: 'goods_code',
+          label: '存货编码',
+          field: 'goods_code',
+          align: 'center'
+        },
+        {
+          name: 'goods_desc',
+          label: '存货名称',
+          field: 'goods_desc',
+          align: 'center'
+        },
+        {
+          name: 'bound_batch_order',
+          label: '批号',
+          field: 'bound_batch_order',
+          align: 'center'
+        },
+        {
+          name: 'goods_qty',
+          label: '计划数目',
+          field: 'goods_qty',
+          align: 'center'
+        },
+        {
+          name: 'goods_in_qty',
+          label: '已分拣数目',
+          field: 'goods_in_qty',
+          align: 'center'
+        },
+        {
+          name: 'goods_std',
+          label: '规格型号',
+          field: 'goods_std',
+          align: 'center',
+          headerStyle: 'width: 20px'
+        },
+        {
+          name: 'goods_unit',
+          label: '单位',
+          field: 'goods_unit',
+          align: 'center',
+          headerStyle: 'width: 20px'
+        },
+        {
+          name: 'goods_package',
+          label: '包装',
+          field: 'goods_package',
+          align: 'center',
+          headerStyle: 'width: 20px'
+        },
+        {
+          name: 'create_time',
+          label: '创建时间',
+          field: 'create_time',
+          align: 'center',
+          headerStyle: 'width: 40px'
+        }
+      ],
       filter: '',
       pagination: {
         page: 1,
@@ -192,18 +300,54 @@ export default {
       max: 0,
       total: 0,
       paginationIpt: 1,
-      current: 1,
-
+      containers: {},
+      timer: null
     }
-
   },
   computed: {
-    interval() {
-      return this.$t('download_center.start') + ' - ' + this.$t('download_center.end')
+    interval () {
+      return (
+        this.$t('download_center.start') +
+        ' - ' +
+        this.$t('download_center.end')
+      )
     }
   },
   methods: {
-    getList(params = {}) {
+    class_to_name (class_id) {
+      const class_map = {
+        1: '整盘',
+        2: '托盘组',
+        3: '零盘'
+      }
+      return class_map[class_id]
+    },
+    handle_row_expand (row) {
+      const _this = this
+      row.expand = !row.expand
+      if (row.expand) {
+        // 添加行级 loading 状态
+        _this.$set(row, 'loading', true)
+        postauth('bound/batch/container/', { batch_id: row.id })
+          .then((res) => {
+            // 将数据存储到当前行的 containers 属性
+
+            _this.$set(row, 'containers', res.data)
+            console.log('当前的', row.containers)
+          })
+          .catch((err) => {
+            _this.$q.notify({ message: err.detail, color: 'negative' })
+          })
+          .finally(() => {
+            row.loading = false // 关闭加载状态
+          })
+      }
+    },
+    getlog () {
+      // console.log(this.table_list)
+      console.log('当前loading状态:', this.loading)
+    },
+    getList (params = {}) {
       var _this = this
       _this.loading = true
       // 合并基础参数
@@ -226,14 +370,27 @@ export default {
       })
 
       getauth(`${_this.pathname}?${queryParams}`)
-        .then(res => {
-          _this.table_list = res.results
+        .then((res) => {
+          _this.table_list = res.results.map((item) => ({
+            ...item,
+            expand: false,
+            containers: [
+              // {
+              //   id: 0,
+              //   container_code: 0,
+              //   current_location: '0',
+              //   goods_qty: 0,
+              //   class: 0
+              // }
+            ],
+            loading: false
+          }))
           _this.total = res.count
           _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage) || 0
           _this.pathname_previous = res.previous
           _this.pathname_next = res.next
         })
-        .catch(err => {
+        .catch((err) => {
           _this.$q.notify({
             message: err.detail,
             icon: 'close',
@@ -244,7 +401,7 @@ export default {
           _this.loading = false
         })
     },
-    changePageEnter() {
+    changePageEnter () {
       if (Number(this.paginationIpt) < 1) {
         this.current = 1
         this.paginationIpt = 1
@@ -258,7 +415,7 @@ export default {
     },
 
     // 带搜索条件加载
-    getSearchList(page = 1) {
+    getSearchList (page = 1) {
       this.current = page
       this.paginationIpt = page
       this.getList({
@@ -267,16 +424,16 @@ export default {
       })
     },
 
-    getListPrevious() {
+    getListPrevious () {
       var _this = this
       if (LocalStorage.has('auth')) {
         getauth(_this.pathname_previous, {})
-          .then(res => {
+          .then((res) => {
             _this.table_list = res.results
             _this.pathname_previous = res.previous
             _this.pathname_next = res.next
           })
-          .catch(err => {
+          .catch((err) => {
             _this.$q.notify({
               message: err.detail,
               icon: 'close',
@@ -286,17 +443,17 @@ export default {
       } else {
       }
     },
-    getListNext() {
+    getListNext () {
       var _this = this
       if (LocalStorage.has('auth')) {
         getauth(_this.pathname_next, {})
-          .then(res => {
+          .then((res) => {
             _this.table_list = res.results
 
             _this.pathname_previous = res.previous
             _this.pathname_next = res.next
           })
-          .catch(err => {
+          .catch((err) => {
             _this.$q.notify({
               message: err.detail,
               icon: 'close',
@@ -305,19 +462,17 @@ export default {
           })
       }
     },
-    reFresh() {
+    reFresh () {
       var _this = this
       _this.getSearchList()
     },
 
-
-    updateProxy() {
+    updateProxy () {
       var _this = this
       _this.proxyDate = _this.date
     }
-
   },
-  created() {
+  created () {
     var _this = this
     if (LocalStorage.has('openid')) {
       _this.openid = LocalStorage.getItem('openid')
@@ -342,19 +497,22 @@ export default {
       _this.authin = '0'
     }
   },
-  mounted() {
+  mounted () {
     var _this = this
     if (_this.$q.platform.is.electron) {
       _this.height = String(_this.$q.screen.height - 290) + 'px'
     } else {
       _this.height = _this.$q.screen.height - 290 + '' + 'px'
     }
+    // _this.timer = setInterval(() => {
+    //   _this.getlog()
+    // }, 1000)
   },
-  updated() { },
-  destroyed() { },
+  updated () {},
+  destroyed () {},
   // 在 watch 或方法中添加调试代码
   watch: {
-    createDate1(val) {
+    createDate1 (val) {
       if (val) {
         if (val.to) {
           this.createDate2 = `${val.from} - ${val.to}`
@@ -364,15 +522,24 @@ export default {
         } else {
           this.createDate2 = `${val}`
           this.dateArray = val.split('/')
-          this.searchUrl = this.pathname + '?' + 'document_date__year=' + this.dateArray[0] + '&' + 'document_date__month=' + this.dateArray[1] + '&' + 'document_date__day=' + this.dateArray[2]
+          this.searchUrl =
+            this.pathname +
+            '?' +
+            'document_date__year=' +
+            this.dateArray[0] +
+            '&' +
+            'document_date__month=' +
+            this.dateArray[1] +
+            '&' +
+            'document_date__day=' +
+            this.dateArray[2]
           // this.downloadhUrl = this.pathname + 'filelist/?' + 'document_date__year=' + this.dateArray[0] + '&' + 'document_date__month=' + this.dateArray[1] + '&' + 'document_date__day=' + this.dateArray[2]
         }
         this.date_range = this.date_range.replace(/\//g, '-')
 
         this.getSearchList()
         this.$refs.qDateProxy.hide()
-      }
-      else {
+      } else {
         this.createDate2 = ''
         this.date_range = ''
         this.getSearchList()
@@ -391,4 +558,22 @@ export default {
 .q-date__range {
   background-color: rgba(25, 118, 210, 0.1);
 }
-</style>
+
+.custom-title {
+  font-size: 0.9rem; /* 推荐使用相对单位 */
+  font-weight: 500;
+}
+/* 添加以下样式 */
+.custom-timeline {
+  --q-timeline-color: #e0e0e0; /* 覆盖时间轴线颜色变量 */
+}
+
+.custom-node .q-timeline__dot {
+  background: #485573 !important; /* 节点填充色 */
+  border: 2px solid #5c6b8c !important; /* 节点边框色 */
+}
+
+.custom-node .q-timeline__content {
+  color: #485573; /* 文字颜色 */
+}
+</style>

File diff suppressed because it is too large
+ 163 - 1052
templates/src/pages/task/task.vue