4 Commits cc90ec8057 ... e67cf8795a

Autor SHA1 Mensaje Fecha
  flower_mr e67cf8795a 提交 hace 2 semanas
  flower_mr a20a741745 出库程序优化 hace 2 semanas
  flower_mr 62f076d54a 任务界面更换 hace 3 semanas
  flower_mr 9c67e41e31 入库规则增加 hace 3 semanas
Se han modificado 61 ficheros con 19085 adiciones y 4115 borrados
  1. 53 2
      bin/algorithms.py
  2. 0 0
      bin/generate_wcs_task.py
  3. 20 0
      bin/migrations/0003_alter_locationcontainerlink_container.py
  4. 20 0
      bin/migrations/0004_alter_locationcontainerlink_container.py
  5. 20 0
      bin/migrations/0005_alter_locationcontainerlink_container.py
  6. 25 0
      bin/migrations/0006_alter_locationcontainerlink_container_and_more.py
  7. 17 2
      bin/models.py
  8. 169 13
      bin/queries.py
  9. 237 13
      bin/services.py
  10. 174 5
      bin/updates.py
  11. 46 7
      bin/views.py
  12. 50 3
      bound/filter.py
  13. 18 0
      bound/migrations/0012_boundbatchmodel_goods_package.py
  14. 30 0
      bound/migrations/0013_materialstatistics.py
  15. 28 0
      bound/migrations/0014_alter_boundbatchmodel_goods_in_location_qty_and_more.py
  16. 18 0
      bound/migrations/0015_rename_goods_out_location_qty_boundbatchmodel_goods_actual_qty.py
  17. 42 0
      bound/migrations/0016_materialstatistics_total_demanded_quantity_and_more.py
  18. 18 0
      bound/migrations/0017_outbounddemandmodel_out_type.py
  19. 19 0
      bound/migrations/0018_outbatchmodel_bound_list.py
  20. 119 4
      bound/models.py
  21. 36 1
      bound/serializers.py
  22. 12 0
      bound/urls.py
  23. 392 5
      bound/views.py
  24. 25 25
      container/container_operate.py
  25. 29 2
      container/filter.py
  26. 30 0
      container/migrations/0016_containerwcsmodel_batch_number_and_more.py
  27. 18 0
      container/migrations/0017_alter_containerdetailmodel_goods_class.py
  28. 17 0
      container/migrations/0018_alter_containerwcsmodel_options.py
  29. 6 5
      container/models.py
  30. 9 0
      container/serializers.py
  31. 3 0
      container/urls.py
  32. 301 130
      container/views.py
  33. 192 192
      data_base/product.csv
  34. 3046 0
      logs/error.log
  35. 6705 0
      logs/server.log
  36. 1 0
      templates/package.json
  37. 116 0
      templates/src/components/CustomDateInput.vue
  38. 181 195
      templates/src/components/goodscard.vue
  39. 225 225
      templates/src/layouts/MainLayout.vue
  40. 2 2
      templates/src/pages/dashboard/dashboard.vue
  41. 788 244
      templates/src/pages/dashboard/flows_statements.vue
  42. 1360 111
      templates/src/pages/dashboard/inboundAndOutbound.vue
  43. 185 88
      templates/src/pages/inbound/asn.vue
  44. 5 4
      templates/src/pages/inbound/inbound.vue
  45. 383 182
      templates/src/pages/inbound/predeliverystock.vue
  46. 327 142
      templates/src/pages/inbound/sortstock.vue
  47. 616 276
      templates/src/pages/outbound/dn.vue
  48. 221 265
      templates/src/pages/outbound/freshorder.vue
  49. 81 270
      templates/src/pages/outbound/neworder.vue
  50. 2 2
      templates/src/pages/outbound/outbound.vue
  51. 1848 172
      templates/src/pages/outbound/pod.vue
  52. 163 1052
      templates/src/pages/task/task.vue
  53. 603 373
      templates/src/pages/warehouse/product.vue
  54. 12 0
      templates/yarn.lock
  55. 20 2
      warehouse/migrations/0001_initial.py
  56. 0 18
      warehouse/migrations/0002_productlistmodel_product_unit.py
  57. 0 18
      warehouse/migrations/0003_alter_productlistmodel_product_unit.py
  58. 0 18
      warehouse/migrations/0004_alter_productlistmodel_product_std.py
  59. 0 28
      warehouse/migrations/0005_baseset.py
  60. 0 18
      warehouse/migrations/0006_baseset_is_delete.py
  61. 2 1
      warehouse/models.py

+ 53 - 2
bin/algorithms.py

@@ -1,6 +1,8 @@
 import copy
 import json
 from collections import defaultdict
+from .models import LocationGroupModel, alloction_pre
+from .queries import LocationQueries
 
 class AllocationAlgorithm:
     CAPACITY_MAP = {'T1':1, 'T2':2, 'T4':4, 'S4':4, 'T5':5}
@@ -28,7 +30,8 @@ class AllocationAlgorithm:
                     new_state = copy.deepcopy(layer_state)
                     new_state[layer_idx][loc_type] -= 1
                     
-                    new_pressure = [p-1 if p>0 else 0 for p in pressure]
+                    # new_pressure = [p-1 if p>0 else 0 for p in pressure]
+                    new_pressure = [p if p>0 else 0 for p in pressure]
                     allocated = min(cap, remain)
                     new_pressure[layer_idx] += allocated
 
@@ -53,4 +56,52 @@ class AllocationAlgorithm:
             if len(parts) == 2:
                 layer, loc_type = parts
                 result[layer][loc_type] += 1
-        return json.dumps(result)
+        return json.dumps(result)
+    
+    @staticmethod
+    def allocation_plan_left_right(location_type_list,batch_number,start_location,container_code):
+        locations=[]
+        existing_solution = alloction_pre.objects.filter(batch_number=batch_number).first()
+
+        for layer, location_type_dict in location_type_list.items():
+            if not location_type_dict:
+                continue
+            # 获取库位类型列表
+            location_type = list(location_type_dict.keys())
+            demand_number = sum(location_type_dict.values())
+            print (f"[1]层{layer} 需求数量: {demand_number}, 库位: {location_type}")                    
+            location_groups = LocationGroupModel.objects.filter(
+                group_type__in=location_type,
+                layer=layer,
+                status='available'
+            )
+            if not location_groups:
+                print(f"层{layer} 无库位")
+            # 根据起始位置选择排序字段
+            if start_location == '203':
+                ordered_groups = location_groups.order_by('left_priority')
+            elif start_location == '103':
+                ordered_groups = location_groups.order_by('right_priority')
+            else:
+                ordered_groups = location_groups.none()
+
+            number = 0
+            for location_group in ordered_groups:
+                if number >= demand_number:
+                    break
+                locations.append(f"{layer}_{location_group.id}")
+                number += 1
+        
+        existing_solution.layer_solution_type = locations
+        existing_solution.save()
+        print(f"[2]分配方案: {locations}")
+        return locations if locations else None
+      
+    def generate_WCS_location(location_list_cnumber):
+        allocation_target_location = (
+                            location_list_cnumber.warehouse_code + '-' 
+                            + f"{int(location_list_cnumber.row):02d}" + '-'  
+                            + f"{int(location_list_cnumber.col):02d}" + '-' 
+                            + f"{int(location_list_cnumber.layer):02d}" 
+                        )
+        return allocation_target_location

bin/location_allocator/__init__.py → bin/generate_wcs_task.py


+ 20 - 0
bin/migrations/0003_alter_locationcontainerlink_container.py

@@ -0,0 +1,20 @@
+# Generated by Django 4.1.2 on 2025-05-20 04:34
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0018_alter_containerwcsmodel_options'),
+        ('bin', '0002_devicemodel_mode_devicemodel_warehouse_code'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='locationcontainerlink',
+            name='container',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='location_links', to='container.containerlistmodel'),
+        ),
+    ]

+ 20 - 0
bin/migrations/0004_alter_locationcontainerlink_container.py

@@ -0,0 +1,20 @@
+# Generated by Django 4.1.2 on 2025-05-20 05:21
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0018_alter_containerwcsmodel_options'),
+        ('bin', '0003_alter_locationcontainerlink_container'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='locationcontainerlink',
+            name='container',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='container.containerlistmodel'),
+        ),
+    ]

+ 20 - 0
bin/migrations/0005_alter_locationcontainerlink_container.py

@@ -0,0 +1,20 @@
+# Generated by Django 4.1.2 on 2025-05-20 05:28
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0018_alter_containerwcsmodel_options'),
+        ('bin', '0004_alter_locationcontainerlink_container'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='locationcontainerlink',
+            name='container',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='location_links', to='container.containerlistmodel'),
+        ),
+    ]

+ 25 - 0
bin/migrations/0006_alter_locationcontainerlink_container_and_more.py

@@ -0,0 +1,25 @@
+# Generated by Django 4.1.2 on 2025-05-20 05:33
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0018_alter_containerwcsmodel_options'),
+        ('bin', '0005_alter_locationcontainerlink_container'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='locationcontainerlink',
+            name='container',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='location_links', to='container.containerlistmodel', verbose_name='关联容器'),
+        ),
+        migrations.AlterField(
+            model_name='locationcontainerlink',
+            name='location',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='container_links', to='bin.locationmodel', verbose_name='库位'),
+        ),
+    ]

+ 17 - 2
bin/models.py

@@ -863,8 +863,23 @@ class LocationGroupModel(models.Model):
         
 # 库位-托盘关联表(记录实时存放关系)
 class LocationContainerLink(models.Model):
-    location = models.ForeignKey(LocationModel, on_delete=models.CASCADE, verbose_name='库位')
-    container = models.ForeignKey(ContainerListModel,on_delete=models.CASCADE)
+    location = models.ForeignKey(
+            LocationModel, 
+            on_delete=models.CASCADE,
+            related_name='container_links',  # 关键修复点
+            verbose_name='库位'
+        )
+    
+    container = models.ForeignKey(
+        ContainerListModel,
+        on_delete=models.CASCADE,
+        related_name='location_links',   # 容器到链接的反向关联
+        verbose_name='关联容器'
+    )
+    task_wcs = models.ForeignKey(
+        ContainerWCSModel,
+        on_delete=models.CASCADE,
+    )
     task_wcs = models.ForeignKey(ContainerWCSModel, on_delete=models.CASCADE, null=True, blank=True)
     task_detail = models.ForeignKey(TaskModel, on_delete=models.CASCADE, null=True, blank=True)
     put_time = models.DateTimeField(auto_now_add=True, verbose_name='上架时间')

+ 169 - 13
bin/queries.py

@@ -1,6 +1,15 @@
 from django.db import models
 from .models import *
+from django.db.models import Q
+import json
+from django.db import transaction
 from container.models import *
+from .models import LocationGroupModel
+import logging
+from collections import defaultdict
+
+logger = logging.getLogger(__name__)
+
 
 class LocationQueries:
     """"
@@ -18,9 +27,6 @@ class LocationQueries:
         get_batch_info(container_code): 获取托盘批次信息
             params: container_code: 托盘编号
             return: dict
-        get_batch_info(container_code): 获取托盘批次信息
-            params: container_code: 托盘编号
-            return: dict
         get_group_capacity(): 获取库位组空闲容量
             return: list
         get_current_pressure(): 获取仓库当前工作压力
@@ -44,18 +50,48 @@ class LocationQueries:
         container = ContainerListModel.objects.filter(
             container_code=container_code
         ).first()
-        if not container:
-            return None
+
 
         detail = ContainerDetailModel.objects.filter(
             container=container.id
         ).exclude(status=3).first()
-        
-        return {
-            'status': detail.batch.status if detail else None,
-            'number': detail.batch.bound_number if detail else None,
-            'container': container
-        }
+        if not detail:
+            return {
+                'status': None,
+                'number': None,
+                'container': container,
+                'class': 2 ,
+            }
+        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():
@@ -82,9 +118,129 @@ class LocationQueries:
     
     @staticmethod
     def get_pallet_count(container_code):
+        """
+        获取托盘数量
+        :param container_code: 要查询的托盘码
+        :return: 所属批次下的托盘总数
+        """
+        # 1. 通过托盘码获取容器详情
         container = ContainerListModel.objects.filter(
             container_code=container_code
         ).first()
         if not container:
-            return 0
-        return container.details.count()
+            logger.error(f"托盘 {container_code} 不存在")
+            return None
+     
+        container_detail = ContainerDetailModel.objects.filter(
+            container=container.id
+        ).exclude(status = 3).first()
+
+        batch_obj = container_detail.batch
+        return batch_obj.container_number
+
+    
+    @staticmethod
+    def get_pallet_count_by_batch( container_code):
+        """
+        根据托盘码查询批次下托盘总数
+        :param container_code: 要查询的托盘码
+        :return: 所属批次下的托盘总数
+        """
+        # 1. 通过托盘码获取容器详情
+        container = ContainerListModel.objects.filter(
+            container_code=container_code
+        ).first()
+
+        if not container:
+            logger.error(f"托盘 {container_code} 不存在")
+            return None
+     
+        container_detail = ContainerDetailModel.objects.filter(
+            container=container.id
+        ).exclude(status = 3).first()
+        if not container_detail:
+            logger.error(f"托盘 {container_code} 未组盘")
+            return None
+ 
+        batch_container = ContainerDetailModel.objects.filter(
+             batch = container_detail.batch.id,
+        ).all().exclude(status = 3)
+        # 统计批次下的不同托盘 item.contianer_id
+        batch_container_count = 0
+        container_ids = []
+        for item in batch_container:
+            if item.container_id not in container_ids: 
+                batch_container_count = batch_container_count + 1
+                container_ids.append(item.container_id)
+
+        return batch_container_count
+    
+
+    @staticmethod
+    def get_current_finish_task(container,batch_info):
+        batch = batch_info.get('number')
+
+        if not batch:
+            return None
+        solution = alloction_pre.objects.filter(batch_number=batch).first()
+        if not solution:
+            return None
+
+        return [solution.layer1_task_finish_number,solution.layer2_task_finish_number,solution.layer3_task_finish_number] 
+
+
+    @staticmethod
+    def divide_solution_by_layer(data):
+        
+
+        # 统计所有存在的层级
+        layer_counts = defaultdict(lambda: defaultdict(int))
+        existing_layers = set()
+        
+        for item in data:
+            # 分割层级和类型
+            try:
+                layer, loc_type = item.split('_')
+                layer_num = int(layer)
+                existing_layers.add(layer_num)
+                layer_counts[layer_num][loc_type] += 1
+            except (ValueError, IndexError):
+                continue  # 跳过无效格式的数据
+
+        # 确定最大层级(至少包含1层)
+        max_layer = max(existing_layers) if existing_layers else 1
+
+        # 构建包含所有层级的最终结果
+        final_result = {}
+        for layer in range(1, max_layer + 1):
+            final_result[str(layer)] = dict(layer_counts.get(layer, {}))
+        
+        return json.dumps(final_result, indent=2)
+    
+    @staticmethod
+    def get_alloction_pre_solution(batch_number):
+        """
+        获取指定批次的预留方案
+        :param batch_number: 批次号
+        :return: 预留方案
+        """
+        solution = alloction_pre.objects.filter(batch_number=batch_number).first()
+        if not solution:
+            return None
+        return solution.layer_solution_type
+    
+    @staticmethod
+    def get_left_location_group(batch_number):
+        """
+        获取指定批次的剩余库位
+        :param batch_number: 批次号
+        :return: 剩余库位
+        """
+        layer_solution_type =[]
+        location_group_obj =LocationGroupModel.objects.filter(current_batch=batch_number,status='occupied').all()
+        if not location_group_obj:
+            return None
+        else:
+            for item in location_group_obj:
+                layer_solution_type.append(f"{item.layer}_{item.id}")
+        return layer_solution_type

+ 237 - 13
bin/services.py

@@ -2,26 +2,87 @@ from django.db import transaction
 from .queries import LocationQueries
 from .updates import LocationUpdates 
 from .algorithms import AllocationAlgorithm
+import logging
+import json
+from collections import defaultdict
+from .models import *
+from bound.models import BoundBatchModel, BoundDetailModel, OutBatchModel, BoundListModel
+import datetime
+from django.utils import timezone
+
+logger = logging.getLogger(__name__)
 
 class AllocationService:
     @classmethod
     @transaction.atomic
     def allocate(cls, container_code, start_point):
+        logger.info(f"请求托盘:{container_code},请求位置:{start_point}")
+
         batch_info = LocationQueries.get_batch_info(container_code)
-        if not batch_info or not batch_info['number']:
+        if not batch_info :
             raise ValueError("无效的容器或批次信息")
-
-        if batch_info['status'] == 1:
+        if not batch_info['number']:
+            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:
-            return cls._subsequent_allocation(batch_info)
+            return cls._subsequent_allocation(container_code, batch_info, start_point)
         else:
             raise ValueError("非法的批次状态")
 
     @classmethod
     @transaction.atomic
     def _first_allocation(cls, container_code, batch_info, start_point):
-        total = LocationQueries.get_pallet_count(container_code)
+        total = LocationQueries.get_pallet_count_by_batch(container_code)
+        current_total = LocationQueries.get_pallet_count(container_code)
+        if not total:
+                raise ValueError("无效的容器或批次信息")
+        LocationUpdates.update_pallet_count(total, batch_info['number'])
+        if current_total == 0:
+            
+            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)
+        else:
+            locations = LocationQueries.get_left_location_group(batch_info['number'])
+            if not locations:
+                layer_cap = LocationQueries.get_group_capacity()
+                pressure = LocationQueries.get_current_pressure()
+                finish_task_sum = sum(LocationQueries.get_current_finish_task(container_code,batch_info))
+                solution, new_pressure = AllocationAlgorithm.generate_plan(total-finish_task_sum, 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)
+            else:
+                return cls._execute_allocation(locations, start_point,container_code,batch_info)   
+
+
+    @classmethod
+    def _container_group_allocation(cls,container_code, batch_info, start_point):
+        total = 1
+        batch_info['number'] = 'ContainerGroup'+str(container_code)+str(timezone.now().strftime('%Y%m%d'))
+        if not total:
+            raise ValueError("无效的容器或批次信息")
+       
         layer_cap = LocationQueries.get_group_capacity()
         pressure = LocationQueries.get_current_pressure()
 
@@ -29,17 +90,180 @@ class AllocationService:
         if not solution:
             raise RuntimeError("无法生成有效分配方案")
 
-        formatted = AllocationAlgorithm.format_solution(solution)
+        formatted = json.loads(AllocationAlgorithm.format_solution(solution))
         LocationUpdates.save_allocation_plan(batch_info['number'], formatted, new_pressure)
-        
-        return cls._execute_allocation(formatted, start_point)
+        # 使用距离最近的库位,增加切换
+        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)
+        print(f"[0] 库位组状态预定成功!")
+        location_min_value = AllocationService.get_location_list_remainder(solution,container_code,batch_info)
+        if not location_min_value:
+            raise ValueError("[1] 任务分配失败,无可用库位")
+        print(f"[1] 任务安排到第{location_min_value.c_number}个库位:{location_min_value}")
+
+        location_min_value.status = 'reserved'
+        location_min_value.save()
+        print(f"[2] 库位状态更新成功!")
+
+        update_location_group_status=LocationUpdates.update_location_group_status(location_min_value.location_code)
+        if not update_location_group_status:
+            raise ValueError("[3] 库位组状态更新失败")
+        print(f"[3] 库位组状态更新成功!")
+
+        update_location_group_batch = LocationUpdates.update_location_group_batch(location_min_value,batch_info['number'])
+        if not update_location_group_batch:
+            raise ValueError("[5] 批次信息更新失败")
+        print(f"[5] 批次信息更新成功!")
+
+        update_location_container_link = LocationUpdates.link_container(location_min_value.location_code, container_code)
+        if not update_location_container_link:
+            raise ValueError("[6] 容器与库位关联失败")
+        print(f"[6] 容器与库位关联成功!")
+ 
+        update_location_container_detail = LocationUpdates.update_container_detail_status(container_code, 2)
+        if not update_location_container_detail:
+            raise ValueError("[7] 托盘详情状态更新失败")
+        print(f"[7] 托盘详情状态更新成功!")
+
+        allocation_target_location = AllocationAlgorithm.generate_WCS_location(location_min_value)
+       
+        return location_min_value,allocation_target_location, batch_info
 
     @classmethod
-    def _execute_allocation(cls, solution, start_point):
-        # 实际分配执行逻辑
-        pass
+    def _execute_allocation(cls, solution, start_point,container_code,batch_info):
+
+        LocationUpdates.update_group_status_reserved(solution)
+        print(f"[0] 库位组状态预定成功!")
+        location_min_value = AllocationService.get_location_list_remainder(solution,container_code,batch_info)
+        if not location_min_value:
+            raise ValueError("[1] 任务分配失败,无可用库位")
+        print(f"[1] 任务安排到第{location_min_value.c_number}个库位:{location_min_value}")
+
+        location_min_value.status = 'reserved'
+        location_min_value.save()
+        print(f"[2] 库位状态更新成功!")
+
+        update_location_group_status=LocationUpdates.update_location_group_status(location_min_value.location_code)
+        if not update_location_group_status:
+            raise ValueError("[3] 库位组状态更新失败")
+        print(f"[3] 库位组状态更新成功!")
+
+        update_batch_status = LocationUpdates.update_batch_status(container_code, 2)
+        if not update_batch_status:
+            raise ValueError("[4] 批次状态更新失败")
+        print(f"[4] 批次状态更新成功!")
+
+        update_location_group_batch = LocationUpdates.update_location_group_batch(location_min_value,batch_info['number'])
+        if not update_location_group_batch:
+            raise ValueError("[5] 批次信息更新失败")
+        print(f"[5] 批次信息更新成功!")
+
+        update_location_container_link = LocationUpdates.link_container(location_min_value.location_code, container_code)
+        if not update_location_container_link:
+            raise ValueError("[6] 容器与库位关联失败")
+        print(f"[6] 容器与库位关联成功!")
+ 
+        update_location_container_detail = LocationUpdates.update_container_detail_status(container_code, 2)
+        if not update_location_container_detail:
+            raise ValueError("[7] 托盘详情状态更新失败")
+        print(f"[7] 托盘详情状态更新成功!")
+
+        allocation_target_location = AllocationAlgorithm.generate_WCS_location(location_min_value)
+       
+        return location_min_value,allocation_target_location, batch_info
 
     @classmethod
-    def _subsequent_allocation(cls, batch_info):
+    def _subsequent_allocation(cls, container_code, batch_info, start_point):
         # 后续分配逻辑
-        pass
+        total = LocationQueries.get_pallet_count_by_batch(container_code)
+        current_total = LocationQueries.get_pallet_count(container_code)
+        LocationUpdates.update_pallet_count(total, batch_info['number'])
+        if total == current_total:
+            locations = LocationQueries.get_left_location_group(batch_info['number'])
+            if not locations:
+                layer_cap = LocationQueries.get_group_capacity()
+                pressure = LocationQueries.get_current_pressure()
+                finish_task_sum = sum(LocationQueries.get_current_finish_task(container_code,batch_info))
+                if finish_task_sum == total:
+                    total += 1
+                solution, new_pressure = AllocationAlgorithm.generate_plan(total-finish_task_sum, 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)
+            else:
+                return cls._execute_allocation(locations, start_point,container_code,batch_info)
+
+    @transaction.atomic
+    def get_location_list_remainder(location_group_list,container_code,batch_info):
+        """
+        获取可用库位的c_number列表
+        :param location_list: 库位对象列表
+        :return: 可用库位编号列表
+        """
+        if not location_group_list:
+            return None
+
+        current_task = LocationQueries.get_current_finish_task(container_code,batch_info)
+        print(f"[1]当前已完成任务: {current_task}")
+        # 按压力排序
+        sorted_pressure = sorted(
+            [(0, current_task[0]), (1, current_task[1]), (2, current_task[2])],
+            key=lambda x: (-x[1], x[0])
+        )
+        # 交换第一和第二个元素的位置
+        sorted_pressure[0], sorted_pressure[1] = sorted_pressure[1], sorted_pressure[0]
+        print(f"[2]任务排序: {sorted_pressure}")
+        print(f"[3]当前选择:{sorted_pressure[0][0]+1}")
+        location_type_dict = json.loads(LocationQueries.divide_solution_by_layer(location_group_list))
+        # print(f"库位类型分配方案: {location_type_dict}")
+   
+        for layer, _ in sorted_pressure:
+            if not location_type_dict.get(str(layer+1)):
+                continue
+            # print(f"当前层: {layer+1}")
+            # print(f"当前层库位组: {location_type_dict[str(layer+1)].keys()}")
+            for group_id in location_type_dict[str(layer+1)].keys():
+                location_group = LocationGroupModel.objects.filter(
+                    id=group_id,
+                ).first()
+                if not location_group:
+                    continue
+                location_list = location_group.location_items.filter(
+                    status='available'
+                ).all().order_by('c_number')
+                if not location_list:
+                    print(f"当前层库位组 {location_group.group_code} 可用库位: None")
+                    continue
+               # 提取所有库位的 c_number
+                c_numbers = [loc.c_number for loc in location_list]
+                print(f"当前层库位组 {location_group.group_code} 可用库位: {c_numbers}")
+                # 更新任务完成数目
+                current_task[layer] = current_task[layer] + 1
+                LocationUpdates.update_current_finish_task(container_code,current_task)
+                return location_list[0]

+ 174 - 5
bin/updates.py

@@ -1,7 +1,9 @@
 from django.db import transaction
 from .models import *
 from container.models import *
+import logging
 
+logger = logging.getLogger(__name__)
 class LocationUpdates:
     """
     库位相关更新
@@ -36,12 +38,16 @@ class LocationUpdates:
             container = ContainerListModel.objects.get(
                 container_code=container_code
             )
-            detail = ContainerDetailModel.objects.select_related('batch').get(
+            detail = ContainerDetailModel.objects.select_related('batch').filter(
                 container=container.id,
                 status__in=[1, 2]
-            )
-            detail.batch.status = status
-            detail.batch.save()
+                ).all()
+            if not detail:
+                print(f"托盘 {container_code} 未组盘_from update_batch_status")
+            for item in detail:
+                item.batch.status = status
+                item.batch.save()
+
             return True
         except Exception as e:
             raise RuntimeError(f"批次状态更新失败: {str(e)}")
@@ -61,4 +67,167 @@ class LocationUpdates:
             alloction_pre.objects.update_or_create(
                 batch_number=batch_number,
                 defaults={'layer_pre_type': solution}
-            )
+            )
+
+    @staticmethod
+    def update_pallet_count(batch_container_count,bound_number):
+        batch_item = BoundBatchModel.objects.filter(bound_number = bound_number).first()
+        if not batch_item:
+            print(f"批次号获取失败!")
+            return None
+        batch_item.container_number = batch_container_count
+        batch_item.save()
+        return True
+    
+    @staticmethod
+    def update_group_status_reserved(location_group_list):
+        """
+        更新库位组状态
+        :param location_group_list: 库位组对象列表
+        :return:
+        """
+        try:
+            for location_group in location_group_list:
+                # 1. 获取库位组
+                if not location_group:
+                    print(f"库位组获取失败!")
+                    return False
+                # 2. 更新库位组状态
+                location_group_id = location_group.split('_')[1]
+                location_group_item = LocationGroupModel.objects.filter(
+                    id=location_group_id
+                ).first()
+                if not location_group_item:
+                    print(f"库位组 {location_group} 不存在")
+                    return False
+                # 3. 更新库位组状态
+                location_group_item.status = 'reserved'
+                location_group_item.save()
+
+            return True
+        except Exception as e:       
+            logger.error(f"更新库位组状态失败:{str(e)}")
+            print(f"更新库位组状态失败:{str(e)}")
+            return False    
+        
+    def update_current_finish_task(container,task_finish_number):
+        """
+        更新当前完成任务数
+        :param container: 托盘号
+        :param task_finish_number: 完成任务数
+        :return: Boolean
+        """
+        from .queries import LocationQueries
+        batch = LocationQueries.get_batch_info(container).get('number') 
+
+        if not batch:
+            return None
+        solution = alloction_pre.objects.filter(batch_number=batch).first()
+        if not solution:
+            return None
+        solution.layer1_task_finish_number = task_finish_number[0]
+        solution.layer2_task_finish_number = task_finish_number[1]
+        solution.layer3_task_finish_number = task_finish_number[2]
+        solution.save()
+        return True
+    
+    @staticmethod
+    @transaction.atomic
+    def update_location_group_status( location_code):
+        """
+        更新库位组状态
+        :param location_code: 库位编码
+        :return:
+        """
+        try:
+            # 1. 获取库位
+            location = LocationModel.objects.filter(
+                location_code=location_code
+            ).first()
+            if not location:
+                print(f"库位获取失败!")
+                return False
+            
+            # 2. 获取库位组
+            location_group = LocationGroupModel.objects.filter(
+                group_code=location.location_group
+            ).first()
+            if not location_group:
+                print(f"库位组获取失败!")
+                return False
+            current=0
+            for location_item in location_group.location_items.all():
+                if location_item.status != 'available':
+                    current=current + 1
+            # 3. 更新库位组状态
+            if current == 0:
+                location_group.status = 'available'
+            elif current == location_group.max_capacity:
+                location_group.status = 'full'
+            else:
+                location_group.status = 'occupied'
+            
+            location_group.current_goods_quantity = sum(
+                    [loc.current_quantity for loc in location_group.location_items.all()]
+                )
+            location_group.current_quantity = current
+                
+            location_group.save()
+            
+            return True
+        except Exception as e:       
+            logger.error(f"更新库位组状态失败:{str(e)}")
+            print(f"更新库位组状态失败:{str(e)}")
+
+    @staticmethod
+    @transaction.atomic
+    def update_location_group_batch(location,bound_number):
+        """
+        :param location: 库位对象
+        :param 
+
+
+        :return:
+        """
+        try:
+            # 1. 获取库位组
+            location_group = LocationGroupModel.objects.filter(
+                group_code=location.location_group
+            ).first()
+            if not location_group:
+                print(f"库位组获取失败!")
+                return False
+            # 2. 更新库位组的批次
+            location_group.current_batch = bound_number
+            location_group.save()
+            print(f"更新库位组的批次成功!")
+            return True
+        except Exception as e:       
+            logger.error(f"更新库位组的批次失败:{str(e)}")
+            print(f"更新库位组的批次失败:{str(e)}")
+            return False
+        
+    def update_container_detail_status(container_code,status):
+        try:
+            # 1. 获取托盘
+            container = ContainerListModel.objects.filter(
+                container_code=container_code
+            ).first()
+            if not container:
+                print(f"托盘 {container_code} 不存在")
+                return False
+            # 2. 更新托盘状态
+            container_detail = ContainerDetailModel.objects.filter(
+                container=container.id
+            ).exclude(status=3).all()
+            if not container_detail:
+                print(f"托盘 {container_code} 未组盘_from update_container_detail_status")
+                return True
+            for detail in container_detail:
+                detail.status = status
+                detail.save()
+            return True
+        except Exception as e:       
+            logger.error(f"更新托盘状态失败:{str(e)}")
+            print(f"更新托盘状态失败:{str(e)}")
+            return False

+ 46 - 7
bin/views.py

@@ -61,14 +61,13 @@ class locationViewSet(viewsets.ModelViewSet):
             return None
     def get_queryset(self):
         id = self.get_project()
-        # 预取激活的关联托盘(通过中间模型过滤)
         prefetch_containers = Prefetch(
-            'current_containers',
-            queryset=ContainerListModel.objects.filter(
-                locationcontainerlink__is_active=True  # 确保中间模型字段名正确
-            ).distinct(),
-            to_attr='active_containers'  # 将结果存储到模型的 active_containers 属性
-        )
+        'container_links', 
+        queryset=LocationContainerLink.objects.filter(
+            is_active=True
+        ).select_related('container'),  # 加载关联的容器对象
+        to_attr='active_links'  # 新的属性名称
+    )
 
         if self.request.user:
             if id is None:
@@ -530,6 +529,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):
         """
         获取批次状态

+ 50 - 3
bound/filter.py

@@ -1,5 +1,50 @@
 from django_filters import FilterSet
-from .models import BoundListModel, BoundDetailModel,BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel
+from .models import BoundListModel, BoundDetailModel,BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
+
+class OutBoundDemandFilter(FilterSet):
+    class Meta:
+        model = OutBoundDemandModel
+        fields = {
+            "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            'goods_code': ['exact', 'icontains'],
+            'goods_desc': ['exact', 'icontains'],
+            'goods_std': ['exact', 'icontains'],
+            'goods_unit': ['exact', 'icontains'],
+            'goods_qty': ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
+            'working': ['exact'],
+            'creater': ['exact', 'icontains'],
+            'create_time': ['exact', 'gt', 'gte', 'lt', 'lte',
+             'year', 'year__gt', 'year__gte', 'year__lt', 'year__lte', 'year__range', 'year__in', 
+             'month','month__gt','month__gte','month__lt','month__lte','month__range','month__in', 
+             'day', 'day__gt', 'day__gte', 'day__lt', 'day__lte', 'day__range', 'day__in',
+              'week_day', 'week_day__gt', 'week_day__gte', 'week_day__lt', 'week_day__lte', 'week_day__range', 'week_day__in', 
+              'hour', 'hour__gt', 'hour__gte', 'hour__lt', 'hour__lte', 'hour__range', 'hour__in',
+
+              'date', 'date__gt', 'date__gte', 'date__lt', 'date__lte', 'date__range', 'date__in', 
+              'time', 'time__gt', 'time__gte', 'time__lt', 'time__lte', 'time__range', 'time__in', 'isnull', 'in', 'range'],
+            'update_time':
+             ['exact', 'gt', 'gte', 'lt', 'lte',
+             'year', 'year__gt', 'year__gte', 'year__lt', 'year__lte', 'year__range', 'year__in', 
+             'month','month__gt','month__gte','month__lt','month__lte','month__range','month__in', 
+             'day', 'day__gt', 'day__gte', 'day__lt', 'day__lte', 'day__range', 'day__in',
+              'week_day', 'week_day__gt', 'week_day__gte', 'week_day__lt', 'week_day__lte', 'week_day__range', 'week_day__in', 
+              'hour', 'hour__gt', 'hour__gte', 'hour__lt', 'hour__lte', 'hour__range', 'hour__in',
+
+              'date', 'date__gt', 'date__gte', 'date__lt', 'date__lte', 'date__range', 'date__in', 
+              'time', 'time__gt', 'time__gte', 'time__lt', 'time__lte', 'time__range', 'time__in', 'isnull', 'in', 'range'],
+            'is_delete': ['exact'],
+        }
+
+class MaterialStatisticsFilter(FilterSet):
+    class Meta:
+        model = MaterialStatistics
+        fields = {
+            "goods_code": ['exact', 'icontains'],
+            "goods_desc": ['exact', 'icontains'],
+            "goods_std": ['exact', 'icontains'],
+            "goods_unit": ['exact', 'icontains'],
+            "total_quantity": ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
+        }
 
 
 class BoundListFilter(FilterSet):
@@ -85,17 +130,19 @@ class BoundBatchFilter(FilterSet):
         fields = {
             "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
             'bound_number': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
+            'sourced_number': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'bound_month': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
 
             
             'warehouse_code': ['icontains', 'exact'],
             'warehouse_name': ['icontains','exact'],
             
-                'goods_code': ['icontains', 'exact'],
-                'goods_desc': ['icontains', 'exact'],
+            'goods_code': ['icontains', 'exact'],
+            'goods_desc': ['icontains', 'exact'],
             'goods_std': ['icontains', 'exact'],
             'goods_qty': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'goods_in_qty': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
+            'goods_in_location_qty' : ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'goods_out_qty': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'status': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],
             'goods_weight': ['exact', 'gt', 'gte', 'lt', 'lte', 'icontains','range'],

+ 18 - 0
bound/migrations/0012_boundbatchmodel_goods_package.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2025-05-15 16:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0011_boundlistmodel_audit_status'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='boundbatchmodel',
+            name='goods_package',
+            field=models.CharField(default='待填写', max_length=255, verbose_name='商品包装'),
+        ),
+    ]

+ 30 - 0
bound/migrations/0013_materialstatistics.py

@@ -0,0 +1,30 @@
+# Generated by Django 4.1.2 on 2025-05-19 19:13
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0012_boundbatchmodel_goods_package'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='MaterialStatistics',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('goods_code', models.CharField(max_length=255, unique=True, verbose_name='商品编码')),
+                ('goods_desc', models.CharField(max_length=255, verbose_name='商品描述')),
+                ('goods_std', models.CharField(blank=True, default='待填写', max_length=255, null=True, verbose_name='商品标准')),
+                ('goods_unit', models.CharField(default='待填写', max_length=255, verbose_name='商品单位')),
+                ('total_quantity', models.BigIntegerField(default=0, verbose_name='总数量')),
+            ],
+            options={
+                'verbose_name': '物料统计',
+                'verbose_name_plural': '物料统计',
+                'db_table': 'materialstatistics',
+                'ordering': ['goods_code'],
+            },
+        ),
+    ]

+ 28 - 0
bound/migrations/0014_alter_boundbatchmodel_goods_in_location_qty_and_more.py

@@ -0,0 +1,28 @@
+# Generated by Django 4.1.2 on 2025-05-19 19:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0013_materialstatistics'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='boundbatchmodel',
+            name='goods_in_location_qty',
+            field=models.BigIntegerField(default=0, verbose_name='库位入库数量'),
+        ),
+        migrations.AlterField(
+            model_name='boundbatchmodel',
+            name='goods_in_qty',
+            field=models.BigIntegerField(default=0, verbose_name='组盘入库数量'),
+        ),
+        migrations.AlterField(
+            model_name='materialstatistics',
+            name='total_quantity',
+            field=models.BigIntegerField(default=0, verbose_name='计划数量'),
+        ),
+    ]

+ 18 - 0
bound/migrations/0015_rename_goods_out_location_qty_boundbatchmodel_goods_actual_qty.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2025-05-19 19:53
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0014_alter_boundbatchmodel_goods_in_location_qty_and_more'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='boundbatchmodel',
+            old_name='goods_out_location_qty',
+            new_name='goods_actual_qty',
+        ),
+    ]

+ 42 - 0
bound/migrations/0016_materialstatistics_total_demanded_quantity_and_more.py

@@ -0,0 +1,42 @@
+# Generated by Django 4.1.2 on 2025-05-19 21:37
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0015_rename_goods_out_location_qty_boundbatchmodel_goods_actual_qty'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='materialstatistics',
+            name='total_demanded_quantity',
+            field=models.BigIntegerField(default=0, verbose_name='需求数量'),
+        ),
+        migrations.CreateModel(
+            name='OutBoundDemandModel',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('goods_code', models.CharField(max_length=255, verbose_name='商品编码')),
+                ('goods_desc', models.CharField(max_length=255, verbose_name='商品描述')),
+                ('goods_std', models.CharField(blank=True, default='待填写', max_length=255, null=True, verbose_name='商品标准')),
+                ('goods_unit', models.CharField(default='待填写', max_length=255, verbose_name='商品单位')),
+                ('goods_qty', models.BigIntegerField(default=0, verbose_name='计划数量')),
+                ('working', models.BooleanField(default=False, verbose_name='是否在工作')),
+                ('creater', models.CharField(default='uesr', max_length=255, verbose_name='Who Created')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='Create Time')),
+                ('update_time', models.DateTimeField(auto_now=True, null=True, verbose_name='Update Time')),
+                ('is_delete', models.BooleanField(default=False, verbose_name='Delete Label')),
+                ('bound_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='out_demand', to='bound.boundlistmodel', verbose_name='Bound List')),
+            ],
+            options={
+                'verbose_name': '出库需求',
+                'verbose_name_plural': '出库需求',
+                'db_table': 'outbounddemand',
+                'ordering': ['-id'],
+            },
+        ),
+    ]

+ 18 - 0
bound/migrations/0017_outbounddemandmodel_out_type.py

@@ -0,0 +1,18 @@
+# Generated by Django 4.1.2 on 2025-05-19 22:07
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0016_materialstatistics_total_demanded_quantity_and_more'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='outbounddemandmodel',
+            name='out_type',
+            field=models.IntegerField(choices=[(0, '发货出库'), (4, '其他出库')], default=4, verbose_name='出库类型'),
+        ),
+    ]

+ 19 - 0
bound/migrations/0018_outbatchmodel_bound_list.py

@@ -0,0 +1,19 @@
+# Generated by Django 4.1.2 on 2025-05-20 02:28
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0017_outbounddemandmodel_out_type'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='outbatchmodel',
+            name='bound_list',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='out_batch_list', to='bound.boundlistmodel', verbose_name='Bound List'),
+        ),
+    ]

+ 119 - 4
bound/models.py

@@ -1,6 +1,8 @@
 from django.db import models
 from erp.models import InboundBill, MaterialDetail, OutboundBill,OutMaterialDetail
-
+from django.db.models import Sum
+from django.db.models.signals import post_save, post_delete
+from django.dispatch import receiver
 
 class BoundListModel(models.Model):
     STATUS =( 
@@ -66,10 +68,11 @@ class BoundBatchModel(models.Model):
     goods_std = models.CharField(default='待填写', max_length=255, verbose_name="商品标准",blank=True, null=True)
     goods_unit = models.CharField(default='待填写', max_length=255, verbose_name="商品单位")
     goods_qty = models.BigIntegerField(default=0, verbose_name="商品数量")
-    goods_in_qty = models.BigIntegerField(default=0, verbose_name="入库数量")
-    goods_in_location_qty = models.BigIntegerField(default=0, verbose_name="库位上入库数量")
+    goods_package = models.CharField(default='待填写', max_length=255, verbose_name="商品包装")
+    goods_in_qty = models.BigIntegerField(default=0, verbose_name="组盘入库数量")
+    goods_in_location_qty = models.BigIntegerField(default=0, verbose_name="库位入库数量")
     goods_out_qty = models.BigIntegerField(default=0, verbose_name="出库数量")
-    goods_out_location_qty = models.BigIntegerField(default=0, verbose_name="库位上出库数量")
+    goods_actual_qty = models.BigIntegerField(default=0, verbose_name="库位上出库数量")
 
     status = models.IntegerField(choices=CONTAINER_STATUS, default=0, verbose_name='批次状态')
     container_number = models.IntegerField( default=0, verbose_name="托盘数目")
@@ -90,6 +93,114 @@ class BoundBatchModel(models.Model):
         verbose_name_plural = "Bound Batch"
         ordering = ['-id']
 
+    def __str__(self):
+        return f"{self.bound_number} - {self.goods_code} - {self.goods_desc}"
+    
+    def save(self, *args, **kwargs):
+
+        # self.goods_actual_qty = self.goods_in_qty - self.goods_out_qty
+        self.goods_actual_qty = self.goods_in_location_qty
+        super().save(*args, **kwargs)
+
+class OutBoundDemandModel(models.Model):
+    OUT_TYPE = (
+        (0, '发货出库'),
+        (4, '其他出库'),
+    )
+    bound_list = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, verbose_name="Bound List", related_name='out_demand')
+    goods_code = models.CharField(max_length=255, verbose_name="商品编码")
+    goods_desc = models.CharField(max_length=255, verbose_name="商品描述")
+    goods_std = models.CharField(default='待填写', max_length=255, verbose_name="商品标准",blank=True, null=True)
+    goods_unit = models.CharField(default='待填写', max_length=255, verbose_name="商品单位")
+    goods_qty = models.BigIntegerField(default=0, verbose_name="计划数量")
+    working = models.BooleanField(default=False, verbose_name="是否在工作")
+    creater = models.CharField(default='uesr', max_length=255, verbose_name="Who Created")
+    create_time = models.DateTimeField(auto_now_add=True, verbose_name="Create Time")
+    update_time = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name="Update Time")
+    is_delete = models.BooleanField(default=False, verbose_name='Delete Label')
+    out_type = models.IntegerField(choices=OUT_TYPE, default=4,  verbose_name="出库类型")
+
+    class Meta:
+        db_table = 'outbounddemand'
+        verbose_name = '出库需求'
+        verbose_name_plural = "出库需求"
+        ordering = ['-id']
+
+class MaterialStatistics(models.Model):
+    goods_code = models.CharField(max_length=255, verbose_name="商品编码", unique=True)
+    goods_desc = models.CharField(max_length=255, verbose_name="商品描述")
+    goods_std = models.CharField(default='待填写', max_length=255, verbose_name="商品标准", blank=True, null=True)
+    goods_unit = models.CharField(default='待填写', max_length=255, verbose_name="商品单位")
+    total_quantity = models.BigIntegerField(default=0, verbose_name="计划数量")
+    total_demanded_quantity = models.BigIntegerField(default=0, verbose_name="需求数量")
+
+    def __str__(self):
+        return f"{self.goods_code} - {self.goods_desc}"
+
+    @property
+    def bound_batches(self):
+        return BoundBatchModel.objects.filter(goods_code=self.goods_code).exclude(goods_actual_qty=0).order_by('bound_batch_order')
+
+    class Meta:
+        db_table = 'materialstatistics'
+        verbose_name = '物料统计'
+        verbose_name_plural = "物料统计"
+        ordering = ['goods_code']
+
+@receiver([post_save, post_delete], sender=BoundBatchModel)
+def update_material_statistics(sender, instance, **kwargs):
+    goods_code = instance.goods_code
+    stats, created = MaterialStatistics.objects.get_or_create(
+        goods_code=goods_code,
+        defaults={
+            'goods_desc': instance.goods_desc,
+            'goods_std': instance.goods_std or '待填写',
+            'goods_unit': instance.goods_unit or '待填写',
+        }
+    )
+    
+    # 更新物料信息为最新批次的信息(可选)
+    stats.goods_desc = instance.goods_desc
+    if instance.goods_std and instance.goods_std != '待填写':
+        stats.goods_std = instance.goods_std
+    if instance.goods_unit and instance.goods_unit != '待填写':
+        stats.goods_unit = instance.goods_unit
+    stats.save()
+
+    # 计算总数量
+    total = BoundBatchModel.objects.filter(goods_code=goods_code).aggregate(
+        total=Sum('goods_in_location_qty')
+    )['total'] or 0
+    stats.total_quantity = total
+    stats.save()
+
+@receiver([post_save, post_delete], sender=OutBoundDemandModel)
+def update_material_demanded(sender, instance, **kwargs):
+    goods_code = instance.goods_code
+    stats, created = MaterialStatistics.objects.get_or_create(
+        goods_code=goods_code,
+        defaults={
+            'goods_desc': instance.goods_desc,
+            'goods_std': instance.goods_std or '待填写',
+            'goods_unit': instance.goods_unit or '待填写',
+        }
+    )
+    
+    # 更新物料信息为最新批次的信息(可选)
+    stats.goods_desc = instance.goods_desc
+    if instance.goods_std and instance.goods_std != '待填写':
+        stats.goods_std = instance.goods_std
+    if instance.goods_unit and instance.goods_unit != '待填写':
+        stats.goods_unit = instance.goods_unit
+    stats.save()
+
+    # 计算总数量
+    total = OutBoundDemandModel.objects.filter(goods_code=goods_code,working=True).aggregate(
+        total=Sum('goods_qty')
+    )['total'] or 0
+    stats.total_demanded_quantity = total
+    stats.save()
+
 class OutBatchModel(models.Model):
     CONTAINER_STATUS = (
         (0, '申请'),
@@ -100,6 +211,8 @@ class OutBatchModel(models.Model):
         (0, '发货出库'),
         (4, '其他出库'),
     )
+    bound_list = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, verbose_name="Bound List", related_name='out_batch_list',blank=True, null=True)
+
     out_number = models.CharField(max_length=255, verbose_name="出库批次号",blank=False, null=False)
 
     batch_number = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name="批次号", related_name='out_batch')
@@ -138,6 +251,8 @@ class OutBatchModel(models.Model):
         verbose_name_plural = "Out Batch"
         ordering = ['-id']
 
+
+
 # 利用创建好的批次来与申请单相对应       
 class OutBoundDetailModel(models.Model):
     CONTAINER_STATUS = (

+ 36 - 1
bound/serializers.py

@@ -1,8 +1,15 @@
 from rest_framework import serializers
-from .models import BoundListModel, BoundDetailModel, BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel
+from .models import BoundListModel, BoundDetailModel, BoundBatchModel,OutBatchModel,BatchLogModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
 from utils import datasolve
 
+class OutBoundDemandModelSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = OutBoundDemandModel
+        fields = '__all__'
+        read_only_fields = ['id', 'create_time', 'update_time']
 
+            
+    
 class BoundListGetSerializer(serializers.ModelSerializer):
     # 定义主单列表的序列化器,用于获取操作,字段只读
     bound_month = serializers.CharField(read_only=True, required=False)
@@ -205,11 +212,17 @@ class BoundDetailPostSerializer(serializers.ModelSerializer):
         }
 
 class OutBatchGetSerializer(serializers.ModelSerializer):
+    batch_number = serializers.SerializerMethodField()
     class Meta:
         model = OutBatchModel
         fields = '__all__'
         read_only_fields = ['id', 'openid']
+    def get_batch_number(self, obj):
+        from .serializers import BoundBatchGetSerializer
+        return BoundBatchGetSerializer(obj.batch_number).data
+    
 class OutBatchPostSerializer(serializers.ModelSerializer):
+
     out_number = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate]) 
     out_type = serializers.CharField(read_only=False, required=True, validators=[datasolve.data_validate]) 
     out_note = serializers.CharField(read_only=False, required=False, validators=[datasolve.data_validate]) 
@@ -336,3 +349,25 @@ class OutBoundDetailPostSerializer(serializers.ModelSerializer):
             }
         }
 
+class MaterialStatisticsSerializer(serializers.ModelSerializer):
+    # 定义物料统计的序列化器,用于获取操作,字段只读
+
+    class Meta:
+        # 指定模型和排除字段
+        model = MaterialStatistics
+        fields = '__all__'
+        read_only_fields = ['id']
+    
+    
+class MaterialStatisticsSerializer_items(serializers.ModelSerializer):
+    # 定义物料统计的序列化器,用于获取操作,字段只读
+    batch_items = serializers.SerializerMethodField()
+    class Meta:
+        # 指定模型和排除字段
+        model = MaterialStatistics
+        exclude = ['id', 'goods_code', 'goods_desc', 'goods_std', 'goods_unit','total_quantity']
+        read_only_fields = ['id']
+    
+    def get_batch_items(self, obj):
+        batches = obj.bound_batches
+        return BoundBatchGetSerializer(batches, many=True).data

+ 12 - 0
bound/urls.py

@@ -29,6 +29,13 @@ 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"), 
+
+path(r'batch/count/', views.MaterialStatisticsViewSet.as_view({"get": "list" }), name="materialstatistics"), 
+re_path(r'^batch/count/(?P<pk>\d+)/$', views.MaterialStatisticsViewSet.as_view({
+    'get': 'retrieve',
+
+}), name="materialstatistics_1"),
 
 re_path(r'^batch/(?P<pk>\d+)/$', views.BoundBatchViewSet.as_view({
     'get': 'retrieve',
@@ -37,6 +44,11 @@ re_path(r'^batch/(?P<pk>\d+)/$', views.BoundBatchViewSet.as_view({
     'delete': 'destroy',
 }), name="boundbatch_1"),
 
+path(r'outdemand/', views.OutBoundDemandViewSet.as_view({"put": "batch_list", "post": "create", "get": "list" }), name="outboundbatchcontainer"), 
+path(r'batchdemand/', views.OutBoundDemandViewSet.as_view({ "post": "distribute", "put": "batch_demanded_list" }), name="outboundbatchcontainer"), 
+
+
+
 path(r'outbatch/', views.OutBoundBatchViewSet.as_view({"get": "list", "post": "create" }), name="outboundbatch"), 
 
 re_path(r'^outbatch/(?P<pk>\d+)/$', views.OutBoundBatchViewSet.as_view({

+ 392 - 5
bound/views.py

@@ -4,23 +4,373 @@ from utils.datasolve import sumOfList, transportation_calculate
 from utils.md5 import Md5
 from rest_framework.filters import OrderingFilter
 from django_filters.rest_framework import DjangoFilterBackend
-
+from django.db import transaction
 from rest_framework.response import Response
 from rest_framework.exceptions import APIException
 from django.utils import timezone
-
-from .models import BoundListModel, BoundDetailModel,BoundBatchModel, BatchLogModel, OutBatchModel,OutBoundDetailModel
+from django.db.models import Sum
+from .models import BoundListModel, BoundDetailModel,BoundBatchModel, BatchLogModel, OutBatchModel,OutBoundDetailModel,MaterialStatistics,OutBoundDemandModel
 # from .files import FileListRenderCN, FileDetailRenderCN
 
 from .serializers import BoundListGetSerializer,BoundListPostSerializer,BoundBatchGetSerializer,BoundBatchPostSerializer,BoundDetailGetSerializer,BoundDetailPostSerializer
 from .serializers import OutBoundDetailGetSerializer,OutBoundDetailPostSerializer,OutBatchGetSerializer,OutBatchPostSerializer,BatchLogGetSerializer
+from .serializers import MaterialStatisticsSerializer,MaterialStatisticsSerializer_items
+from .serializers import OutBoundDemandModelSerializer
 from .filter import BoundListFilter, BoundDetailFilter,BoundBatchFilter
 from .filter import OutBatchFilter,OutBoundDetailFilter,BatchlogFilter
+from .filter import MaterialStatisticsFilter
+from .filter import OutBoundDemandFilter
 # 以后添加模块检验
 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 OutBoundDemandViewSet(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 = OutBoundDemandFilter
+    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 OutBoundDemandModel.objects.filter( is_delete=False)
+            else:
+                return OutBoundDemandModel.objects.filter( id=id, is_delete=False)
+        else:
+            return OutBoundDemandModel.objects.none()
 
+    def get_serializer_class(self):
+        if self.action in ['list' ]:
+            return OutBoundDemandModelSerializer
+        else:
+            return OutBoundDemandModelSerializer
+    
+    def batch_list(self, request):
+        data =self.request.data
+        OutBoundDemand_all = OutBoundDemandModel.objects.filter(bound_list_id=data['bound_list_id'], is_delete=False).all()
+        data = OutBoundDemandModelSerializer(OutBoundDemand_all, many=True).data
+        return_data ={
+                "code": 200,
+                "msg": "Success Create",
+            	"data": data
+        }
+    
+        return Response(return_data,status=200,headers={})
+
+  
+    def create(self, request, *args, **kwargs):
+        data = self.request.data
+
+        data['openid'] = self.request.auth.openid
+        data['create_time'] = str(timezone.now().strftime('%Y-%m-%d %H:%M:%S'))
+        data['update_time'] = str(timezone.now().strftime('%Y-%m-%d %H:%M:%S'))
+        data['working'] = True
+        bound_list_obj = BoundListModel.objects.get(id=data['bound_list_id'])
+
+        OutBoundDemand_obj =OutBoundDemandModel.objects.create(
+            bound_list=bound_list_obj, 
+            goods_code=data['goods_code'], 
+            goods_desc=data['goods_desc'], 
+            goods_std=data['goods_std'], 
+            goods_unit=data['goods_unit'], 
+            goods_qty=data['goods_out_qty'],
+            out_type = data['out_type'],
+            creater=data['creater'], 
+            create_time=data['create_time'],
+            update_time=data['update_time'], 
+            working=data['working']
+            )
+        return_data = OutBoundDemandModelSerializer(OutBoundDemand_obj).data
+        headers = self.get_success_headers(return_data)
+
+        return Response(return_data, status=200, headers=headers)
+    
+    def batch_demanded_list(self, request):
+        data =self.request.data
+        OutBoundDemand_all = OutBatchModel.objects.filter(bound_list_id=data['bound_list_id'], is_delete=False).all()
+        data = OutBatchGetSerializer(OutBoundDemand_all, many=True).data
+        return_data = {
+            "code": 200,
+            "msg": "Success Create",
+            "data": data
+        }
+
+        return Response(return_data,status=200,headers={})
+
+    def distribute(self, request):
+        """主分配入口"""
+        try:
+            with transaction.atomic():
+                bound_list_id, demands,out_type, creater= self.validate_distribute_request(request)
+
+                if OutBatchModel.objects.filter(bound_list_id=bound_list_id, is_delete=False).exists():
+                    return_data = {
+                        "code": 200,
+                        "msg": "Success Create",
+                        "data": {
+                            "msg": "该订单已分配,请勿重复分配"
+                        }
+                    }
+                    return Response(return_data, status=200, headers={})
+                aggregated_demands = self.aggregate_demands(demands)
+                result = self.process_all_goods(aggregated_demands, request, out_type, creater,bound_list_id)
+                return self.build_success_response(result)
+        except APIException as e:
+            return self.build_error_response(e.detail, 200)
+        except Exception as e:
+            return self.build_error_response(str(e), 200)
+
+    # 验证层方法
+    def validate_distribute_request(self, request):
+        """验证请求参数"""
+        bound_list_id = request.data.get('bound_list_id')
+        if not bound_list_id:
+            raise APIException({"detail": "Missing bound_list_id"})
+
+        demands = OutBoundDemandModel.objects.filter(
+            bound_list_id=bound_list_id,
+            is_delete=False
+        )
+        if not demands.exists():
+            raise APIException({"detail": "No demands found"})
+        base_info = OutBoundDemandModel.objects.filter(
+            bound_list_id=bound_list_id,
+            is_delete=False
+        ).first()
+        out_type = base_info.out_type
+        creater = base_info.creater
+
+        return bound_list_id, demands, out_type, creater
+
+    # 数据处理层方法
+    def aggregate_demands(self, demands):
+        """合并相同物料需求"""
+        return demands.values('goods_code').annotate(
+            total_demand=Sum('goods_qty')
+        )
+
+    # 核心分配逻辑
+    def process_all_goods(self, aggregated_demands, request, out_type, creater,bound_list_id):
+        """处理所有物料分配"""
+        return [
+            self.process_single_goods(
+                goods_code=item['goods_code'],
+                total_demand=item['total_demand'],
+                request=request,
+                out_type=out_type,
+                creater=creater,
+                bound_list_id=bound_list_id
+
+            )
+            for item in aggregated_demands
+        ]
+
+    def process_single_goods(self, goods_code, total_demand, request,out_type, creater,bound_list_id):
+        """处理单个物料分配"""
+        batches = self.get_available_batches(goods_code)
+        remaining, allocations = self.allocate_batches(total_demand, batches, request,out_type, creater,bound_list_id)
+        
+        if remaining > 0:
+            raise APIException({
+                "detail": f"Insufficient stock for {goods_code}",
+                "required": total_demand,
+                "allocated": total_demand - remaining
+            })
+        
+        return {
+            "goods_code": goods_code,
+            "total_demand": total_demand,
+            "allocations": allocations
+        }
+
+    def get_available_batches(self, goods_code):
+        """获取可用入库批次"""
+        return BoundBatchModel.objects.filter(
+            goods_code=goods_code,
+            is_delete=False
+        ).order_by('bound_batch_order')
+
+    # 批次分配逻辑
+    def allocate_batches(self, total_demand, batches, request,out_type, creater,bound_list_id):
+        """分配具体批次"""
+        remaining = total_demand
+        allocations = []
+        
+        for batch in batches:
+            if remaining <= 0:
+                break
+            
+            allocated = self.allocate_single_batch(
+                batch=batch,
+                remaining=remaining,
+                request=request,
+                out_type=out_type,
+                creater=creater,
+                bound_list_id=bound_list_id
+
+            )
+            
+            if allocated == 0:
+                continue
+                
+            allocations.append(allocated)
+            remaining -= allocated['allocated']
+        
+        return remaining, allocations
+
+    def allocate_single_batch(self, batch, remaining, request,out_type, creater,bound_list_id):
+        """单个批次分配逻辑"""
+        available = batch.goods_in_location_qty - batch.goods_out_qty
+        if available <= 0:
+            return 0
+
+        allocate_qty = min(remaining, available)
+        self.update_batch_status(batch, allocate_qty)
+        out_batch = self.create_out_batch(batch, allocate_qty, request,out_type, creater,bound_list_id)
+        
+        return {
+            "batch": batch.bound_number,
+            "allocated": allocate_qty,
+            "out_batch": out_batch.out_number
+        }
+
+    # 数据操作层方法
+    def update_batch_status(self, batch, allocate_qty):
+        """更新批次状态和数量"""
+        batch.goods_out_qty += allocate_qty
+        
+        if batch.goods_out_qty == batch.goods_in_location_qty:
+            batch.status = 6  # 已出库
+        elif batch.goods_out_qty > 0:
+            batch.status = 5  # 部分出库
+            
+        batch.save()
+
+    def create_out_batch(self, batch, allocate_qty, request,out_type, creater,bound_list_id):
+        """创建出库记录"""
+        out_data = {
+            'bound_list': bound_list_id,
+            'out_number': self.generate_out_number(batch.goods_code),
+            'batch_number': batch.id,
+            'out_date': timezone.now(),
+            'warehouse_code': batch.warehouse_code,
+            'warehouse_name': batch.warehouse_name,
+            'goods_code': batch.goods_code,
+            'goods_desc': batch.goods_desc,
+            'goods_out_qty': allocate_qty,
+            'status': 0,
+            'openid': request.auth.openid,
+            'out_type': out_type,
+            'creater': creater,
+
+
+        }
+        
+        serializer = OutBatchPostSerializer(data=out_data)
+        if not serializer.is_valid():
+            raise APIException({
+                "detail": f"Serialization error for {batch.goods_code}",
+                "errors": serializer.errors
+            })
+            
+        return serializer.save()
+
+    # 工具方法
+    def generate_out_number(self, goods_code):
+        """生成唯一出库单号"""
+        timestamp = timezone.now().strftime("%Y%m%d%H%M%S")
+        import uuid
+
+        return f"OUT-{goods_code}-{timestamp}-{uuid.uuid4().hex[:6]}"
+
+    def build_success_response(self, data):
+        """构建成功响应"""
+        return Response({
+            "code": 200,
+            "msg": "Distribution completed",
+            "data": data
+        })
+
+    def build_error_response(self, error, status_code):
+        """构建错误响应"""
+        return Response({
+            "code": status_code,
+            "msg": "Distribution failed" if status_code == 400 else "Server error",
+            "error": error
+        }, status=status_code)
+
+
+class MaterialStatisticsViewSet(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 = MaterialStatisticsFilter
+
+    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 MaterialStatistics.objects.filter()
+            else:
+                return MaterialStatistics.objects.filter(id=id)
+        else:
+            return MaterialStatistics.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list' ]:
+            return MaterialStatisticsSerializer
+        elif self.action in ['retrieve']:
+            return MaterialStatisticsSerializer_items
+        else:
+            return self.http_method_not_allowed(request=self.request)
+        
+    
 
 class BoundListViewSet(viewsets.ModelViewSet):
     """
@@ -181,8 +531,8 @@ class BoundBatchViewSet(viewsets.ModelViewSet):
                     data['bound_batch_order'] = int(bound_last_code.split('-')[-1])+1
                     data['bound_number'] = data['goods_code'] + '-' + str(int(bound_last_code.split('-')[-1])+1)
                 else:
-                    data['bound_batch_order'] = int(order_day.split('-')[-1])*100 +1
-                    data['bound_number'] = data['goods_code'] + order_day + '01'
+                    data['bound_batch_order'] = int(order_day.split('-')[-1])*1000 +1
+                    data['bound_number'] = data['goods_code'] + order_day + '001'
             else:
                 data['bound_number'] = data['goods_code'] + '-' + str(data['bound_batch_order'])
             serializer = self.get_serializer(data=data)
@@ -572,3 +922,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)

+ 25 - 25
container/container_operate.py

@@ -105,7 +105,7 @@ class BatchOperator:
             logger
         )
         
-        # 更新批次状态
+        # 更新批次状态(新增数目)
         update_result = BatchStatusUpdater.update_status(
             bound_obj, 
             batch['goods_qty'] * batch['goods_group'],
@@ -116,8 +116,8 @@ class BatchOperator:
         RecordCreator.create_related_records(
             data=data,
             bound_obj=bound_obj,
+            batch_data= batch,
             qty_diff=update_result['added_qty'],
-            expected_qty=update_result['expected_qty'],
             logger=logger
         )
 
@@ -145,8 +145,7 @@ class BatchStatusUpdater:
         bound_obj.save()
         return {
             'last_qty': last_qty,
-            'added_qty': added_qty,
-            'expected_qty': bound_obj.goods_qty
+            'added_qty': added_qty
         }
     @staticmethod
     def update_container_status(container_obj, status, container_code):
@@ -158,8 +157,7 @@ class BatchStatusUpdater:
         container_obj.save()
         return {
             'last_qty': 1,
-            'added_qty': 1,
-            'expected_qty': 1
+            'added_qty': 1
         }
   
 
@@ -201,7 +199,7 @@ class ListStatusUpdater:
 
 class RecordCreator:
     @staticmethod
-    def create_container_related_records(data, container_obj, qty_diff=1, expected_qty=1):
+    def create_container_related_records(data, container_obj, qty_diff=1):
         """记录创建批次"""
         # 生成明细记录
         detail_data = RecordBuilder.build_detail_data_container_group(
@@ -215,28 +213,30 @@ class RecordCreator:
         # 生成操作记录
         operation_data = RecordBuilder.build_operation_data_container_group(
             detail_data=detail_data,
-            qty_diff=qty_diff,
-            expected_qty=expected_qty
+            qty_diff=qty_diff
         )
 
         OperationRecordCreator.create_container_related_operation_records(operation_data)
 
     @staticmethod
-    def create_related_records(data, bound_obj, qty_diff, expected_qty, logger):
+    def create_related_records(data, bound_obj, batch_data, qty_diff, logger):
         """记录创建批次"""
         # 生成明细记录
-        detail_data = RecordBuilder.build_detail_data(
-            data=data,
-            bound_obj=bound_obj,
-            qty_diff=qty_diff
-        )
-        DetailRecordCreator.create_record(detail_data)
+
+        goods_group = batch_data.get('goods_group')
+        goods_qty = batch_data.get('goods_qty')
+        for i in range(goods_group):
+            detail_data = RecordBuilder.build_detail_data(
+                data=data,
+                bound_obj=bound_obj,
+                qty_diff=goods_qty
+            )
+            DetailRecordCreator.create_record(detail_data)
         
         # 生成操作记录
         operation_data = RecordBuilder.build_operation_data(
             detail_data=detail_data,
-            qty_diff=qty_diff,
-            expected_qty=expected_qty
+            qty_diff=qty_diff
         )
         OperationRecordCreator.create_operation_record(operation_data)
 
@@ -255,7 +255,7 @@ class RecordBuilder:
             "goods_weight": bound_obj.goods_weight,
             "status": 1,
             "month": data['month'],
-            "creater": data.get('creater', 'zl')
+            "creater": data.get('creater', 'wms')
         }
     
     @staticmethod
@@ -269,13 +269,13 @@ class RecordBuilder:
             "goods_weight": 0,
             "status": 1,
             "month": data['month'],
-            "creater": data.get('creater', 'zl')
+            "creater": data.get('creater', 'wms')
         }
 
     @staticmethod
-    def build_operation_data(detail_data, qty_diff, expected_qty):
+    def build_operation_data(detail_data, qty_diff):
         """构建操作记录数据"""
-        note_type = "(数量不一致)" if qty_diff != expected_qty else ""
+
         return {
             "month": detail_data['month'],
             "container": detail_data['container'],
@@ -284,15 +284,15 @@ class RecordBuilder:
             "goods_code": detail_data['goods_code'],
             "goods_desc": detail_data['goods_desc'],
             "goods_qty": qty_diff,
-            "goods_weight": detail_data['goods_weight'],
+            "goods_weight": qty_diff,
             "operator": detail_data['creater'],
             "timestamp": timezone.now(),
             "from_location": "container",
             "to_location": "container",
-            "memo": f"每组数目{detail_data['goods_qty']}入库PDA组盘{note_type}{detail_data['goods_code']}数量{qty_diff}"
+            "memo": f"每组{detail_data['goods_code']}数目{detail_data['goods_qty']},PDA组盘共计组盘{qty_diff}"
         }
     
-    def build_operation_data_container_group(detail_data, qty_diff, expected_qty):
+    def build_operation_data_container_group(detail_data, qty_diff):
         """构建操作记录数据"""
 
         return {

+ 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'],  
-            }
+            }
+        

+ 30 - 0
container/migrations/0016_containerwcsmodel_batch_number_and_more.py

@@ -0,0 +1,30 @@
+# Generated by Django 4.1.2 on 2025-05-15 23:42
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bound', '0012_boundbatchmodel_goods_package'),
+        ('container', '0015_containerlistmodel_available'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='containerwcsmodel',
+            name='batch_number',
+            field=models.CharField(blank=True, max_length=50, null=True, verbose_name='批次号'),
+        ),
+        migrations.AlterField(
+            model_name='containerwcsmodel',
+            name='batch',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='bound.boundbatchmodel', verbose_name='关联批次'),
+        ),
+        migrations.AlterField(
+            model_name='containerwcsmodel',
+            name='bound_list',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='bound.boundlistmodel', verbose_name='关联出库单'),
+        ),
+    ]

+ 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='货品类别'),
+        ),
+    ]

+ 17 - 0
container/migrations/0018_alter_containerwcsmodel_options.py

@@ -0,0 +1,17 @@
+# Generated by Django 4.1.2 on 2025-05-18 11:22
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0017_alter_containerdetailmodel_goods_class'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='containerwcsmodel',
+            options={'ordering': ['-create_time'], 'verbose_name': 'ContainerWCS', 'verbose_name_plural': 'ContainerWCS'},
+        ),
+    ]

+ 6 - 5
container/models.py

@@ -37,8 +37,8 @@ class ContainerDetailModel(models.Model):
     )
     BATCH_CLASS = (
         (1, '成品'),
-        (2, '设备'),
-        (3, '成品'),
+        (2, '空盘'),
+        (3, '散盘'),
     )
     month = models.IntegerField(verbose_name='月份')
     container = models.ForeignKey(ContainerListModel, on_delete=models.CASCADE, related_name='details')
@@ -107,9 +107,10 @@ class ContainerWCSModel(models.Model):
         (400, '失败'),
     )
     taskid = models.CharField(max_length=50, verbose_name='任务ID')
-    batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='关联批次')
+    batch = models.ForeignKey(BoundBatchModel, on_delete=models.CASCADE, verbose_name='关联批次',null=True, blank=True )
     batch_out = models.ForeignKey(OutBatchModel, on_delete=models.CASCADE, verbose_name='出库批次' , null=True, blank=True )
-    bound_list = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, verbose_name='关联出库单')
+    bound_list = models.ForeignKey(BoundListModel, on_delete=models.CASCADE, verbose_name='关联出库单',null=True, blank=True )
+    batch_number = models.CharField(max_length=50, verbose_name='批次号',null=True, blank=True )
     sequence = models.BigIntegerField(verbose_name='任务顺序')
     priority = models.IntegerField(default=100, verbose_name='优先级')
     month = models.IntegerField(verbose_name='月份')
@@ -130,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 {

+ 9 - 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)
@@ -43,6 +51,7 @@ class ContainerDetailGetSerializer(serializers.ModelSerializer):
     goods_desc = serializers.CharField(read_only=True, required=False)
     goods_qty = serializers.IntegerField(read_only=True, required=False)
     goods_weight = serializers.DecimalField(read_only=True, required=False, max_digits=10, decimal_places=2)
+    goods_class = serializers.IntegerField(read_only=True, required=False)
     status = serializers.IntegerField(read_only=True, required=False)
     creater = serializers.CharField(read_only=True, required=False)
     create_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M', 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',

+ 301 - 130
container/views.py

@@ -2,10 +2,11 @@ from wsgiref import headers
 from rest_framework.views import APIView
 from rest_framework import viewsets
 from utils.page import MyPageNumberPagination
-
+from django.db.models import Prefetch 
 from rest_framework.filters import OrderingFilter
 from django_filters.rest_framework import DjangoFilterBackend
 from rest_framework.response import Response
+from django.db.models import F, Case, When
 
 from django.utils import timezone
 import requests
@@ -14,20 +15,23 @@ from django.db import transaction
 import logging
 from rest_framework import status
 from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,ContainerWCSModel,TaskModel
-from bound.models import BoundBatchModel,BoundDetailModel,BoundListModel,OutBoundDetailModel
+from bound.models import BoundDetailModel,BoundListModel,OutBoundDetailModel
 from bin.views import LocationAllocation,base_location
 from bin.models import LocationModel,LocationContainerLink,LocationGroupModel
-from bound.models import BoundBatchModel
+from bound.models import BoundBatchModel,OutBatchModel,BatchLogModel
 
 from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSerializer
 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
 from django.db import close_old_connections
+from bin.services import AllocationService
+
 logger = logging.getLogger(__name__)
 class ContainerListViewSet(viewsets.ModelViewSet):
     """
@@ -105,6 +109,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:
@@ -309,84 +358,24 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
                         'data': current_task.to_dict()
                     }
                 else:
-                     # 库位分配
-                    container_code = container
-                    print(f"开始生成库位,托盘编码:{container_code}")
-                    allocator = LocationAllocation()  # 创建实例
-
-                    location_list_cnumber = allocator.get_location_by_status(container_code, current_location)  # 获取库位列表
-                    if not location_list_cnumber:
-                        print("❌ 通用库位获取失败,请检查托盘编码")
-                        return
-                    print(f"[1]库位:{location_list_cnumber}")
-                    
-                    update_location_status = allocator.update_location_status(location_list_cnumber.location_code, 'reserved')  # 更新库位状态
-                    if not update_location_status:
-                        print("❌ 库位状态更新失败,请检查托盘编码")
-                        return
-                    print(f"[2]发送任务,库位状态更新成功!")
-                    update_location_group_status = allocator.update_location_group_status(location_list_cnumber.location_code)  # 更新库位组状态
-                    if not update_location_group_status:
-                        print("❌ 库位组状态更新失败,请检查托盘编码")
-                        return
-                    print(f"[3]库位组状态更新成功!")
-
-                    update_batch_status = allocator.update_batch_status(container_code, '2')  # 更新批次状态
-                    if not update_batch_status:
-                        print("❌ 批次状态更新失败,请检查批次号")
-                        return
-                    print(f"[4]批次状态更新成功!")
-
-                    update_location_group_batch = allocator.update_location_group_batch(location_list_cnumber, container_code)  # 更新库位组的批次
-                    if not update_location_group_batch:
-                        print("❌ 库位组批次更新失败,请检查托盘编码")
-                        return
-                    print(f"[5]库位组批次更新成功!")
-
-                    update_location_container_link = allocator.update_location_container_link(location_list_cnumber.location_code, container_code)  # 更新库位和托盘的关联关系
-                    if not update_location_container_link:
-                        print("❌ 库位和托盘的关联关系更新失败,请检查托盘编码")
-                        return
-                    print(f"[7]库位和托盘的关联关系更新成功!")
-
-                    update_location_container_detail = allocator.update_container_detail_status(container_code,2)  # 更新库位和托盘的关联关系
-                    if not update_location_container_detail:
-                        print("❌ 库位和托盘的关联关系更新失败,请检查托盘编码")
-                        return
-                    print(f"[8]托盘的关联关系更新成功!")
-                    
+                    # todo: 这里的入库操作记录里面的记录的数量不对
+                    location_min_value,allocation_target_location, batch_info = AllocationService.allocate(container, current_location)
+                    batch_id = batch_info['number']
+                    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)
 
-                    allocation_target_location = (
-                            location_list_cnumber.warehouse_code + '-' 
-                            + f"{int(location_list_cnumber.row):02d}" + '-'  
-                            + f"{int(location_list_cnumber.col):02d}" + '-' 
-                            + f"{int(location_list_cnumber.layer):02d}" 
-                        )
-                    batch_id = allocator.get_batch(container_code)
-                    self.generate_task(container, current_location, allocation_target_location,batch_id,location_list_cnumber.c_number)  # 生成任务
                     current_task = ContainerWCSModel.objects.get(
                         container=container, 
                         tasktype='inbound',
                         working=1,
                     )
-                    batch_obj = BoundBatchModel.objects.filter(bound_number=batch_id).first()
-                    ContainerOperationModel.objects.create(
-                    month = int(timezone.now().strftime("%Y%m")),
-                    container = container_obj,
-                    goods_code = batch_obj.goods_code,
-                    goods_desc = batch_obj.goods_desc,
-                    operation_type ="inbound",
-                    batch_id = batch_obj.id,
-                    
-                    goods_qty = batch_obj.goods_qty,
-                    goods_weight = batch_obj.goods_qty,
-                    from_location = container_obj.current_location,
-                    to_location= allocation_target_location,
-                    timestamp=timezone.now(),
-                    operator="WMS",
-                    memo=f"WCS入库: 批次: {batch_id}, 数量: {batch_obj.goods_qty}"
-                    )
-
                     data_return = {
                         'code': '200',
                         'message': '任务下发成功',
@@ -394,7 +383,8 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
                     }
                     container_obj.target_location = allocation_target_location
                     container_obj.save()
-                    self.inport_update_task(current_task.id, container_obj.id)
+                    if batch_info['class'] == 1 or batch_info['class'] == 3:
+                        self.inport_update_task(current_task.id, container_obj.id)
 
 
             http_status = status.HTTP_200_OK if data_return['code'] == '200' else status.HTTP_400_BAD_REQUEST
@@ -408,6 +398,42 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
             )
 
     @transaction.atomic
+    def generate_container_operate(self, container_obj, bound_number,allocation_target_location):
+        batch_obj = BoundBatchModel.objects.filter(bound_number=bound_number).first()
+        ContainerOperationModel.objects.create(
+            month = int(timezone.now().strftime("%Y%m")),
+            container = container_obj,
+            goods_code = batch_obj.goods_code,
+            goods_desc = batch_obj.goods_desc,
+            operation_type ="inbound",
+            batch_id = batch_obj.id,
+            
+            goods_qty = batch_obj.goods_qty,
+            goods_weight = batch_obj.goods_qty,
+            from_location = container_obj.current_location,
+            to_location= allocation_target_location,
+            timestamp=timezone.now(),
+            operator="WMS",
+            memo=f"WCS入库: 批次: {bound_number}, 数量: {batch_obj.goods_qty}"
+        )
+    @transaction.atomic
+    def generate_container_operate_no_batch(self, container_obj, bound_number,allocation_target_location):
+       
+        ContainerOperationModel.objects.create(
+            month = int(timezone.now().strftime("%Y%m")),
+            container = container_obj,
+            goods_code = 'container',
+            goods_desc = '托盘组',
+            operation_type ="inbound",
+            goods_qty = 1,
+            goods_weight = 0,
+            from_location = container_obj.current_location,
+            to_location= allocation_target_location,
+            timestamp=timezone.now(),
+            memo=f"WCS入库: 批次: {bound_number}, 数量: 1"
+
+        )
+    @transaction.atomic
     def generate_task(self, container, current_location, target_location,batch_id,location_c_number):
         batch = BoundBatchModel.objects.filter(bound_number=batch_id).first()
         batch_detail = BoundDetailModel.objects.filter(bound_batch=batch).first()
@@ -417,6 +443,7 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
         data_tosave = {
             'container': container,
             'batch': batch,
+            'batch_number': batch_id,
             'batch_out': None,
             'bound_list': batch_detail.bound_list,
             'sequence': 1,
@@ -448,8 +475,46 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
         # 每月生成唯一递增的 taskNumber
         data_tosave['tasknumber'] = number_id
         ContainerWCSModel.objects.create(**data_tosave)
-        
 
+    def generate_task_no_batch(self, container, current_location, target_location,batch_id,location_c_number):
+    
+ 
+        data_tosave = {
+            'container': container,
+            'batch': None,
+            'batch_number': batch_id,
+            'batch_out': None,
+            'bound_list': None,
+            'sequence': 1,
+            'order_number' :location_c_number,
+            'priority': 1,
+            'current_location': current_location,
+            'month': timezone.now().strftime('%Y%m'),
+            'target_location': target_location,  
+            'tasktype': 'inbound',
+            'status': 103,
+            'is_delete': False
+        }
+
+        # 生成唯一递增的 taskid
+        last_task = ContainerWCSModel.objects.filter(
+            month=data_tosave['month'],
+        ).order_by('-tasknumber').first()
+
+        if last_task:
+            
+            number_id = last_task.tasknumber + 1
+            new_id = f"{number_id:05d}"
+        else:
+            new_id = "00001"
+            number_id = f"{data_tosave['month']}{new_id}"
+        
+        data_tosave['taskid'] = f"inbound-{data_tosave['month']}-{new_id}"
+        logger.info(f"生成入库任务: {data_tosave['taskid']}")
+        # 每月生成唯一递增的 taskNumber
+        data_tosave['tasknumber'] = number_id
+        ContainerWCSModel.objects.create(**data_tosave)
+        
     def update_container_wcs(self, request, *args, **kwargs):
         data = self.request.data
         logger.info(f"请求托盘:{data.get('container_number')}, 请求位置:{data.get('current_location')}, 任务号:{data.get('taskNumber')}")
@@ -475,7 +540,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': '任务已存在,重新下发',
@@ -517,6 +581,8 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
 
     def is_already_at_target(self, container_obj, current_location):
         """检查是否已在目标位置"""
+        print (current_location)
+        print (str(container_obj.target_location))
         return current_location == str(container_obj.target_location)
 
     def handle_target_reached(self, container_obj, data):
@@ -524,6 +590,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},
@@ -581,7 +650,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'),
@@ -1122,21 +1191,34 @@ class OutTaskViewSet(APIView):
             
             # 假设从请求中获取 bound_list_id
             bound_list_id = data.get('bound_list_id')
-            batch_count = self.get_batch_count_by_boundlist(bound_list_id)
+            # 获取关联的出库批次
+            out_batches = OutBatchModel.objects.filter(
+                bound_list_id=bound_list_id,
+                is_delete=False
+            ).select_related('batch_number')
+            
+            if not out_batches.exists():
+                return Response({"code": "404", "msg": "未找到相关出库批次"}, status=404)
+             # 构建批次需求字典
+            batch_demand = {
+                ob.batch_number_id: {
+                    'required': ob.goods_out_qty,
+                    'allocated': ob.goods_qty,
+                    'remaining': ob.goods_out_qty 
+                } for ob in out_batches
+            }
+            
+            # 生成出库任务
+            generate_result = self.generate_location_by_demand(
+                batch_demand=batch_demand,
+                bound_list_id=bound_list_id
+            )
             
-            logger.info(f"出库批次数量: {batch_count}")
-            # 获取需要出库的托盘列表
-            generate_result = self.generate_location_by_demand(batch_count,bound_list_id)
             if generate_result['code'] != '200':
-                current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id).first()
-                if current_WCS:
-                    OutboundService.process_next_task()
-                    return Response({"code": "200", "msg": "Success 再次发送任务"}, status=200)
-
                 return Response(generate_result, status=500)
-            container_list = generate_result['data']
             
-            logger.info(f"生成出库任务: {container_list}")
+            # 创建并处理出库任务
+            container_list = generate_result['data']
 
             #  2. 生成初始任务
             OutboundService.create_initial_tasks(container_list,bound_list_id)
@@ -1222,51 +1304,140 @@ class OutTaskViewSet(APIView):
             logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
             return {}
 
-    def generate_location_by_demand(self,demand_list,bound_list_id):
-        # demand_list {1: 25, 2: 17}
+    def get_container_allocation(self, batch_id):
+        """兼容所有数据库的去重方案"""
+        # 获取唯一容器ID列表
+        container_ids = (
+            ContainerDetailModel.objects
+            .filter(batch_id=batch_id, status=2)
+            .values_list('container_id', flat=True)
+            .distinct()
+        )
+
+        # 获取每个容器的最新明细(按id倒序)
+        return (
+            ContainerDetailModel.objects
+            .filter(container_id__in=container_ids, status=2)
+            .select_related('container')
+            .prefetch_related(
+                Prefetch('container__location_links', 
+                        queryset=LocationContainerLink.objects.select_related('location'),
+                        to_attr='active_location')
+            )
+            .order_by('container_id', '-id')
+        )
+        
+    def generate_location_by_demand(self, batch_demand, bound_list_id):
         try:
-            return_location =[]
-            for demand_id, demand_qty in demand_list.items():
-                container_list = self.get_location_by_status_and_batch(2, demand_id)
-                if not container_list:
-
-                    return {"code": "500", "msg": f"批次 {demand_id} 不存在"}
-                container_id_list = container_list.keys()
-                container_order = self.get_order_by_batch(container_id_list,demand_id) 
-                if not container_order:
-                    return {"code": "500", "msg": f"托盘 {container_id_list} 不存在"}
-                order = sorted(
-                        container_order.values(),
-                        key=lambda x: (
-                            int(x['location_type'][-1]),  # 提取最后一位数字并转为整数
-                            -x['location_c_number']       # 按location_c_number降序
-                        )
+            return_data = []
+            
+            for batch_id, demand in batch_demand.items():
+                # 获取已去重的容器列表
+                container_qs = self.get_container_allocation(batch_id)
+                
+                # 构建容器信息字典(自动去重)
+                container_map = {}
+                for cd in container_qs:
+                    if cd.container_id in container_map:
+                        continue  # 跳过已处理容器
+                    
+                    # 获取有效库位信息
+                    active_location = next(
+                        (link.location for link in cd.container.active_location 
+                        if link.is_active),
+                        None
                     )
-                current_qty = 0
-                for container in order:
-   
-                    container_detail_obj = ContainerDetailModel.objects.filter(container_id=container['container_number'],batch_id=demand_id,status=2).all()
-                    container_obj = ContainerListModel.objects.filter(id=container['container_number']).first()
-                    if not container_obj:
-                        return {"code": "500", "msg": f"托盘 {container['container_number']} 不存在"}
-                    if not container_detail_obj:
-                        return {"code": "500", "msg": f"托盘上无该批次,请检查{container['container_number']} 不存在"}
-                    goods_qty = 0 
-                    for obj in container_detail_obj:
-                        goods_qty += obj.goods_qty
-                    if current_qty < demand_qty:
-                        now_qty = current_qty
-                        current_qty += goods_qty
-                        return_location.append(container)
-                        logger.info(f"批次 {demand_id} 托盘 {container['container_number']} 当前数量 {current_qty}")
-                        self.create_or_update_container_operation(container_obj,demand_id,bound_list_id,203,min(demand_qty-now_qty,goods_qty),min(demand_qty-now_qty,goods_qty))
-                        self.update_container_detail_out_qty(container_obj,demand_id)
-                    else:
+                    
+                    container_map[cd.container_id] = {
+                        'detail': cd,
+                        'container': cd.container,
+                        'location': active_location
+                    }
+
+                # 转换为排序列表
+                container_list = list(container_map.values())
+                
+                # 多维度排序(优化性能版)
+                sorted_containers = sorted(
+                    container_list,
+                    key=lambda x: (
+                        self._get_goods_class_priority(x['detail'].goods_class),
+                        -(x['location'].c_number if x['location'] else 0),
+                        x['location'].warehouse_code if x['location'] else '',
+                        -(x['location'].layer if x['location'] else 0),
+                        x['location'].row if x['location'] else 0,
+                        x['location'].col if x['location'] else 0
+                    )
+                )
+                
+                # 分配逻辑
+                remaining = demand['remaining']
+                for item in sorted_containers:
+                    if remaining <= 0:
                         break
-            return {"code": "200", "msg": "Success", "data": return_location}
+                    
+                    # 获取可分配数量
+                    allocatable = item['detail'].goods_qty - item['detail'].goods_out_qty
+                    allocate_qty = min(remaining, allocatable)
+                    
+                    # 记录分配信息
+                    return_data.append({
+                        "container_number": item['container'].id,
+                        "batch_id": batch_id,
+                        "location_code": item['location'].location_code if item['location'] else 'N/A',
+                        "allocate_qty": allocate_qty,
+                        "c_number": item['location'].c_number if item['location'] else 0
+                    })
+                    
+                    remaining -= allocate_qty
+                    # 更新数据库状态(需要事务处理)
+                    self._update_allocation_status(item, allocate_qty)
+                    
+            return {"code": "200", "msg": "Success", "data": return_data}
+            
         except Exception as e:
+            logger.error(f"出库任务生成失败: {str(e)}", exc_info=True)
             return {"code": "500", "msg": str(e)}
+    
+    def _get_goods_class_priority(self, goods_class):
+        """货物类型优先级权重"""
+        return {
+            3: 0,  # 散盘最高
+            1: 1,  # 成品次之
+            2: 2   # 空盘最低
+        }.get(goods_class, 99)
 
+    @transaction.atomic
+    def _update_allocation_status(self, item, allocate_qty):
+        """事务化更新分配状态"""
+        try:
+            # 更新容器明细
+            ContainerDetailModel.objects.filter(
+                id=item['detail'].id
+            ).update(
+                goods_out_qty=F('goods_out_qty') + allocate_qty,
+                status=Case(
+                    When(goods_qty=F('goods_out_qty') + allocate_qty, then=3),
+                    default=2
+                )
+            )
+            
+            # 更新库位状态
+            if item['location']:
+                LocationModel.objects.filter(
+                    id=item['location'].id
+                ).update(
+                    current_quantity=F('current_quantity') - allocate_qty,
+                    status=Case(
+                        When(current_quantity=F('current_quantity') - allocate_qty, then='available'),
+                        default='occupied'
+                    )
+                )
+                
+            return True
+        except Exception as e:
+            logger.error(f"状态更新失败: {str(e)}")
+            return False
     def create_or_update_container_operation(self,container_obj,batch_id,bound_id,to_location,goods_qty,goods_weight):
         try:
             container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,bound_id=bound_id,operation_type="outbound").first()

+ 192 - 192
data_base/product.csv

@@ -1,192 +1,192 @@
-id,product_code,product_name,product_std,product_unit,creater,is_delete
-1,D/DA06,地奥司明,STP,kg,first,0
-2,DAP01,地奥司明,STP,kg,first,0
-3,DAP02,地奥司明,STP,kg,first,0
-4,DAP04,地奥司明,STP,kg,first,0
-5,DAP05,地奥司明,STP,kg,first,0
-6,DAP06,地奥司明,STP,kg,first,0
-7,DAP08,柑橘黄酮,STP,kg,first,0
-8,DAP09,地奥司明,STP,kg,first,0
-9,DAP10,地奥司明,STP,kg,first,0
-10,DAP11,地奥司明,STP,kg,first,0
-11,DAP08,柑橘黄酮,STP,kg,first,0
-12,DBP08,柑橘黄酮,STP,kg,first,0
-13,DBP01,地奥司明微粉,STP,kg,first,0
-14,DBP04,地奥司明微粉,STP,kg,first,0
-15,DBP05,地奥司明微粉,STP,kg,first,0
-16,DBP06,地奥司明微粉,STP,kg,first,0
-17,DBP09,地奥司明微粉,STP,kg,first,0
-18,DBP10,地奥司明微粉,STP,kg,first,0
-19,DBP11,地奥司明微粉,STP,kg,first,0
-20,DBP12,地奥司明微粉,STP,kg,first,0
-21,DFF03,地奥司明A/SG,STP,kg,first,0
-22,DBP07,MPFF,STP,kg,first,0
-23,DAF01,地奥司明,STP,kg,first,0
-24,DAF02,地奥司明,STP,kg,first,0
-25,DAF03,地奥司明,STP,kg,first,0
-26,DAF04,地奥司明,STP,kg,first,0
-27,DAF05,地奥司明,STP,kg,first,0
-28,DBF01,地奥司明微粉,STP,kg,first,0
-29,DBF02,地奥司明微粉,STP,kg,first,0
-30,DBF03,地奥司明微粉,STP,kg,first,0
-31,DBF04,地奥司明微粉,STP,kg,first,0
-32,DBF05,地奥司明微粉,STP,kg,first,0
-33,D/RAF01,芦丁,STP,kg,first,0
-34,D/RCF01,芦丁,STP,kg,first,0
-35,D/RDF01,芦丁,STP,kg,first,0
-36,D/RDF02,芦丁,STP,kg,first,0
-37,D/RF02,芦丁,STP,kg,first,0
-38,D/RDP01,芦丁,STP,kg,first,0
-39,D/RI01,芦丁(S),STP,kg,first,0
-40,RDP01,芦丁(S),STP,kg,first,0
-41,RDP02,芦丁(S),STP,kg,first,0
-42,RF02,芦丁A,STP,kg,first,0
-43,RF04,芦丁E,STP,kg,first,0
-44,D/TAF01,曲克芦丁,STP,kg,first,0
-45,D/UAF01,L-鼠李糖,STP,kg,first,0
-46,DHF02,香叶木素,STP,kg,first,0
-47,DHF02,香叶木素,STP,kg,first,0
-48,D/NAF01,新橙皮苷,STP,kg,first,0
-49,D/NCF01,柚苷,STP,kg,first,0
-50,D/NDF01,柚皮苷二氢查耳酮,STP,kg,first,0
-51,CCF01,鹰嘴豆蛋白,STP,kg,first,0
-52,PBF01,根皮素,STP,kg,first,0
-53,PAF01,普鲁宁,STP,kg,first,0
-54,EG01,蛋黄粉(脱脂),STP,kg,first,0
-55,GAF01,染料木素,STP,kg,first,0
-56,HDF01,橙皮素,STP,kg,first,0
-57,GBF01,葡糖基芦丁(AGR),STP,kg,first,0
-58,D/EG02,(鸡)蛋黄粉,STP,kg,first,0
-59,EG02,(鸡)蛋黄粉,STP,kg,first,0
-60,RHF01,白藜芦醇,STP,kg,first,0
-61,LBF01,左旋多巴,STP,kg,first,0
-62,BDF01,苦荞黄酮,STP,kg,first,0
-63,SBF01,甜橙皮提取物,STP,kg,first,0
-64,D/SBF01,甜橙皮提取物,STP,kg,first,0
-65,LCF01,圣草次苷,STP,kg,first,0
-66,AAP01,青蒿素,STP,kg,first,0
-67,ABP01,双氢青蒿素,STP,kg,first,0
-68,ADP01,蒿甲醚,STP,kg,first,0
-69,ACP01,青蒿琥酯,STP,kg,first,0
-70,DAF04,地奥司明,F-STP,kg,first,0
-71,DCF02,地奥司明:橙皮苷(90:10),F-STP,kg,first,0
-72,DCF03,地奥司明:橙皮苷(90:10),F-STP,kg,first,0
-73,DCF04,地奥司明:橙皮苷(90:10),F-STP,kg,first,0
-74,DCF06,地奥司明:橙皮苷(90:10),F-STP,kg,first,0
-75,DDF02,地奥司明:橙皮苷(90:10)微粉,F-STP,kg,first,0
-76,DDF03,地奥司明:橙皮苷(90:10)微粉,F-STP,kg,first,0
-77,DGF01,地奥司明:橙皮苷(90:10)颗粒,F-STP,kg,first,0
-78,LDF01,柠檬黄酮,F-STP,kg,first,0
-79,QHF01,二氢槲皮素,F-STP,kg,first,0
-80,QM02,芦丁水解中和浓缩液,F-STP,kg,first,0
-81,GBF01,葡萄柚黄酮,F-STP,kg,first,0
-82,GBF02,葡萄柚黄酮,F-STP,kg,first,0
-83,GBF03,葡萄柚黄酮,F-STP,kg,first,0
-84,BAF01,盐酸小檗碱,F-STP,kg,first,0
-85,BAF02,盐酸小檗碱,F-STP,kg,first,0
-86,BAF03,盐酸小檗碱,F-STP,kg,first,0
-87,BBF04,盐酸小檗碱颗粒,F-STP,kg,first,0
-88,BBF05,盐酸小檗碱颗粒,F-STP,kg,first,0
-89,BCF03,高密度盐酸小檗碱,F-STP,kg,first,0
-90,D/NBF05,新甲基橙皮苷二氢查耳酮,F-STP,kg,first,0
-91,NBF01,新甲基橙皮苷二氢查耳酮,F-STP,kg,first,0
-92,NBF05,新甲基橙皮苷二氢查耳酮,F-STP,kg,first,0
-93,D/QAF01,二水槲皮素,F-STP,kg,first,0
-94,D/QAF03,二水槲皮素,F-STP,kg,first,0
-95,D/QAF08,二水槲皮素,F-STP,kg,first,0
-96,D/QBF03,二水槲皮素颗粒,F-STP,kg,first,0
-97,D/QDF02,无水槲皮素,F-STP,kg,first,0
-98,D/QDF02,无水槲皮素(HM-02),F-STP,kg,first,0
-99,D/QDF03,无水槲皮素,F-STP,kg,first,0
-100,D/QDF04,无水槲皮素,F-STP,kg,first,0
-101,D/QEF01,无水槲皮素颗粒,F-STP,kg,first,0
-102,QAF01,二水槲皮素,F-STP,kg,first,0
-103,QAF03,二水槲皮素,F-STP,kg,first,0
-104,QAF05,二水槲皮素,F-STP,kg,first,0
-105,QAF08,二水槲皮素,F-STP,kg,first,0
-106,QBF03,二水槲皮素颗粒,F-STP,kg,first,0
-107,QCF02,二水高密度槲皮素,F-STP,kg,first,0
-108,QCF03,二水高密度槲皮素,F-STP,kg,first,0
-109,QCF05,二水高密度槲皮素,F-STP,kg,first,0
-110,QCF06,二水高密度槲皮素,F-STP,kg,first,0
-111,QDF02,无水槲皮素(HM-02),F-STP,kg,first,0
-112,QDF02,无水槲皮素,F-STP,kg,first,0
-113,QDF03,无水槲皮素,F-STP,kg,first,0
-114,QDF04,无水槲皮素,F-STP,kg,first,0
-115,QDF05,无水槲皮素,F-STP,kg,first,0
-116,QEF01,无水槲皮素颗粒,F-STP,kg,first,0
-117,QEF02,无水槲皮素颗粒,F-STP,kg,first,0
-118,QEF03,无水槲皮素颗粒,F-STP,kg,first,0
-119,QEF04,无水槲皮素颗粒,F-STP,kg,first,0
-120,QFF02,无水高密度槲皮素,F-STP,kg,first,0
-121,QFF04,无水高密度槲皮素,F-STP,kg,first,0
-122,QGF01,槐树花浸膏,F-STP,kg,first,0
-123,RGF01,白藜芦醇颗粒,F-STP,kg,first,0
-124,D/IAF01,异槲皮苷,F-STP,kg,first,0
-125,D/IAF02,异槲皮苷,F-STP,kg,first,0
-126,QHF01,二氢槲皮素,F-STP,kg,first,0
-127,IAF01,异槲皮苷,F-STP,kg,first,0
-128,IAF02,异槲皮苷,F-STP,kg,first,0
-129,HAF01,橙皮苷,F-STP,kg,first,0
-130,HAF02,橙皮苷,F-STP,kg,first,0
-131,HAF03,橙皮苷,F-STP,kg,first,0
-132,HAF04,橙皮苷,F-STP,kg,first,0
-133,HBF01,橙皮苷微粉,F-STP,kg,first,0
-134,HBF02,橙皮苷微粉,F-STP,kg,first,0
-135,HBF03,橙皮苷微粉,F-STP,kg,first,0
-136,HBP01,橙皮苷微粉,F-STP,kg,first,0
-137,HCF01,橙皮苷颗粒,F-STP,kg,first,0
-138,HCF02,橙皮苷颗粒,F-STP,kg,first,0
-139,HCF03,橙皮苷颗粒,F-STP,kg,first,0
-140,HAF01,橙皮苷,F-STP,kg,first,0
-141,CAF01,枳实黄酮,F-STP,kg,first,0
-142,CAF02,枳实黄酮,F-STP,kg,first,0
-143,CAF03,枳实黄酮,F-STP,kg,first,0
-144,CAF04,枳实黄酮,F-STP,kg,first,0
-145,CAF05,枳实黄酮,F-STP,kg,first,0
-146,CAF06,枳实黄酮,F-STP,kg,first,0
-147,CAF07,枳实黄酮,F-STP,kg,first,0
-148,CAF08,枳实黄酮,F-STP,kg,first,0
-149,CBF01,枳实黄酮颗粒,F-STP,kg,first,0
-150,CBF02,枳实黄酮颗粒,F-STP,kg,first,0
-151,CBF03,枳实黄酮颗粒,F-STP,kg,first,0
-152,RI01,芦丁(S),F-STP,kg,first,0
-153,RI02,芦丁(S),F-STP,kg,first,0
-154,TAF01,曲克芦丁,F-STP,kg,first,0
-155,TAF03,曲克芦丁,F-STP,kg,first,0
-156,TAF04,曲克芦丁,F-STP,kg,first,0
-157,TBF01,曲克芦丁,F-STP,kg,first,0
-158,RFF01,水溶性芦丁,F-STP,kg,first,0
-159,RAF01,芦丁,F-STP,kg,first,0
-160,RCF01,芦丁,F-STP,kg,first,0
-161,RDF01,芦丁,F-STP,kg,first,0
-162,RDF02,芦丁,F-STP,kg,first,0
-163,RDF03,芦丁,F-STP,kg,first,0
-164,RDF04,芦丁,F-STP,kg,first,0
-165,RDF05,芦丁,F-STP,kg,first,0
-166,REF01,芦丁颗粒,F-STP,kg,first,0
-167,NCF01,柚苷,F-STP,kg,first,0
-168,MBF01,橙皮苷甲基查耳酮,F-STP,kg,first,0
-169,HDF01,橙皮素,F-STP,kg,first,0
-170,NDF01,柚皮苷二氢查耳酮,F-STP,kg,first,0
-171,NGF01,柚皮素,F-STP,kg,first,0
-172,NAF01,新橙皮苷,F-STP,kg,first,0
-173,NAF02,新橙皮苷,F-STP,kg,first,0
-174,DHF01,香叶木素,F-STP,kg,first,0
-175,DHF02,香叶木素,F-STP,kg,first,0
-176,DFF01,地奥司明香叶木素颗粒,F-STP,kg,first,0
-177,DFF02,地奥司明香叶木素颗粒,F-STP,kg,first,0
-178,DEF01,地奥司明颗粒,F-STP,kg,first,0
-179,DEF02,地奥司明颗粒,F-STP,kg,first,0
-180,DEF03,地奥司明颗粒,F-STP,kg,first,0
-181,DEF04,地奥司明颗粒,F-STP,kg,first,0
-182,DFF03,地奥司明A/SG,F-STP,kg,first,0
-183,HFF01,柑橘提取物,F-STP,kg,first,0
-184,D/UAF01,L-鼠李糖,F-STP,kg,first,0
-185,UAF01,L-鼠李糖,F-STP,kg,first,0
-186,NFF01,柚苷颗粒,F-STP,kg,first,0
-187,LUF01,木犀草素,F-STP,kg,first,0
-188,GAF01,染料木素,F-STP,kg,first,0
-189,NGF01,柚皮苷,F-STP,kg,first,0
-190,LCF01,圣草次苷,F-STP,kg,first,0
-191,FAF01,漆黄素,F-STP,kg,first,0
+id,product_code,product_name,product_std,product_unit,product_package,creater,is_delete
+1,D/DA06,地奥司明,STP,kg,箱,first,0
+2,DAP01,地奥司明,STP,kg,桶,first,0
+3,DAP02,地奥司明,STP,kg,袋,first,0
+4,DAP04,地奥司明,STP,kg,箱,first,0
+5,DAP05,地奥司明,STP,kg,桶,first,0
+6,DAP06,地奥司明,STP,kg,袋,first,0
+7,DAP08,柑橘黄酮,STP,kg,箱,first,0
+8,DAP09,地奥司明,STP,kg,桶,first,0
+9,DAP10,地奥司明,STP,kg,袋,first,0
+10,DAP11,地奥司明,STP,kg,箱,first,0
+11,DAP08,柑橘黄酮,STP,kg,桶,first,0
+12,DBP08,柑橘黄酮,STP,kg,袋,first,0
+13,DBP01,地奥司明微粉,STP,kg,箱,first,0
+14,DBP04,地奥司明微粉,STP,kg,桶,first,0
+15,DBP05,地奥司明微粉,STP,kg,袋,first,0
+16,DBP06,地奥司明微粉,STP,kg,箱,first,0
+17,DBP09,地奥司明微粉,STP,kg,桶,first,0
+18,DBP10,地奥司明微粉,STP,kg,袋,first,0
+19,DBP11,地奥司明微粉,STP,kg,箱,first,0
+20,DBP12,地奥司明微粉,STP,kg,桶,first,0
+21,DFF03,地奥司明A/SG,STP,kg,袋,first,0
+22,DBP07,MPFF,STP,kg,箱,first,0
+23,DAF01,地奥司明,STP,kg,桶,first,0
+24,DAF02,地奥司明,STP,kg,袋,first,0
+25,DAF03,地奥司明,STP,kg,箱,first,0
+26,DAF04,地奥司明,STP,kg,桶,first,0
+27,DAF05,地奥司明,STP,kg,袋,first,0
+28,DBF01,地奥司明微粉,STP,kg,箱,first,0
+29,DBF02,地奥司明微粉,STP,kg,桶,first,0
+30,DBF03,地奥司明微粉,STP,kg,袋,first,0
+31,DBF04,地奥司明微粉,STP,kg,箱,first,0
+32,DBF05,地奥司明微粉,STP,kg,桶,first,0
+33,D/RAF01,芦丁,STP,kg,袋,first,0
+34,D/RCF01,芦丁,STP,kg,箱,first,0
+35,D/RDF01,芦丁,STP,kg,桶,first,0
+36,D/RDF02,芦丁,STP,kg,袋,first,0
+37,D/RF02,芦丁,STP,kg,箱,first,0
+38,D/RDP01,芦丁,STP,kg,桶,first,0
+39,D/RI01,芦丁(S),STP,kg,袋,first,0
+40,RDP01,芦丁(S),STP,kg,箱,first,0
+41,RDP02,芦丁(S),STP,kg,桶,first,0
+42,RF02,芦丁A,STP,kg,袋,first,0
+43,RF04,芦丁E,STP,kg,箱,first,0
+44,D/TAF01,曲克芦丁,STP,kg,桶,first,0
+45,D/UAF01,L-鼠李糖,STP,kg,袋,first,0
+46,DHF02,香叶木素,STP,kg,箱,first,0
+47,DHF02,香叶木素,STP,kg,桶,first,0
+48,D/NAF01,新橙皮苷,STP,kg,袋,first,0
+49,D/NCF01,柚苷,STP,kg,箱,first,0
+50,D/NDF01,柚皮苷二氢查耳酮,STP,kg,桶,first,0
+51,CCF01,鹰嘴豆蛋白,STP,kg,袋,first,0
+52,PBF01,根皮素,STP,kg,箱,first,0
+53,PAF01,普鲁宁,STP,kg,桶,first,0
+54,EG01,蛋黄粉(脱脂),STP,kg,袋,first,0
+55,GAF01,染料木素,STP,kg,箱,first,0
+56,HDF01,橙皮素,STP,kg,桶,first,0
+57,GBF01,葡糖基芦丁(AGR),STP,kg,袋,first,0
+58,D/EG02,(鸡)蛋黄粉,STP,kg,箱,first,0
+59,EG02,(鸡)蛋黄粉,STP,kg,桶,first,0
+60,RHF01,白藜芦醇,STP,kg,袋,first,0
+61,LBF01,左旋多巴,STP,kg,箱,first,0
+62,BDF01,苦荞黄酮,STP,kg,桶,first,0
+63,SBF01,甜橙皮提取物,STP,kg,袋,first,0
+64,D/SBF01,甜橙皮提取物,STP,kg,箱,first,0
+65,LCF01,圣草次苷,STP,kg,桶,first,0
+66,AAP01,青蒿素,STP,kg,袋,first,0
+67,ABP01,双氢青蒿素,STP,kg,箱,first,0
+68,ADP01,蒿甲醚,STP,kg,桶,first,0
+69,ACP01,青蒿琥酯,STP,kg,袋,first,0
+70,DAF04,地奥司明,F-STP,kg,箱,first,0
+71,DCF02,地奥司明:橙皮苷(90:10),F-STP,kg,桶,first,0
+72,DCF03,地奥司明:橙皮苷(90:10),F-STP,kg,袋,first,0
+73,DCF04,地奥司明:橙皮苷(90:10),F-STP,kg,箱,first,0
+74,DCF06,地奥司明:橙皮苷(90:10),F-STP,kg,桶,first,0
+75,DDF02,地奥司明:橙皮苷(90:10)微粉,F-STP,kg,袋,first,0
+76,DDF03,地奥司明:橙皮苷(90:10)微粉,F-STP,kg,箱,first,0
+77,DGF01,地奥司明:橙皮苷(90:10)颗粒,F-STP,kg,桶,first,0
+78,LDF01,柠檬黄酮,F-STP,kg,袋,first,0
+79,QHF01,二氢槲皮素,F-STP,kg,箱,first,0
+80,QM02,芦丁水解中和浓缩液,F-STP,kg,桶,first,0
+81,GBF01,葡萄柚黄酮,F-STP,kg,袋,first,0
+82,GBF02,葡萄柚黄酮,F-STP,kg,箱,first,0
+83,GBF03,葡萄柚黄酮,F-STP,kg,桶,first,0
+84,BAF01,盐酸小檗碱,F-STP,kg,袋,first,0
+85,BAF02,盐酸小檗碱,F-STP,kg,箱,first,0
+86,BAF03,盐酸小檗碱,F-STP,kg,桶,first,0
+87,BBF04,盐酸小檗碱颗粒,F-STP,kg,袋,first,0
+88,BBF05,盐酸小檗碱颗粒,F-STP,kg,箱,first,0
+89,BCF03,高密度盐酸小檗碱,F-STP,kg,桶,first,0
+90,D/NBF05,新甲基橙皮苷二氢查耳酮,F-STP,kg,袋,first,0
+91,NBF01,新甲基橙皮苷二氢查耳酮,F-STP,kg,箱,first,0
+92,NBF05,新甲基橙皮苷二氢查耳酮,F-STP,kg,桶,first,0
+93,D/QAF01,二水槲皮素,F-STP,kg,袋,first,0
+94,D/QAF03,二水槲皮素,F-STP,kg,箱,first,0
+95,D/QAF08,二水槲皮素,F-STP,kg,桶,first,0
+96,D/QBF03,二水槲皮素颗粒,F-STP,kg,袋,first,0
+97,D/QDF02,无水槲皮素,F-STP,kg,箱,first,0
+98,D/QDF02,无水槲皮素(HM-02),F-STP,kg,桶,first,0
+99,D/QDF03,无水槲皮素,F-STP,kg,袋,first,0
+100,D/QDF04,无水槲皮素,F-STP,kg,箱,first,0
+101,D/QEF01,无水槲皮素颗粒,F-STP,kg,桶,first,0
+102,QAF01,二水槲皮素,F-STP,kg,袋,first,0
+103,QAF03,二水槲皮素,F-STP,kg,箱,first,0
+104,QAF05,二水槲皮素,F-STP,kg,桶,first,0
+105,QAF08,二水槲皮素,F-STP,kg,袋,first,0
+106,QBF03,二水槲皮素颗粒,F-STP,kg,箱,first,0
+107,QCF02,二水高密度槲皮素,F-STP,kg,桶,first,0
+108,QCF03,二水高密度槲皮素,F-STP,kg,袋,first,0
+109,QCF05,二水高密度槲皮素,F-STP,kg,箱,first,0
+110,QCF06,二水高密度槲皮素,F-STP,kg,桶,first,0
+111,QDF02,无水槲皮素(HM-02),F-STP,kg,袋,first,0
+112,QDF02,无水槲皮素,F-STP,kg,箱,first,0
+113,QDF03,无水槲皮素,F-STP,kg,桶,first,0
+114,QDF04,无水槲皮素,F-STP,kg,袋,first,0
+115,QDF05,无水槲皮素,F-STP,kg,箱,first,0
+116,QEF01,无水槲皮素颗粒,F-STP,kg,桶,first,0
+117,QEF02,无水槲皮素颗粒,F-STP,kg,袋,first,0
+118,QEF03,无水槲皮素颗粒,F-STP,kg,箱,first,0
+119,QEF04,无水槲皮素颗粒,F-STP,kg,桶,first,0
+120,QFF02,无水高密度槲皮素,F-STP,kg,袋,first,0
+121,QFF04,无水高密度槲皮素,F-STP,kg,箱,first,0
+122,QGF01,槐树花浸膏,F-STP,kg,桶,first,0
+123,RGF01,白藜芦醇颗粒,F-STP,kg,袋,first,0
+124,D/IAF01,异槲皮苷,F-STP,kg,箱,first,0
+125,D/IAF02,异槲皮苷,F-STP,kg,桶,first,0
+126,QHF01,二氢槲皮素,F-STP,kg,袋,first,0
+127,IAF01,异槲皮苷,F-STP,kg,箱,first,0
+128,IAF02,异槲皮苷,F-STP,kg,桶,first,0
+129,HAF01,橙皮苷,F-STP,kg,袋,first,0
+130,HAF02,橙皮苷,F-STP,kg,箱,first,0
+131,HAF03,橙皮苷,F-STP,kg,桶,first,0
+132,HAF04,橙皮苷,F-STP,kg,袋,first,0
+133,HBF01,橙皮苷微粉,F-STP,kg,箱,first,0
+134,HBF02,橙皮苷微粉,F-STP,kg,桶,first,0
+135,HBF03,橙皮苷微粉,F-STP,kg,袋,first,0
+136,HBP01,橙皮苷微粉,F-STP,kg,箱,first,0
+137,HCF01,橙皮苷颗粒,F-STP,kg,桶,first,0
+138,HCF02,橙皮苷颗粒,F-STP,kg,袋,first,0
+139,HCF03,橙皮苷颗粒,F-STP,kg,箱,first,0
+140,HAF01,橙皮苷,F-STP,kg,桶,first,0
+141,CAF01,枳实黄酮,F-STP,kg,袋,first,0
+142,CAF02,枳实黄酮,F-STP,kg,箱,first,0
+143,CAF03,枳实黄酮,F-STP,kg,桶,first,0
+144,CAF04,枳实黄酮,F-STP,kg,袋,first,0
+145,CAF05,枳实黄酮,F-STP,kg,箱,first,0
+146,CAF06,枳实黄酮,F-STP,kg,桶,first,0
+147,CAF07,枳实黄酮,F-STP,kg,袋,first,0
+148,CAF08,枳实黄酮,F-STP,kg,箱,first,0
+149,CBF01,枳实黄酮颗粒,F-STP,kg,桶,first,0
+150,CBF02,枳实黄酮颗粒,F-STP,kg,袋,first,0
+151,CBF03,枳实黄酮颗粒,F-STP,kg,箱,first,0
+152,RI01,芦丁(S),F-STP,kg,桶,first,0
+153,RI02,芦丁(S),F-STP,kg,袋,first,0
+154,TAF01,曲克芦丁,F-STP,kg,箱,first,0
+155,TAF03,曲克芦丁,F-STP,kg,桶,first,0
+156,TAF04,曲克芦丁,F-STP,kg,袋,first,0
+157,TBF01,曲克芦丁,F-STP,kg,箱,first,0
+158,RFF01,水溶性芦丁,F-STP,kg,桶,first,0
+159,RAF01,芦丁,F-STP,kg,袋,first,0
+160,RCF01,芦丁,F-STP,kg,箱,first,0
+161,RDF01,芦丁,F-STP,kg,桶,first,0
+162,RDF02,芦丁,F-STP,kg,袋,first,0
+163,RDF03,芦丁,F-STP,kg,箱,first,0
+164,RDF04,芦丁,F-STP,kg,桶,first,0
+165,RDF05,芦丁,F-STP,kg,袋,first,0
+166,REF01,芦丁颗粒,F-STP,kg,箱,first,0
+167,NCF01,柚苷,F-STP,kg,桶,first,0
+168,MBF01,橙皮苷甲基查耳酮,F-STP,kg,袋,first,0
+169,HDF01,橙皮素,F-STP,kg,箱,first,0
+170,NDF01,柚皮苷二氢查耳酮,F-STP,kg,桶,first,0
+171,NGF01,柚皮素,F-STP,kg,袋,first,0
+172,NAF01,新橙皮苷,F-STP,kg,箱,first,0
+173,NAF02,新橙皮苷,F-STP,kg,桶,first,0
+174,DHF01,香叶木素,F-STP,kg,袋,first,0
+175,DHF02,香叶木素,F-STP,kg,箱,first,0
+176,DFF01,地奥司明香叶木素颗粒,F-STP,kg,桶,first,0
+177,DFF02,地奥司明香叶木素颗粒,F-STP,kg,袋,first,0
+178,DEF01,地奥司明颗粒,F-STP,kg,箱,first,0
+179,DEF02,地奥司明颗粒,F-STP,kg,桶,first,0
+180,DEF03,地奥司明颗粒,F-STP,kg,袋,first,0
+181,DEF04,地奥司明颗粒,F-STP,kg,箱,first,0
+182,DFF03,地奥司明A/SG,F-STP,kg,桶,first,0
+183,HFF01,柑橘提取物,F-STP,kg,袋,first,0
+184,D/UAF01,L-鼠李糖,F-STP,kg,箱,first,0
+185,UAF01,L-鼠李糖,F-STP,kg,桶,first,0
+186,NFF01,柚苷颗粒,F-STP,kg,袋,first,0
+187,LUF01,木犀草素,F-STP,kg,箱,first,0
+188,GAF01,染料木素,F-STP,kg,桶,first,0
+189,NGF01,柚皮苷,F-STP,kg,袋,first,0
+190,LCF01,圣草次苷,F-STP,kg,箱,first,0
+191,FAF01,漆黄素,F-STP,kg,桶,first,0

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 3046 - 0
logs/error.log


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 6705 - 0
logs/server.log


+ 1 - 0
templates/package.json

@@ -38,6 +38,7 @@
     "xmldom": "^0.6.0",
     "screenfull": "4.2.0",
     "yargs-parser": "^20.2.9",
+    "vuedraggable": "^2.24.3",
     "jsbarcode": "3.9.0"
   },
   "devDependencies": {

+ 116 - 0
templates/src/components/CustomDateInput.vue

@@ -0,0 +1,116 @@
+<template>
+  <div class="row q-gutter-xs">
+    <!-- 年 -->
+    <q-select
+      v-model="year"
+      :options="yearOptions"
+      label="年"
+      dense
+      emit-value
+      @update:model-value="handleChange"
+    />
+
+    <!-- 月 -->
+    <q-select
+      v-model="month"
+      :options="monthOptions"
+      label="月"
+      dense
+      emit-value
+      :disable="!year"
+      @update:model-value="handleChange"
+    />
+
+    <!-- 日 -->
+    <q-select
+      v-model="day"
+      :options="dayOptions"
+      label="日"
+      dense
+      emit-value
+      :disable="!month"
+      @update:model-value="handleChange"
+    />
+  </div>
+</template>
+
+<script>
+export default {
+  props: ['modelValue'],
+  emits: ['update:modelValue'],
+
+  data() {
+    return {
+      year: null,
+      month: null,
+      day: null
+    }
+  },
+
+  computed: {
+    yearOptions() {
+      const currentYear = new Date().getFullYear()
+      return Array.from({ length: 5 }, (_, i) => currentYear - i)
+    },
+
+    monthOptions() {
+      return Array.from({ length: 12 }, (_, i) => ({
+        label: `${i + 1}月`,
+        value: i + 1
+      }))
+    },
+
+    dayOptions() {
+      if (!this.year || !this.month) return []
+      const days = new Date(this.year, this.month, 0).getDate()
+      return Array.from({ length: days }, (_, i) => i + 1)
+    }
+  },
+
+  watch: {
+    modelValue: {
+      immediate: true,
+      handler(val) {
+        if (val) {
+          // 增强格式容错
+          const [y, m, d] = val.split('-').map(Number)
+          this.year = y || null
+          this.month = m || null
+          this.day = d || null
+        } else {
+          this.clear()
+        }
+      }
+    }
+  },
+
+  methods: {
+    handleChange() {
+      let dateStr = null
+      
+      // 允许部分选择
+      if (this.year) {
+        dateStr = `${this.year}`
+        if (this.month) {
+          dateStr += `-${this.pad(this.month)}`
+          if (this.day) {
+            dateStr += `-${this.pad(this.day)}`
+          }
+        }
+      }
+
+      this.$emit('update:modelValue', dateStr)
+    },
+
+    pad(num) {
+      return num.toString().padStart(2, '0')
+    },
+
+    clear() {
+      this.year = null
+      this.month = null
+      this.day = null
+    }
+  }
+}
+</script>

+ 181 - 195
templates/src/components/goodscard.vue

@@ -1,6 +1,5 @@
 <template>
     <div  :style="{ backgroundColor: bgColor }">
- 
 
         <q-dialog v-model="storage_dialog" transition-show="jump-down" transition-hide="jump-up" @show=prepareDialog()>
             <q-card style="min-width: 600px; ">
@@ -27,14 +26,14 @@
                     <q-tab-panel name="tab1" style="height:300px">
                         <div class="row q-gutter-x-md">
                             <div class="col column q-gutter-y-md">
-                            
+
                                 <q-input dense outlined square v-model="storage_form.goods_code"
                                     :label="$t('stock.shelf.goods_code')" :readonly="onlyread"
                                     :rules="[val => val?.length > 0 || error1]" />
-                    
+
                             </div>
                             <div class="col column q-gutter-y-md">
-  
+
                                 <q-input dense outlined square v-model="storage_form.goods_name"
                                     :label="$t('stock.shelf.goods_name')" :readonly="onlyread"
                                     :rules="[val => val?.length > 0 || error1]" />
@@ -78,8 +77,6 @@
                                     :label="$t('stock.shelf.goods_unit')" :readonly="onlyread"
                                     :rules="[val => val?.length > 0 || error1]" />
 
-
-
                             </div>
                         </div>
                     </q-tab-panel>
@@ -224,214 +221,204 @@
 <script>
 
 
-import { LocalStorage } from 'quasar'
-import { getauth, postauth, putauth, deleteauth, getfile } from 'boot/axios_request'
+import { getauth } from 'boot/axios_request'
 
+export default {
+  props: {
+
+    rowIndex: Number,
+    colIndex: Number,
+    layerIndex: Number,
+    selectedShelfType: String,
+    goodsData: Object
+
+  },
+  data () {
+    return {
+      pathname: 'bin/',
+      pathnamecontainer: 'container/detail/',
+      container_id: 123456,
+      results: [],
+      storage_form: {
+        shelf_name: '',
+        shelf_department: '',
+        shelf_warehouse: '',
+        shelf_status: '',
+        goods_code: '',
+        goods_name: '',
+        goods_std: '',
+        goods_desc: '',
+        goods_qty: '',
+        goods_unit: '',
+        goods_price: '',
+        goods_batch: '',
+        goods_notes: '',
+        goods_in: '',
+        goods_out: ''
+      },
+      elevator_form: {
+        ip: '',
+        port: 8080,
+        // username: 'admin',
+        // password: '123456',
+        status: '未连接'
+      },
+      conveyor_form: {
+        ip: '',
+        port: 8080,
+        // destination: 'A010203',
+        status: '未连接'
+      },
+      showInventoryDetails: false,
+      inventoryDetails: [
+        {
+          id: 1,
+          batch: '',
+          quantity: 0,
+          location: ''
 
+        },
+        {
+          id: 2,
+          batch: '',
+          quantity: 0,
+          location: ''
 
-export default {
-    props: {
+        }
 
-        rowIndex: Number,
-        colIndex: Number,
-        layerIndex: Number,
-        selectedShelfType: String,
-        goodsData: Object,
+      ],
+      inventoryColumns: [
+        { name: 'batch', label: '批次', field: 'batch', align: 'left' },
+        { name: 'quantity', label: '数量', field: 'quantity', align: 'right' },
+        { name: 'location', label: '位置', field: 'location', align: 'left' }
+
+      ],
+      user_id: '',
+      auth_id: '',
+      onlyread: true,
+      clickedinput: false,
+      storage_dialog: false,
+      elevator_dialog: false,
+      conveyor_dialog: false,
+      bgColor: 'transparent',
+
+      shelf_type: { shelf_type: 'undefined' },
+      ip_change: { ip_address: '192.168.1.100', port: 8080, status: 'offline' },
+      goods_change: { shelf_status: '未满', goods_batch: '20230701-001', goods_code: 'A010203', goods_name: '存货货物', goods_std: '规格型号', goods_desc: '存货描述', goods_qty: 123456, goods_unit: 'kg', goods_price: 123456, goods_notes: '备注', goods_in: 123456, goods_out: 123456 },
+
+      error1: this.$t('stock.shelf.error1'),
+      shelfLocal: '',
+      activeTab: 'tab2'
+    }
+  },
+  created () {
+    this.shelfLocal = this.selectedShelfType
+    this.handleclick()
+  },
 
+  methods: {
 
-    },
-    data() {
-        return {
-            pathname: 'bin/',
-            pathnamecontainer: 'container/detail/',
-            container_id: 123456,
-            results: [],
-            storage_form: {
-                shelf_name: '',
-                shelf_department: '',
-                shelf_warehouse: '',
-                shelf_status: '',
-                goods_code: '',
-                goods_name: '',
-                goods_std: '',
-                goods_desc: '',
-                goods_qty: '',
-                goods_unit: '',
-                goods_price: '',
-                goods_batch: '',
-                goods_notes: '',
-                goods_in: '',
-                goods_out: '',
-            },
-            elevator_form: {
-                ip: '',
-                port: 8080,
-                // username: 'admin',
-                // password: '123456',
-                status: '未连接',
-            },
-            conveyor_form: {
-                ip: '',
-                port: 8080,
-                // destination: 'A010203',
-                status: '未连接',
-            },
-            showInventoryDetails: false,
-            inventoryDetails: [
-                {
-                    id: 1,
-                    batch: "",
-                    quantity: 0,
-                    location: "",
-
-                },
-                {
-                    id: 2,
-                    batch: "",
-                    quantity: 0,
-                    location: "",
-
-                }
-
-            ],
-            inventoryColumns: [
-                { name: 'batch', label: '批次', field: 'batch', align: 'left' },
-                { name: 'quantity', label: '数量', field: 'quantity', align: 'right' },
-                { name: 'location', label: '位置', field: 'location', align: 'left' },
-
-            ],
-            user_id: "",
-            auth_id: "",
-            onlyread: true,
-            clickedinput: false,
-            storage_dialog: false,
-            elevator_dialog: false,
-            conveyor_dialog: false,
-            bgColor: "transparent",
-
-            shelf_type: { shelf_type: 'undefined' },
-            ip_change: { ip_address: '192.168.1.100', port: 8080, status: 'offline' },
-            goods_change: { shelf_status: '未满', goods_batch: '20230701-001', goods_code: 'A010203', goods_name: '存货货物', goods_std: '规格型号', goods_desc: '存货描述', goods_qty: 123456, goods_unit: 'kg', goods_price: 123456,  goods_notes: '备注', goods_in: 123456, goods_out: 123456 },
-
-            error1: this.$t('stock.shelf.error1'),
-            shelfLocal: "",
-            activeTab: 'tab2'
-        }
-    },
-    created() {
-        this.shelfLocal = this.selectedShelfType;
-        this.handleclick()
+    prepareDialog () {
+      this.onlyread = true
     },
 
-    methods: {
+    handleclick () {
+      this.shelfLocal = this.selectedShelfType
 
-        prepareDialog() {
-            this.onlyread = true;
-        },
+      if (this.shelfLocal === 'undefined') { this.clickedinput = true, this.storage_dialog = false, this.elevator_dialog = false, this.conveyor_dialog = false }
+      if (this.shelfLocal === 'storage') { this.getList(), this.clickedinput = false, this.storage_dialog = true, this.elevator_dialog = false, this.conveyor_dialog = false }
+      if (this.shelfLocal === 'elevator') { this.getList(), this.clickedinput = false, this.storage_dialog = false, this.elevator_dialog = true, this.conveyor_dialog = false }
+      if (this.shelfLocal === 'conveyor') { this.getList(), this.clickedinput = false, this.storage_dialog = false, this.elevator_dialog = false, this.conveyor_dialog = true }
+    },
 
-                                               
-        handleclick() {
-            this.shelfLocal = this.selectedShelfType;
-            
-            if (this.shelfLocal == "undefined") { this.clickedinput = true, this.storage_dialog = false, this.elevator_dialog = false, this.conveyor_dialog = false }
-            if (this.shelfLocal == "storage") { this.getList(), this.clickedinput = false, this.storage_dialog = true, this.elevator_dialog = false, this.conveyor_dialog = false }
-            if (this.shelfLocal == "elevator") { this.getList(), this.clickedinput = false, this.storage_dialog = false, this.elevator_dialog = true, this.conveyor_dialog = false }
-            if (this.shelfLocal == "conveyor") { this.getList(), this.clickedinput = false, this.storage_dialog = false, this.elevator_dialog = false, this.conveyor_dialog = true }
+    getList () {
+      var _this = this
+      _this.storage_form = {
+        shelf_name: '',
+        shelf_department: '',
+        shelf_warehouse: '',
+        shelf_status: '',
+        goods_code: '',
+        goods_name: '',
+        goods_std: '',
+        goods_desc: '',
+        goods_qty: 0,
+        goods_unit: '',
+        goods_price: 0,
+        goods_batch: '',
+        goods_notes: 0,
+        goods_in: 0,
+        goods_out: 0
+      }
+      _this.inventoryDetails = [
+        {
+          id: 1,
+          batch: '',
+          quantity: 0,
+          location: ''
 
         },
+        {
+          id: 2,
+          batch: '',
+          quantity: 0,
+          location: ''
+        }
 
-        getList() {
-            var _this = this;
-            _this.storage_form = {
-                shelf_name: '',
-                shelf_department: '',
-                shelf_warehouse: '',
-                shelf_status: '',
-                goods_code: '',
-                goods_name: '',
-                goods_std: '',
-                goods_desc: '',
-                goods_qty: 0,
-                goods_unit: '',
-                goods_price: 0,
-                goods_batch: '',
-                goods_notes: 0,
-                goods_in: 0,
-                goods_out: 0,
+      ]
+      getauth(_this.pathname + _this.goodsData.id + '/')
+        .then(res1 => {
+          console.log(res1)
+          console.log(res1.current_containers)
+          if (res1.current_containers.length == 0) {
+            console.log('当前托盘ID为空')
+            _this.$q.notify({
+              message: '当前库位为空',
+              icon: 'info',
+              color: 'info'
+            })
+            return
+          }
+          _this.container_id = res1.current_containers[0].id
+          console.log('当前托盘ID', _this.container_id)
+          console.log('当前托盘ID长度', res1.current_containers.length)
+
+          getauth(_this.pathnamecontainer + '?container=' + _this.container_id)
+            .then(res => {
+              var results = res.results[0]
+              this.storage_form.goods_batch = results.batch.bound_number
+              this.storage_form.goods_code = results.batch.goods_code
+              this.storage_form.goods_name = results.batch.goods_desc
+              this.storage_form.goods_std = results.batch.goods_std
+              this.storage_form.goods_desc = results.batch.goods_desc
+              this.storage_form.goods_qty = results.batch.goods_qty
+              this.storage_form.goods_unit = results.batch.goods_unit
+              this.storage_form.goods_price = results.batch.goods_price
+              this.storage_form.goods_notes = results.goods_qty
+              this.storage_form.goods_in = results.batch.goods_in
+              this.storage_form.goods_out = results.batch.goods_out
+              this.storage_form.shelf_name = results.shelf_name
+              this.storage_form.shelf_department = results.shelf_department
+              this.storage_form.shelf_warehouse = results.warehouse_name
+              this.storage_form.shelf_status = results.status
             }
-            _this.inventoryDetails = [
-                {
-                    id: 1,
-                    batch: "",
-                    quantity: 0,
-                    location: "",
-
-                },
-                {
-                    id: 2,
-                    batch: "",
-                    quantity: 0,
-                    location: "",
-                }
-
-            ]
-            getauth(_this.pathname + _this.goodsData.id + '/')
-                .then(res1 => {
-                    console.log(res1)
-                    console.log(res1.current_containers)
-                    if(res1.current_containers.length == 0)
-                    {
-                        console.log("当前托盘ID为空")
-                        _this.$q.notify({
-                            message: "当前库位为空",
-                            icon: 'info',
-                            color: 'info'
-                        });
-                        return
-                    }
-                    _this.container_id = res1.current_containers[0].id
-                    console.log("当前托盘ID",_this.container_id)
-                    console.log("当前托盘ID长度",res1.current_containers.length)
-               
-                    getauth(_this.pathnamecontainer + '?container='+_this.container_id)
-                    .then(res => {
-                        var results = res.results[0]
-                        this.storage_form.goods_batch = results.batch.bound_number
-                        this.storage_form.goods_code = results.batch.goods_code
-                        this.storage_form.goods_name = results.batch.goods_desc
-                        this.storage_form.goods_std = results.batch.goods_std
-                        this.storage_form.goods_desc = results.batch.goods_desc
-                        this.storage_form.goods_qty = results.batch.goods_qty
-                        this.storage_form.goods_unit = results.batch.goods_unit
-                        this.storage_form.goods_price = results.batch.goods_price
-                        this.storage_form.goods_notes = results.goods_qty
-                        this.storage_form.goods_in = results.batch.goods_in
-                        this.storage_form.goods_out = results.batch.goods_out
-                        this.storage_form.shelf_name = results.shelf_name
-                        this.storage_form.shelf_department = results.shelf_department
-                        this.storage_form.shelf_warehouse = results.warehouse_name
-                        this.storage_form.shelf_status = results.status
-                    }
-
-
-                ) 
-            }).catch(err => {
-                    _this.$q.notify({
-                        message: err.detail,
-                        icon: 'close',
-                        color: 'negative'
-                    });
-                });
-        },
+
+            )
+        }).catch(err => {
+          _this.$q.notify({
+            message: err.detail,
+            icon: 'close',
+            color: 'negative'
+          })
+        })
     }
+  }
 }
 
 </script>
 
 <style scoped>
 
-
-
 :deep(.q-field__label) {
 
     margin-top: 8px;
@@ -439,11 +426,10 @@ export default {
 
 }
 
-
 :deep(.q-field__control-container) {
 
     padding-left: 50px;
     margin-top: -5px;
 
 }
-</style>
+</style>

+ 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 {

+ 2 - 2
templates/src/pages/dashboard/dashboard.vue

@@ -23,7 +23,7 @@
                 exact
               />
             </transition>
-            <transition appear enter-active-class="animated zoomIn">
+            <!-- <transition appear enter-active-class="animated zoomIn">
               <q-route-tab
                 name="status_statements"
                 :label="$t('dashboards.status_statements')"
@@ -49,7 +49,7 @@
                 :to="{ name: 'inventory_inquiry' }"
                 exact
                 />
-            </transition>
+            </transition> -->
 
             
           </q-tabs>

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 788 - 244
templates/src/pages/dashboard/flows_statements.vue


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1360 - 111
templates/src/pages/dashboard/inboundAndOutbound.vue


+ 185 - 88
templates/src/pages/inbound/asn.vue

@@ -519,6 +519,7 @@
                 transition-show="scale"
                 transition-hide="scale"
                 :rules="[(val) => (val && val.length > 0) || error1]"
+                :onClick="assignGoodsCode()"
               />
             </div>
             <div class="col column q-gutter-y-md">
@@ -549,7 +550,7 @@
             option-value="value"
             emit-value
             map-options
-            :readonly="true"
+            :readonly="false"
             transition-show="scale"
             transition-hide="scale"
             :rules="[(val) => (val && val.length > 0) || error1]"
@@ -566,7 +567,7 @@
 
           <div class="row q-gutter-x-md">
             <div class="col column q-gutter-y-md">
-              <q-input
+              <!-- <q-input
                 dense
                 outlined
                 square
@@ -574,15 +575,28 @@
                 :label="'单重'"
                 type="number"
                 :rules="[(val) => (val && val > 0) || error1]"
+              /> -->
+              <q-input
+                dense
+                outlined
+                square
+                v-model.number="newBatchFormData.goods_qty"
+                :label="'数量'"
+                type="number"
+                :rules="[(val) => (val && val > 0) || error1]"
               />
               <q-input
                 dense
                 outlined
                 square
-                v-model="newBatchFormData.goods_std"
-                :label="'规格/备注'"
+                v-model="newBatchFormData.goods_package"
+                :label="'包装'"
                 :rules="[(val) => (val && val.length > 0) || error1]"
               />
+              <q-toggle
+                v-model="isorder"
+                :label="isorder ? '自动编码' : '手动编码(建议)'"
+              />
             </div>
             <div class="col column q-gutter-y-md">
               <q-input
@@ -593,9 +607,47 @@
                 :label="'单位'"
                 :rules="[(val) => (val && val.length > 0) || error1]"
               />
-              <q-toggle
-                v-model="isorder"
-                :label="isorder ? '自动编码' : '手动编码(建议)'"
+              <q-input
+                dense
+                outlined
+                square
+                v-model="newBatchFormData.goods_std"
+                :label="'规格/备注'"
+                :rules="[(val) => (val && val.length > 0) || error1]"
+              />
+            </div>
+          </div>
+          <div class="row q-gutter-x-md" v-show="!isorder">
+            <div class="col column q-gutter-y-md">
+              <q-input
+                dense
+                outlined
+                square
+                v-model="batch_number_year"
+                type="number"
+                :label="'年'"
+                :rules="[(val) => (val && val > 0) || error1]"
+              />
+            </div>
+            <div class="col column q-gutter-y-md">
+              <q-input
+                dense
+                outlined
+                square
+                v-model="batch_number_month"
+                :label="'月'"
+                type="number"
+                :rules="[(val) => (val && val > 0) || error1]"
+              />
+            </div>
+            <div class="col column q-gutter-y-md">
+              <q-input
+                dense
+                outlined
+                v-model="batch_number_batch"
+                type="number"
+                :label="'批'"
+                :rules="[(val) => (val && val > 0) || '请输入正确的批号']"
               />
             </div>
           </div>
@@ -605,16 +657,8 @@
             outlined
             square
             v-model.number="newBatchFormData.bound_batch_order"
-            :label="'批号——年月第几批(20250409)'"
-            type="number"
-            :rules="[(val) => (val && val > 0) || error1]"
-          />
-          <q-input
-            dense
-            outlined
-            square
-            v-model.number="newBatchFormData.goods_qty"
-            :label="'数量'"
+            :label="'批号——年月第几批(202504009)'"
+            class="centered-input"
             type="number"
             :rules="[(val) => (val && val > 0) || error1]"
           />
@@ -648,7 +692,7 @@
       transition-show="jump-down"
       transition-hide="jump-up"
     >
-      <q-card style="min-width: 900px">
+      <q-card style="min-width: 1150px">
         <q-bar
           class="bg-light-blue-10 text-white rounded-borders"
           style="height: 50px"
@@ -722,73 +766,29 @@
             <q-card-section>
               <template>
                 <div class="text-h6 q-mb-md">{{ "批次信息" }}</div>
-                <template v-if="batch_detail.length > 0">
-                  <div
-                    v-for="(item, index) in batch_detail"
-                    :key="index"
-                    class="row q-col-gutter-md q-mb-sm"
-                  >
-                    <div class="col-1">
-                      <!-- 修改打印按钮,使用v-print指令 -->
+                <q-table
+                  v-if="batch_detail.length > 0"
+                  :data="batch_detail"
+                  :columns="columns_batch"
+                  row-key="id"
+                  flat
+                  bordered
+                  hide-pagination
+                  class="my-sticky-table"
+                >
+                  <template v-slot:body-cell-actions="props">
+                    <q-td :props="props">
                       <q-btn
                         icon="print"
                         flat
-                        v-print="getPrintConfig(item)"
-                        @click="setCurrentBatch(item)"
+                        v-print="getPrintConfig(props.row)"
+                        @click="setCurrentBatch(props.row)"
                       >
                         <q-tooltip>打印条码</q-tooltip>
                       </q-btn>
-                    </div>
-                    <div class="col-3">
-                      <q-input
-                        v-model="item.bound_batch.sourced_number"
-                        :label="'批次'"
-                        :readonly="onlyread"
-                        dense
-                        outlined
-                      />
-                    </div>
-                    <div class="col-4">
-                      <q-input
-                        v-model="item.bound_batch.goods_desc"
-                        :label="'货物'"
-                        :readonly="onlyread"
-                        dense
-                        outlined
-                      />
-                    </div>
-
-                    <div class="col-2">
-                      <q-input
-                        v-model="item.bound_batch.goods_in_qty"
-                        :label="'数量'"
-                        :readonly="onlyread"
-                        dense
-                        outlined
-                      />
-                    </div>
-                    <div class="col-2">
-                      <q-input
-                        v-model="item.bound_batch.goods_unit"
-                        :label="'单位'"
-                        :readonly="onlyread"
-                        dense
-                        outlined
-                      />
-                    </div>
-                    <!-- <div class="col" style="max-width: 50px">
-                      <q-btn
-                        v-if="!onlyread"
-                        icon="delete"
-                        flat
-                        dense
-                        color="primary"
-                        @click="addbatch(table_detail.id)"
-                        style="margin-top: 1px"
-                      />
-                    </div> -->
-                  </div>
-                </template>
+                    </q-td>
+                  </template>
+                </q-table>
               </template>
             </q-card-section>
           </q-card>
@@ -817,9 +817,9 @@
 
 <script>
 import { getauth, postauth, putauth, deleteauth } from 'boot/axios_request'
+
 import JsBarcode from 'jsbarcode'
 import { date, exportFile, LocalStorage, QToggle } from 'quasar'
-
 export default {
   components: {
     QToggle
@@ -869,6 +869,9 @@ export default {
       bound_status_map: [],
       product_list: [],
       product_map: [],
+      batch_number_year: '',
+      batch_number_month: '',
+      batch_number_batch: '',
 
       columns: [
         { name: 'detail', label: '详情', field: 'detail', align: 'center' },
@@ -917,6 +920,55 @@ export default {
         },
         { name: 'action', label: '操作', align: 'center' }
       ],
+      // 列定义
+      columns_batch: [
+        {
+          name: 'actions',
+          label: '操作',
+          align: 'center',
+          style: 'width: 80px'
+        },
+        {
+          name: 'bound_batch_order',
+          label: '批次',
+          field: (row) => row.bound_batch.bound_batch_order,
+          align: 'center'
+        },
+        {
+          name: 'goods_desc',
+          label: '货物',
+          field: (row) => row.bound_batch.goods_desc,
+          align: 'center'
+        },
+        {
+          name: 'plan_weight',
+          label: '计划入库重量',
+          field: (row) => row.bound_batch.goods_qty, // 确保字段路径正确
+          align: 'center',
+          style: 'width: 150px'
+        },
+        {
+          name: 'actual_weight',
+          label: '实际入库重量',
+          field: (row) => row.bound_batch.goods_in_qty, // 根据实际字段调整
+          align: 'center',
+          style: 'width: 150px'
+        },
+        {
+          name: 'unit',
+          label: '单位',
+          field: (row) => row.bound_batch.goods_unit,
+          align: 'center',
+          style: 'width: 10px'
+        },
+        {
+          name: 'package',
+          label: '包装',
+          field: (row) => row.bound_batch.goods_package,
+          align: 'center',
+          style: 'width: 10px'
+        }
+      ],
       filter: '',
       product_filter: '',
       pagination: {
@@ -948,7 +1000,7 @@ export default {
       activeTab: 'tab1',
       isorder: false,
       order: 'false',
-      currentBarcode: '',
+
       printConfig: {
         id: 'printBarcode'
       },
@@ -967,6 +1019,27 @@ export default {
     }
   },
   methods: {
+    assignGoodsCode () {
+      console.log('data', this.newBatchFormData.goods_code)
+      console.log(
+        'product_map',
+        this.product_map[this.newBatchFormData.goods_code]
+      )
+      if (this.product_map[this.newBatchFormData.goods_code]) {
+        this.newBatchFormData.goods_desc =
+          this.product_map[this.newBatchFormData.goods_code].product_name
+        this.newBatchFormData.goods_std =
+          this.product_map[this.newBatchFormData.goods_code].product_std
+        this.newBatchFormData.goods_unit =
+          this.product_map[this.newBatchFormData.goods_code].product_unit
+        this.newBatchFormData.goods_package =
+          this.product_map[this.newBatchFormData.goods_code].product_package
+        this.newBatchFormData.goods_weight = 1
+      }
+      // this.newBatchFormData.goods_code = this.product_map[this.newBatchFormData.goods_code].product_code
+      // this.newBatchFormData.goods_std = this.product_map[this.newBatchFormData.goods_code].goods_std
+      // this.newBatchFormData.goods_package = this.product_map[this.newBatchFormData.goods_code].goods_package
+    },
     setCurrentBatch (item) {
       this.currentBarcode = item.bound_batch?.bound_number || ''
       this.currentgoods = item.bound_batch
@@ -1073,7 +1146,7 @@ export default {
             value: item.product_code
           }))
           _this.product_map = res.results.reduce((acc, item) => {
-            acc[item.product_code] = item.product_name
+            acc[item.product_code] = item
             return acc
           }, {})
         })
@@ -1201,7 +1274,7 @@ export default {
         .then((res) => {
           _this.getSearchList()
           _this.newDataCancel()
-          if (res.status_code != 500) {
+          if (res.status_code !== 500) {
             _this.$q.notify({
               message: '成功新增数据',
               icon: 'check',
@@ -1227,13 +1300,13 @@ export default {
       console.log('当前的order是', _this.newBatchFormData.order)
       postauth('bound/batch/', _this.newBatchFormData)
         .then((res) => {
-          if (res.status_code != 500) {
+          if (res.status_code !== 500) {
             _this.newDetailFormData.bound_batch = res.id
             _this.newDetailFormData.creater = _this.login_name
 
             postauth('bound/detail/', _this.newDetailFormData).then(
               (res) => {
-                if (res.status_code != 500) {
+                if (res.status_code !== 500) {
                   _this.detailData(_this.newDetailFormData)
 
                   _this.$q.notify({
@@ -1275,6 +1348,9 @@ export default {
     addbatch (bound_number) {
       var _this = this
       _this.newBatchForm = true
+      _this.batch_number_year = _this.date.slice(0, 4)
+      _this.batch_number_month = _this.date.slice(5, 7)
+      _this.newBatchFormData.creater = _this.login_name
       _this.newDetailFormData = {
         id: bound_number,
         bound_list: bound_number
@@ -1316,7 +1392,7 @@ export default {
         .then((res) => {
           _this.editDataCancel()
           _this.getSearchList()
-          if (res.status_code != 500) {
+          if (res.status_code !== 500) {
             _this.$q.notify({
               message: '开始入库',
               icon: 'check',
@@ -1340,7 +1416,7 @@ export default {
         .then((res) => {
           _this.editDataCancel()
           _this.getSearchList()
-          if (res.status_code != 500) {
+          if (res.status_code !== 500) {
             _this.$q.notify({
               message: '成功编辑数据',
               icon: 'check',
@@ -1589,7 +1665,11 @@ export default {
         this.order = 'false'
       }
     },
-
+    batch_number_batch: function (val) {
+      console.log(val)
+      this.newBatchFormData.bound_batch_order =
+        this.batch_number_year * 10000 + this.batch_number_month * 100 + val
+    },
     createDate1 (val) {
       if (val) {
         if (val.to) {
@@ -1702,4 +1782,21 @@ export default {
     gap: 1mm;
   }
 }
+
+/* 在样式文件中添加 */
+.centered-input ::v-deep .q-field__control {
+  justify-content: center;
+}
+
+.centered-input ::v-deep input {
+  text-align: center;
+  -moz-appearance: textfield; /* 隐藏Firefox的数字箭头 */
+}
+
+/* 隐藏Chrome/Safari的数字箭头 */
+.centered-input ::v-deep input::-webkit-outer-spin-button,
+.centered-input ::v-deep input::-webkit-inner-spin-button {
+  -webkit-appearance: none;
+  margin: 0;
+}
 </style>

+ 5 - 4
templates/src/pages/inbound/inbound.vue

@@ -9,10 +9,8 @@
         <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="asn" :label="$t('inbound.asn')" icon="img:statics/inbound/asn.png" :to="{ name: 'asn' }" exact/>
         </transition>
-        <!-- <transition appear enter-active-class="animated zoomIn">
-          <q-route-tab name="predeliverystock" :label="$t('inbound.predeliverystock')"  icon="img:statics/inbound/polist.png" :to="{ name: 'predeliverystock' }" exact/>
-        </transition>
-        <transition appear enter-active-class="animated zoomIn">
+
+        <!--<transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="preloadstock" :label="$t('inbound.preloadstock')" icon="img:statics/inbound/preloadstock.png" :to="{ name: 'preloadstock' }" exact/>
         </transition>
         <transition appear enter-active-class="animated zoomIn">
@@ -21,6 +19,9 @@
         <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="sortstock" :label="$t('inbound.sortstock')" icon="img:statics/inbound/sortstock.png" :to="{ name: 'sortstock' }" exact/>
         </transition>
+        <transition appear enter-active-class="animated zoomIn">
+          <q-route-tab name="predeliverystock" :label="'分拣统计'"  icon="img:statics/inbound/polist.png" :to="{ name: 'predeliverystock' }" exact/>
+        </transition>
         <!-- <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="shortage" :label="$t('inbound.shortage')" icon="img:statics/inbound/shortage.png" :to="{ name: 'shortage' }" exact/>
         </transition>

+ 383 - 182
templates/src/pages/inbound/predeliverystock.vue

@@ -1,13 +1,12 @@
 <template>
-    <div>
-      <transition appear enter-active-class="animated fadeIn">
+  <div>
+    <transition appear enter-active-class="animated fadeIn">
       <q-table
-        class="my-sticky-header-table shadow-24"
+        class="my-sticky-header-column-table shadow-24"
         :data="table_list"
         row-key="id"
         :separator="separator"
         :loading="loading"
-        :filter="filter"
         :columns="columns"
         hide-bottom
         :pagination.sync="pagination"
@@ -17,116 +16,197 @@
         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-btn>
-           </q-btn-group>
-           <q-space />
-           <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>
-         </template>
-         <template v-slot:body="props">
-           <q-tr :props="props">
-               <q-td key="asn_code" :props="props">
-                 {{ props.row.asn_code }}
-               </q-td>
-               <q-td key="goods_code" :props="props">
-                 {{ props.row.goods_code }}
-               </q-td>
-               <q-td key="goods_desc" :props="props">
-                 {{ props.row.goods_desc }}
-               </q-td>
-               <q-td key="goods_qty" :props="props">
-                 {{ props.row.goods_qty }}
-               </q-td>
-               <q-td key="goods_weight" :props="props">
-                 {{ props.row.goods_weight }}
-               </q-td>
-             <q-td key="goods_volume" :props="props">
-               {{ props.row.goods_volume }}
-             </q-td>
-             <q-td key="supplier" :props="props">
-               {{ props.row.supplier }}
-             </q-td>
-             <q-td key="creater" :props="props">
-               {{ props.row.creater }}
-             </q-td>
-             <q-td key="create_time" :props="props">
-               {{ props.row.create_time }}
-             </q-td>
-             <q-td key="update_time" :props="props">
-               {{ props.row.update_time }}
-             </q-td>
-           </q-tr>
-         </template>
+        <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-btn>
+            <!-- <q-btn :label="'日志'" icon="logout" @click="getlog()"> </q-btn> -->
+          </q-btn-group>
+
+          <q-space />
+
+          <div class="flex items-center">
+
+            <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">
+            <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.batch_items?.length"
+                >
+                  <q-timeline-entry
+                    v-for="(batch_item, index) in props.row.batch_items"
+                    :key="index"
+                    class="custom-node"
+                  >
+                    <template v-slot:title>
+                      <span>
+                        <div>批次 {{ batch_item.batch_code }}</div>
+                        <div class="row">
+                          <div class="col">
+                            <div class="custom-title">
+                              {{ batch_item.goods_desc }}
+                            </div>
+                          </div>
+                          <div class="col">
+                            <div class="custom-title">
+                              数量:{{ batch_item.goods_in_qty }}
+                            </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="getList()"
+    </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>
+          <input
+            v-model="paginationIpt"
+            @blur="changePageEnter"
+            @keyup.enter="changePageEnter"
+            style="width: 60px; text-align: center"
           />
-          <div>
-            <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>
+      </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>
+  </div>
 </template>
-    <router-view />
+<router-view />
 
 <script>
-import { getauth } from 'boot/axios_request'
+import { getauth, postauth, putauth, deleteauth } from 'boot/axios_request'
+import { date, LocalStorage } from 'quasar'
 
 export default {
-  name: 'Pageasndetail',
+  name: 'PageTask',
   data () {
     return {
+      createDate1: '',
+      createDate2: '',
+      date_range: '',
+      proxyDate: '',
+      date: '',
+      goods_code: '',
+      goods_desc: '',
       openid: '',
       login_name: '',
       authin: '0',
-      pathname: 'asn/detail/?asn_status=1',
+      searchUrl: '',
+      pathname: 'bound/batch/count/',
       pathname_previous: '',
       pathname_next: '',
       separator: 'cell',
       loading: false,
       height: '',
+      viewForm: false,
+
       table_list: [],
-      bin_size_list: [],
-      bin_property_list: [],
-      warehouse_list: [],
       columns: [
-        { name: 'asn_code', required: true, label: this.$t('inbound.view_asn.asn_code'), align: 'left', field: 'asn_code' },
-        { name: 'goods_code', label: this.$t('goods.view_goodslist.goods_code'), field: 'goods_code', align: 'center' },
-        { name: 'goods_desc', label: this.$t('goods.view_goodslist.goods_desc'), field: 'goods_desc', align: 'center' },
-        { name: 'goods_qty', label: this.$t('inbound.view_asn.goods_qty'), field: 'goods_qty', align: 'center' },
-        { name: 'goods_weight', label: this.$t('inbound.view_asn.total_weight'), field: 'goods_weight', align: 'center' },
-        { name: 'goods_volume', label: this.$t('inbound.view_asn.total_volume'), field: 'goods_volume', align: 'center' },
-        { name: 'supplier', label: this.$t('baseinfo.view_supplier.supplier_name'), field: 'supplier', align: 'center' },
-        { name: 'creater', label: this.$t('creater'), field: 'creater', align: 'center' },
-        { name: 'create_time', label: this.$t('createtime'), field: 'create_time', align: 'center' },
-        { name: 'update_time', label: this.$t('updatetime'), field: 'update_time', align: 'center' }
+        {
+          name: 'expand',
+          label: '',
+          align: 'left',
+          headerStyle: 'width: 50px'
+        },
+
+        {
+          name: 'goods_code',
+          label: '存货编码',
+          field: 'goods_code',
+          align: 'center'
+        },
+        {
+          name: 'goods_desc',
+          label: '存货名称',
+          field: 'goods_desc',
+          align: 'center'
+        },
+
+        {
+          name: 'total_quantity',
+          label: '在库数目',
+          field: 'total_quantity',
+          align: 'center'
+        },
+        {
+          name: 'goods_unit',
+          label: '单位',
+          field: 'goods_unit',
+          align: 'center',
+          headerStyle: 'width: 20px'
+        }
       ],
       filter: '',
       pagination: {
@@ -136,135 +216,190 @@ export default {
       current: 1,
       max: 0,
       total: 0,
-      paginationIpt: 1
+      paginationIpt: 1,
+      containers: {},
+      timer: null
+    }
+  },
+  computed: {
+    interval () {
+      return (
+        this.$t('download_center.start') +
+        ' - ' +
+        this.$t('download_center.end')
+      )
     }
   },
   methods: {
-    getList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
+    class_to_name (class_id) {
+      const class_map = {
+        1: '整盘',
+        2: '托盘组',
+        3: '零盘'
       }
+      return class_map[class_id]
     },
-    changePageEnter(e) {
-      if (Number(this.paginationIpt) < 1) {
-        this.current = 1;
-        this.paginationIpt = 1;
-      } else if (Number(this.paginationIpt) > this.max) {
-        this.current = this.max;
-        this.paginationIpt = this.max;
-      } else {
-        this.current = Number(this.paginationIpt);
+    handle_row_expand (row) {
+      const _this = this
+      row.expand = !row.expand
+      if (row.expand) {
+        // 添加行级 loading 状态
+        _this.$set(row, 'loading', true)
+        getauth('bound/batch/count/' + row.id + '/', {})
+          .then((res) => {
+            _this.$set(row, 'batch_items', res.batch_items)
+            console.log('当前的', row.batch_items)
+          })
+          .catch((err) => {
+            _this.$q.notify({ message: err.detail, color: 'negative' })
+          })
+          .finally(() => {
+            row.loading = false // 关闭加载状态
+          })
       }
-      this.getList();
     },
-    getSearchList () {
+    getlog () {
+      // console.log(this.table_list)
+      console.log('当前loading状态:', this.loading)
+    },
+    getList (params = {}) {
       var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        _this.current = 1
-        _this.paginationIpt = 1
-        getauth(_this.pathname + '&asn_code__icontains=' + _this.filter + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
+      _this.loading = true
+      // 合并基础参数
+      const baseParams = {
+        page: _this.current,
+        page_size: _this.pagination.rowsPerPage
+      }
+
+      // 创建URLSearchParams处理参数
+      const queryParams = new URLSearchParams({
+        ...baseParams,
+        ...params
+      })
+      console.log(queryParams)
+      // 过滤空值参数
+      Array.from(queryParams.entries()).forEach(([key, value]) => {
+        if (value === '' || value === null || value === undefined) {
+          queryParams.delete(key)
+        }
+      })
+
+      getauth(`${_this.pathname}?${queryParams}`)
+        .then((res) => {
+          _this.table_list = res.results.map((item) => ({
+            ...item,
+            expand: false,
+            batch_items: [
+
+            ],
+            loading: false
+          }))
           _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
+          _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',
             color: 'negative'
           })
         })
+        .finally(() => {
+          _this.loading = false
+        })
+    },
+    changePageEnter () {
+      if (Number(this.paginationIpt) < 1) {
+        this.current = 1
+        this.paginationIpt = 1
+      } else if (Number(this.paginationIpt) > this.max) {
+        this.current = this.max
+        this.paginationIpt = this.max
       } else {
+        this.current = Number(this.paginationIpt)
       }
+      this.getSearchList(this.current)
     },
+
+    // 带搜索条件加载
+    getSearchList (page = 1) {
+      this.current = page
+      this.paginationIpt = page
+      this.getList({
+        goods_desc__icontains: this.filter,
+        create_time__range: this.date_range
+      })
+    },
+
     getListPrevious () {
       var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_previous, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
+      if (LocalStorage.has('auth')) {
+        getauth(_this.pathname_previous, {})
+          .then((res) => {
+            _this.table_list = res.results
+            _this.pathname_previous = res.previous
+            _this.pathname_next = res.next
+          })
+          .catch((err) => {
+            _this.$q.notify({
+              message: err.detail,
+              icon: 'close',
+              color: 'negative'
+            })
           })
-        })
       } else {
       }
     },
     getListNext () {
       var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_next, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
+      if (LocalStorage.has('auth')) {
+        getauth(_this.pathname_next, {})
+          .then((res) => {
+            _this.table_list = res.results
+
+            _this.pathname_previous = res.previous
+            _this.pathname_next = res.next
+          })
+          .catch((err) => {
+            _this.$q.notify({
+              message: err.detail,
+              icon: 'close',
+              color: 'negative'
+            })
           })
-        })
-      } else {
       }
     },
     reFresh () {
       var _this = this
-      _this.getList()
+      _this.getSearchList()
+    },
+
+    updateProxy () {
+      var _this = this
+      _this.proxyDate = _this.date
     }
   },
   created () {
     var _this = this
-    if (_this.$q.localStorage.has('openid')) {
-      _this.openid = _this.$q.localStorage.getItem('openid')
+    if (LocalStorage.has('openid')) {
+      _this.openid = LocalStorage.getItem('openid')
     } else {
       _this.openid = ''
-      _this.$q.localStorage.set('openid', '')
+      LocalStorage.set('openid', '')
     }
-    if (_this.$q.localStorage.has('login_name')) {
-      _this.login_name = _this.$q.localStorage.getItem('login_name')
+    if (LocalStorage.has('login_name')) {
+      _this.login_name = LocalStorage.getItem('login_name')
     } else {
       _this.login_name = ''
-      _this.$q.localStorage.set('login_name', '')
+      LocalStorage.set('login_name', '')
     }
-    if (_this.$q.localStorage.has('auth')) {
+    if (LocalStorage.has('auth')) {
+      const timeStamp = Date.now()
+      const formattedString = date.formatDate(timeStamp, 'YYYY/MM/DD')
+      _this.date = formattedString
+      console.log(_this.date)
       _this.authin = '1'
       _this.getList()
     } else {
@@ -278,10 +413,76 @@ export default {
     } else {
       _this.height = _this.$q.screen.height - 290 + '' + 'px'
     }
+    // _this.timer = setInterval(() => {
+    //   _this.getlog()
+    // }, 1000)
   },
-  updated () {
-  },
-  destroyed () {
+  updated () {},
+  destroyed () {},
+  // 在 watch 或方法中添加调试代码
+  watch: {
+    createDate1 (val) {
+      if (val) {
+        if (val.to) {
+          this.createDate2 = `${val.from} - ${val.to}`
+          this.date_range = `${val.from},${val.to} `
+
+          // this.downloadhUrl = this.pathname + 'filelist/?' + 'document_date__range=' + this.date_range
+        } 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.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 {
+        this.createDate2 = ''
+        this.date_range = ''
+        this.getSearchList()
+      }
+    }
   }
 }
 </script>
+<style scoped>
+/* 添加在 <style> 中 */
+.q-date__calendar-item--selected {
+  transition: all 0.3s ease;
+  background-color: #1976d2 !important;
+}
+
+.q-date__range {
+  background-color: rgba(25, 118, 210, 0.1);
+}
+
+.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>

+ 327 - 142
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,25 +415,25 @@ export default {
     },
 
     // 带搜索条件加载
-    getSearchList(page = 1) {
+    getSearchList (page = 1) {
       this.current = page
       this.paginationIpt = page
       this.getList({
-        container_detail__goods_desc__icontains: this.filter,
+        goods_desc__icontains: this.filter,
         create_time__range: this.date_range
       })
     },
 
-    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>

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 616 - 276
templates/src/pages/outbound/dn.vue


+ 221 - 265
templates/src/pages/outbound/freshorder.vue

@@ -1,287 +1,243 @@
 <template>
-    <div>
-      <transition appear enter-active-class="animated fadeIn">
-      <q-table
-        class="my-sticky-header-table shadow-24"
-        :data="table_list"
-        row-key="id"
-        :separator="separator"
-        :loading="loading"
-        :filter="filter"
-        :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-card class="q-pa-md shadow-5">
+    <!-- 条件组操作栏 -->
+    <div class="q-mb-md">
+      <div class="row">
+        <q-btn
+          label="添加条件"
+          icon="add"
+          color="primary"
+          @click="addCondition"
+        />
+        <q-select
+          v-model="logicOperator"
+          :options="['AND', 'OR']"
+          dense
+          class="q-ml-md"
+          style="width: 100px"
+        />
+      </div>
+    </div>
+
+    <!-- 拖拽式条件容器 -->
+    <draggable :list="conditions" handle=".handle">
+      <div
+        v-for="(condition, index) in conditions"
+        :key="condition.id"
+        class="row q-gutter-sm q-mb-md"
       >
-         <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-btn>
-           </q-btn-group>
-           <q-space />
-           <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>
-         </template>
-         <template v-slot:body="props">
-           <q-tr :props="props">
-               <q-td key="dn_code" :props="props">
-                 {{ props.row.dn_code }}
-               </q-td>
-               <q-td key="goods_code" :props="props">
-                 {{ props.row.goods_code }}
-               </q-td>
-               <q-td key="goods_desc" :props="props">
-                 {{ props.row.goods_desc }}
-               </q-td>
-               <q-td key="goods_qty" :props="props">
-                 {{ props.row.goods_qty }}
-               </q-td>
-               <q-td key="goods_weight" :props="props">
-                 {{ props.row.goods_weight }}
-               </q-td>
-             <q-td key="goods_volume" :props="props">
-               {{ props.row.goods_volume }}
-             </q-td>
-             <q-td key="customer" :props="props">
-               {{ props.row.customer }}
-             </q-td>
-             <q-td key="creater" :props="props">
-               {{ props.row.creater }}
-             </q-td>
-             <q-td key="create_time" :props="props">
-               {{ props.row.create_time }}
-             </q-td>
-             <q-td key="update_time" :props="props">
-               {{ props.row.update_time }}
-             </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="getList()"
-          />
-          <div>
-            <input
-              v-model="paginationIpt"
-              @blur="changePageEnter"
-              @keyup.enter="changePageEnter"
-              style="width: 60px; text-align: center"
+        <!-- 拖拽手柄 -->
+
+        <q-icon name="drag_indicator" class="handle text-grey-6" size="24px" />
+
+        <!-- 字段选择 -->
+        <q-select
+          v-model="condition.field"
+          :options="fieldOptions"
+          label="字段"
+          dense
+          emit-value
+          map-options
+          style="min-width: 160px"
+        />
+
+        <!-- 运算符 -->
+        <q-select
+          v-model="condition.operator"
+          :options="operatorOptions(condition.field)"
+          label="条件"
+          dense
+          emit-value
+          map-options
+          style="min-width: 130px"
+        />
+
+        <!-- 值输入 -->
+        <component
+          v-if="condition.field !== 'date'"
+          :is="inputComponent(condition.field)"
+          class="col"
+        />
+        <template v-else>
+          <div class="row q-gutter-xs">
+            <!-- 年 -->
+            <q-select
+              v-model="year"
+              :options="yearOptions"
+              label="年"
+              dense
+              emit-value
+              @update:model-value="handleChange"
+            />
+
+            <!-- 月 -->
+            <q-select
+              v-model="month"
+              :options="monthOptions"
+              label="月"
+              dense
+              emit-value
+              :disable="!year"
+              @update:model-value="handleChange"
+            />
+
+            <!-- 日 -->
+            <q-select
+              v-model="day"
+              :options="dayOptions"
+              label="日"
+              dense
+              emit-value
+              :disable="!month"
+              @update:model-value="handleChange"
             />
           </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>
+        </template>
+
+        <!-- 删除按钮 -->
+        <q-btn
+          flat
+          round
+          icon="delete"
+          color="negative"
+          @click="removeCondition(index)"
+        />
+      </div>
+    </draggable>
+    <div class="q-mt-md" align="right">
+      <q-btn label="重置" @click="resetConditions" />
+      <q-btn label="执行查询" color="primary" @click="executeQuery" />
+    </div>
+    <!-- 查询预览 & 操作 -->
+    <div class="q-mt-lg">
+      <q-card flat bordered>
+        <q-card-section>
+          <div class="text-caption">生成查询条件:</div>
+          <pre>{{ generatedQuery }}</pre>
+        </q-card-section>
+
+        <q-card-actions align="right"> </q-card-actions>
+      </q-card>
     </div>
+  </q-card>
 </template>
-    <router-view />
 
 <script>
-import { getauth } from 'boot/axios_request'
+import draggable from "vuedraggable";
+import CustomDateInput from "components/CustomDateInput.vue";
 
 export default {
-  name: 'Pagednneworder',
-  data () {
+  components: {
+    draggable,
+    CustomDateInput,
+  },
+  data() {
     return {
-      openid: '',
-      login_name: '',
-      authin: '0',
-      pathname: 'dn/detail/?dn_status=1',
-      pathname_previous: '',
-      pathname_next: '',
-      separator: 'cell',
-      loading: false,
-      height: '',
-      table_list: [],
-      bin_size_list: [],
-      bin_property_list: [],
-      warehouse_list: [],
-      columns: [
-        { name: 'dn_code', required: true, label: this.$t('outbound.view_dn.dn_code'), align: 'left', field: 'dn_code' },
-        { name: 'goods_code', label: this.$t('goods.view_goodslist.goods_code'), field: 'goods_code', align: 'center' },
-        { name: 'goods_desc', label: this.$t('goods.view_goodslist.goods_desc'), field: 'goods_desc', align: 'center' },
-        { name: 'goods_qty', label: this.$t('outbound.view_dn.goods_qty'), field: 'goods_qty', align: 'center' },
-        { name: 'goods_weight', label: this.$t('outbound.view_dn.total_weight'), field: 'goods_weight', align: 'center' },
-        { name: 'goods_volume', label: this.$t('outbound.view_dn.total_volume'), field: 'empty_label', align: 'center' },
-        { name: 'customer', label: this.$t('baseinfo.view_customer.customer_name'), field: 'customer', align: 'center' },
-        { name: 'creater', label: this.$t('creater'), field: 'creater', align: 'center' },
-        { name: 'create_time', label: this.$t('createtime'), field: 'create_time', align: 'center' },
-        { name: 'update_time', label: this.$t('updatetime'), field: 'update_time', align: 'center' }
+      conditions: [],
+      logicOperator: "AND",
+      fieldOptions: [
+        { label: "物料编码", value: "material_code" },
+        { label: "物料名称", value: "material_name" },
+        { label: "批次号", value: "batch" },
+        { label: "库存数量", value: "quantity" },
+        { label: "入库日期", value: "date" },
       ],
-      filter: '',
-      pagination: {
-        page: 1,
-        rowsPerPage: 11
+      operatorMap: {
+        default: [
+          { label: "等于", value: "eq" },
+          { label: "包含", value: "contains" },
+          { label: "开头为", value: "startswith" },
+        ],
+        quantity: [
+          { label: "大于", value: "gt" },
+          { label: "等于", value: "eq" },
+          { label: "小于", value: "lt" },
+        ],
+        date: [
+          { label: "之前", value: "lte" },
+          { label: "之后", value: "gte" },
+        ],
       },
-      current: 1,
-      max: 0,
-      total: 0,
-      paginationIpt: 1
-    }
+    };
   },
-  methods: {
-    getList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
+  computed: {
+    // 生成查询参数
+    generatedQuery() {
+      return this.conditions
+        .map((cond) => {
+          const suffix = {
+            eq: "",
+            contains: "__icontains",
+            startswith: "__istartswith",
+            gt: "__gt",
+            lt: "__lt",
+            gte: "__gte",
+            lte: "__lte",
+          }[cond.operator];
+
+          return `${cond.field}${suffix}=${cond.value}`;
         })
-      }
+        .join(` ${this.logicOperator} `);
+    },
+
+    // 动态输入组件
+    inputComponent() {
+      return function (field) {
+        const components = {
+          material_code: "q-input",
+          material_name: "q-input",
+          batch: "q-input",
+          quantity: "q-input",
+          date: "CustomDateInput",
+        };
+        return components[field] || "q-input";
+      };
     },
-    changePageEnter(e) {
-      if (Number(this.paginationIpt) < 1) {
-        this.current = 1;
-        this.paginationIpt = 1;
-      } else if (Number(this.paginationIpt) > this.max) {
-        this.current = this.max;
-        this.paginationIpt = this.max;
-      } else {
-        this.current = Number(this.paginationIpt);
-      }
-      this.getList();
+  },
+  methods: {
+    // 获取运算符选项
+    operatorOptions(field) {
+      return this.operatorMap[field] || this.operatorMap.default;
     },
-    getSearchList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        _this.current = 1
-        _this.paginationIpt = 1
-        getauth(_this.pathname + '&dn_code__icontains=' + _this.filter + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+
+    // 添加条件
+    addCondition() {
+      this.conditions.push({
+        id: Date.now(),
+        field: "material_code",
+        operator: "eq",
+        value: "",
+        ...(this.field === "date" && { value: "2025-01-01" }),
+      });
     },
-    getListPrevious () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_previous, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+    getDefaultDate() {
+      const today = new Date();
+      return `${today.getFullYear()}-${(today.getMonth() + 1)
+        .toString()
+        .padStart(2, "0")}-01`;
     },
-    getListNext () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_next, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+    // 删除条件
+    removeCondition(index) {
+      this.conditions.splice(index, 1);
+    },
+
+    // 重置条件
+    resetConditions() {
+      this.conditions = [];
+    },
+
+    // 执行查询
+    executeQuery() {
+      console.log("执行查询:", this.generatedQuery);
+      // 这里接入实际API调用
     },
-    reFresh () {
-      var _this = this
-      _this.getList()
-    }
-  },
-  created () {
-    var _this = this
-    if (_this.$q.localStorage.has('openid')) {
-      _this.openid = _this.$q.localStorage.getItem('openid')
-    } else {
-      _this.openid = ''
-      _this.$q.localStorage.set('openid', '')
-    }
-    if (_this.$q.localStorage.has('login_name')) {
-      _this.login_name = _this.$q.localStorage.getItem('login_name')
-    } else {
-      _this.login_name = ''
-      _this.$q.localStorage.set('login_name', '')
-    }
-    if (_this.$q.localStorage.has('auth')) {
-      _this.authin = '1'
-      _this.getList()
-    } else {
-      _this.authin = '0'
-    }
-  },
-  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'
-    }
-  },
-  updated () {
   },
-  destroyed () {
-  }
-}
+};
 </script>
+
+<style scoped>
+.handle {
+  cursor: move;
+  padding: 8px;
+}
+</style>

+ 81 - 270
templates/src/pages/outbound/neworder.vue

@@ -1,287 +1,98 @@
 <template>
-    <div>
-      <transition appear enter-active-class="animated fadeIn">
-      <q-table
-        class="my-sticky-header-table shadow-24"
-        :data="table_list"
-        row-key="id"
-        :separator="separator"
-        :loading="loading"
-        :filter="filter"
-        :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-btn>
-           </q-btn-group>
-           <q-space />
-           <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>
-         </template>
-         <template v-slot:body="props">
-           <q-tr :props="props">
-               <q-td key="dn_code" :props="props">
-                 {{ props.row.dn_code }}
-               </q-td>
-               <q-td key="goods_code" :props="props">
-                 {{ props.row.goods_code }}
-               </q-td>
-               <q-td key="goods_desc" :props="props">
-                 {{ props.row.goods_desc }}
-               </q-td>
-               <q-td key="goods_qty" :props="props">
-                 {{ props.row.goods_qty }}
-               </q-td>
-               <q-td key="goods_weight" :props="props">
-                 {{ props.row.goods_weight }}
-               </q-td>
-             <q-td key="goods_volume" :props="props">
-               {{ props.row.goods_volume }}
-             </q-td>
-             <q-td key="customer" :props="props">
-               {{ props.row.customer }}
-             </q-td>
-             <q-td key="creater" :props="props">
-               {{ props.row.creater }}
-             </q-td>
-             <q-td key="create_time" :props="props">
-               {{ props.row.create_time }}
-             </q-td>
-             <q-td key="update_time" :props="props">
-               {{ props.row.update_time }}
-             </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="getList()"
-          />
-          <div>
-            <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>
+  <div class="row q-gutter-xs">
+    <!-- 年份选择 -->
+    <q-select
+      v-model="year"
+      :options="yearOptions"
+      label="年"
+      dense
+      style="min-width: 90px"
+      @update:model-value="updateDate"
+    />
+
+    <!-- 月份选择 -->
+    <q-select
+      v-model="month"
+      :options="monthOptions"
+      label="月"
+      dense
+      style="min-width: 80px"
+      @update:model-value="updateDate"
+    />
+
+    <!-- 日期选择 -->
+    <q-select
+      v-model="day"
+      :options="dayOptions"
+      label="日"
+      dense
+      style="min-width: 80px"
+      @update:model-value="updateDate"
+    />
+  </div>
 </template>
-    <router-view />
 
 <script>
-import { getauth } from 'boot/axios_request'
-
 export default {
-  name: 'Pagednneworder',
-  data () {
+  props: ['modelValue'],
+  emits: ['update:modelValue'],
+  
+  data() {
     return {
-      openid: '',
-      login_name: '',
-      authin: '0',
-      pathname: 'dn/detail/?dn_status=2',
-      pathname_previous: '',
-      pathname_next: '',
-      separator: 'cell',
-      loading: false,
-      height: '',
-      table_list: [],
-      bin_size_list: [],
-      bin_property_list: [],
-      warehouse_list: [],
-      columns: [
-        { name: 'dn_code', required: true, label: this.$t('outbound.view_dn.dn_code'), align: 'left', field: 'dn_code' },
-        { name: 'goods_code', label: this.$t('goods.view_goodslist.goods_code'), field: 'goods_code', align: 'center' },
-        { name: 'goods_desc', label: this.$t('goods.view_goodslist.goods_desc'), field: 'goods_desc', align: 'center' },
-        { name: 'goods_qty', label: this.$t('outbound.view_dn.goods_qty'), field: 'goods_qty', align: 'center' },
-        { name: 'goods_weight', label: this.$t('outbound.view_dn.total_weight'), field: 'goods_weight', align: 'center' },
-        { name: 'goods_volume', label: this.$t('outbound.view_dn.total_volume'), field: 'empty_label', align: 'center' },
-        { name: 'customer', label: this.$t('baseinfo.view_customer.customer_name'), field: 'customer', align: 'center' },
-        { name: 'creater', label: this.$t('creater'), field: 'creater', align: 'center' },
-        { name: 'create_time', label: this.$t('createtime'), field: 'create_time', align: 'center' },
-        { name: 'update_time', label: this.$t('updatetime'), field: 'update_time', align: 'center' }
-      ],
-      filter: '',
-      pagination: {
-        page: 1,
-        rowsPerPage: 11
-      },
-      current: 1,
-      max: 0,
-      total: 0,
-      paginationIpt: 1
+      year: null,
+      month: null,
+      day: null
     }
   },
-  methods: {
-    getList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      }
-    },
-    changePageEnter(e) {
-      if (Number(this.paginationIpt) < 1) {
-        this.current = 1;
-        this.paginationIpt = 1;
-      } else if (Number(this.paginationIpt) > this.max) {
-        this.current = this.max;
-        this.paginationIpt = this.max;
-      } else {
-        this.current = Number(this.paginationIpt);
-      }
-      this.getList();
-    },
-    getSearchList () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        _this.current = 1
-        _this.paginationIpt = 1
-        getauth(_this.pathname + '&dn_code__icontains=' + _this.filter + '&page=' + '' + _this.current, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.total = res.count
-          if (res.count === 0) {
-            _this.max = 0
-          } else {
-            if (Math.ceil(res.count / _this.pagination.rowsPerPage) === 1) {
-              _this.max = 0
-            } else {
-              _this.max = Math.ceil(res.count / _this.pagination.rowsPerPage)
-            }
-          }
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
-    },
-    getListPrevious () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_previous, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+
+  computed: {
+    // 生成年份选项(最近5年)
+    yearOptions() {
+      const currentYear = new Date().getFullYear()
+      return Array.from({length: 5}, (_, i) => currentYear - i)
     },
-    getListNext () {
-      var _this = this
-      if (_this.$q.localStorage.has('auth')) {
-        getauth(_this.pathname_next, {
-        }).then(res => {
-          _this.table_list = res.results
-          _this.pathname_previous = res.previous
-          _this.pathname_next = res.next
-        }).catch(err => {
-          _this.$q.notify({
-            message: err.detail,
-            icon: 'close',
-            color: 'negative'
-          })
-        })
-      } else {
-      }
+
+    // 月份选项(1-12月)
+    monthOptions() {
+      return Array.from({length: 12}, (_, i) => i + 1)
     },
-    reFresh () {
-      var _this = this
-      _this.getList()
+
+    // 动态生成日期选项
+    dayOptions() {
+      if (!this.year || !this.month) return []
+      const days = new Date(this.year, this.month, 0).getDate()
+      return Array.from({length: days}, (_, i) => i + 1)
     }
   },
-  created () {
-    var _this = this
-    if (_this.$q.localStorage.has('openid')) {
-      _this.openid = _this.$q.localStorage.getItem('openid')
-    } else {
-      _this.openid = ''
-      _this.$q.localStorage.set('openid', '')
-    }
-    if (_this.$q.localStorage.has('login_name')) {
-      _this.login_name = _this.$q.localStorage.getItem('login_name')
-    } else {
-      _this.login_name = ''
-      _this.$q.localStorage.set('login_name', '')
-    }
-    if (_this.$q.localStorage.has('auth')) {
-      _this.authin = '1'
-      _this.getList()
-    } else {
-      _this.authin = '0'
+
+  methods: {
+    // 组合日期并触发更新
+    updateDate() {
+      if (this.year && this.month && this.day) {
+        const dateStr = `${this.year}-${this.pad(this.month)}-${this.pad(this.day)}`
+        this.$emit('update:modelValue', dateStr)
+      }
+    },
+
+    // 补零函数
+    pad(num) {
+      return num.toString().padStart(2, '0')
     }
   },
-  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'
+
+  watch: {
+    // 解析外部传入的日期值
+    modelValue: {
+      immediate: true,
+      handler(value) {
+        if (/^\d{4}-\d{2}-\d{2}$/.test(value)) {
+          const [y, m, d] = value.split('-')
+          this.year = parseInt(y)
+          this.month = parseInt(m)
+          this.day = parseInt(d)
+        }
+      }
     }
-  },
-  updated () {
-  },
-  destroyed () {
   }
 }
-</script>
+</script>

+ 2 - 2
templates/src/pages/outbound/outbound.vue

@@ -9,7 +9,7 @@
         <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="dn" :label="$t('outbound.dn')" icon="img:statics/outbound/dnlist.png" :to="{ name: 'dn' }" exact/>
         </transition>
-        <transition appear enter-active-class="animated zoomIn">
+        <!-- <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="freshorder" :label="$t('outbound.freshorder')" icon="img:statics/outbound/freshorder.png" :to="{ name: 'freshorder' }" exact/>
         </transition>
         <transition appear enter-active-class="animated zoomIn">
@@ -32,7 +32,7 @@
         </transition>
         <transition appear enter-active-class="animated zoomIn">
           <q-route-tab name="pod" :label="$t('outbound.pod')" icon="img:statics/outbound/receiving.png" :to="{ name: 'pod' }" exact/>
-        </transition>
+        </transition> -->
       </q-tabs>
     </div>
   </div>

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1848 - 172
templates/src/pages/outbound/pod.vue


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 163 - 1052
templates/src/pages/task/task.vue


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 603 - 373
templates/src/pages/warehouse/product.vue


+ 12 - 0
templates/yarn.lock

@@ -9400,6 +9400,11 @@ sort-keys@^2.0.0:
   dependencies:
     is-plain-obj "^1.0.0"
 
+sortablejs@1.10.2:
+  version "1.10.2"
+  resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290"
+  integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
+
 source-list-map@^2.0.0:
   version "2.0.1"
   resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
@@ -10569,6 +10574,13 @@ vue@^2.6.10, vue@^2.6.11, vue@^2.7.1:
     "@vue/compiler-sfc" "2.7.8"
     csstype "^3.1.0"
 
+vuedraggable@^2.24.3:
+  version "2.24.3"
+  resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.24.3.tgz#43c93849b746a24ce503e123d5b259c701ba0d19"
+  integrity sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==
+  dependencies:
+    sortablejs "1.10.2"
+
 vuex@3.6.2:
   version "3.6.2"
   resolved "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz"

+ 20 - 2
warehouse/migrations/0001_initial.py

@@ -1,4 +1,4 @@
-# Generated by Django 4.1.2 on 2025-03-28 10:18
+# Generated by Django 4.1.2 on 2025-05-15 13:32
 
 from django.db import migrations, models
 
@@ -11,6 +11,22 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
+        migrations.CreateModel(
+            name='baseset',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('set_name', models.CharField(max_length=255, verbose_name='Set Name')),
+                ('set_value', models.CharField(max_length=9999, verbose_name='Set Value')),
+                ('set_desc', models.CharField(blank=True, max_length=255, null=True, verbose_name='Set Description')),
+                ('is_delete', models.BooleanField(default=False, verbose_name='Delete Label')),
+            ],
+            options={
+                'verbose_name': 'Base Set',
+                'verbose_name_plural': 'Base Set',
+                'db_table': 'baseset',
+                'ordering': ['-id'],
+            },
+        ),
         migrations.CreateModel(
             name='BoundBSListModel',
             fields=[
@@ -137,7 +153,9 @@ class Migration(migrations.Migration):
                 ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                 ('product_code', models.CharField(max_length=255, verbose_name='Product Code')),
                 ('product_name', models.CharField(max_length=255, verbose_name='Product Name')),
-                ('product_std', models.CharField(max_length=255, verbose_name='Product Description')),
+                ('product_std', models.CharField(blank=True, max_length=255, null=True, verbose_name='Product Description')),
+                ('product_unit', models.CharField(blank=True, default='KG', max_length=255, null=True, verbose_name='Product Unit')),
+                ('product_package', models.CharField(blank=True, default='箱', max_length=255, null=True, verbose_name='Product Package')),
                 ('creater', models.CharField(default='first', max_length=255, verbose_name='Who Created')),
                 ('is_delete', models.BooleanField(default=False, verbose_name='Delete Label')),
             ],

+ 0 - 18
warehouse/migrations/0002_productlistmodel_product_unit.py

@@ -1,18 +0,0 @@
-# Generated by Django 4.1.2 on 2025-05-07 10:10
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('warehouse', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='productlistmodel',
-            name='product_unit',
-            field=models.CharField(default='KG', max_length=255, verbose_name='Product Unit'),
-        ),
-    ]

+ 0 - 18
warehouse/migrations/0003_alter_productlistmodel_product_unit.py

@@ -1,18 +0,0 @@
-# Generated by Django 4.1.2 on 2025-05-08 09:05
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('warehouse', '0002_productlistmodel_product_unit'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='productlistmodel',
-            name='product_unit',
-            field=models.CharField(blank=True, default='KG', max_length=255, null=True, verbose_name='Product Unit'),
-        ),
-    ]

+ 0 - 18
warehouse/migrations/0004_alter_productlistmodel_product_std.py

@@ -1,18 +0,0 @@
-# Generated by Django 4.1.2 on 2025-05-08 09:09
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('warehouse', '0003_alter_productlistmodel_product_unit'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='productlistmodel',
-            name='product_std',
-            field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Product Description'),
-        ),
-    ]

+ 0 - 28
warehouse/migrations/0005_baseset.py

@@ -1,28 +0,0 @@
-# Generated by Django 4.1.2 on 2025-05-08 20:40
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('warehouse', '0004_alter_productlistmodel_product_std'),
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='baseset',
-            fields=[
-                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('set_name', models.CharField(max_length=255, verbose_name='Set Name')),
-                ('set_value', models.CharField(max_length=9999, verbose_name='Set Value')),
-                ('set_desc', models.CharField(blank=True, max_length=255, null=True, verbose_name='Set Description')),
-            ],
-            options={
-                'verbose_name': 'Base Set',
-                'verbose_name_plural': 'Base Set',
-                'db_table': 'baseset',
-                'ordering': ['-id'],
-            },
-        ),
-    ]

+ 0 - 18
warehouse/migrations/0006_baseset_is_delete.py

@@ -1,18 +0,0 @@
-# Generated by Django 4.1.2 on 2025-05-12 09:48
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('warehouse', '0005_baseset'),
-    ]
-
-    operations = [
-        migrations.AddField(
-            model_name='baseset',
-            name='is_delete',
-            field=models.BooleanField(default=False, verbose_name='Delete Label'),
-        ),
-    ]

+ 2 - 1
warehouse/models.py

@@ -106,7 +106,8 @@ class ProductListModel(models.Model):
     product_name = models.CharField(max_length=255, verbose_name="Product Name")
     product_std = models.CharField(max_length=255, verbose_name="Product Description", blank=True, null=True)
     product_unit = models.CharField(default='KG', max_length=255, verbose_name="Product Unit", blank=True, null=True)
-
+    product_package = models.CharField(default='箱', max_length=255, verbose_name="Product Package", blank=True, null=True)
+    
     creater = models.CharField(default='first', max_length=255, verbose_name="Who Created")
     is_delete = models.BooleanField(default=False, verbose_name='Delete Label')