2 Commits bbf3cb3e4c ... 05dfcf976a

Author SHA1 Message Date
  flower_bs 05dfcf976a 补充前端 4 weeks ago
  flower_bs aa195ad88b 任务优化 4 weeks ago
100 changed files with 646 additions and 86 deletions
  1. 24 1
      container/filter.py
  2. 44 0
      container/migrations/0035_dispatchconfig_and_more.py
  3. 51 0
      container/migrations/0036_wcstasklogmodel.py
  4. 28 0
      container/migrations/0037_rename_wcs_task_l_task_nu_idx_wcs_task_lo_task_nu_968290_idx_and_more.py
  5. 28 0
      container/migrations/0038_wcstasklogmodel_floor_wcstasklogmodel_is_completed_and_more.py
  6. 71 1
      container/models.py
  7. 22 1
      container/serializers.py
  8. 5 0
      container/urls.py
  9. 333 45
      container/views.py
  10. 0 1
      templates/dist/spa/css/34.5557cd4a.css
  11. 1 0
      templates/dist/spa/css/34.6a5a5158.css
  12. 1 0
      templates/dist/spa/css/35.56a2e799.css
  13. 0 0
      templates/dist/spa/css/36.8f3f6188.css
  14. 0 0
      templates/dist/spa/css/37.44ddcebd.css
  15. 0 0
      templates/dist/spa/css/38.2ac1dad1.css
  16. 0 0
      templates/dist/spa/css/39.12670fd1.css
  17. 0 0
      templates/dist/spa/css/40.9478c981.css
  18. 0 0
      templates/dist/spa/css/41.c4652654.css
  19. 0 0
      templates/dist/spa/css/42.7a23b7fb.css
  20. 0 0
      templates/dist/spa/css/43.07732723.css
  21. 0 0
      templates/dist/spa/css/44.2594d0b9.css
  22. 0 0
      templates/dist/spa/css/45.0faa4aeb.css
  23. 1 1
      templates/dist/spa/index.html
  24. 1 0
      templates/dist/spa/js/34.2315457a.js
  25. BIN
      templates/dist/spa/js/34.2315457a.js.gz
  26. 0 1
      templates/dist/spa/js/34.cb30dfa4.js
  27. BIN
      templates/dist/spa/js/34.cb30dfa4.js.gz
  28. BIN
      templates/dist/spa/js/35.73cf8fc6.js.gz
  29. 1 0
      templates/dist/spa/js/35.83bfe8ac.js
  30. BIN
      templates/dist/spa/js/35.83bfe8ac.js.gz
  31. 1 1
      templates/dist/spa/js/35.73cf8fc6.js
  32. BIN
      templates/dist/spa/js/36.0ef8719a.js.gz
  33. BIN
      templates/dist/spa/js/37.54c90e2b.js.gz
  34. 1 1
      templates/dist/spa/js/36.04984167.js
  35. BIN
      templates/dist/spa/js/36.04984167.js.gz
  36. 1 1
      templates/dist/spa/js/37.54c90e2b.js
  37. BIN
      templates/dist/spa/js/38.51ed54fd.js.gz
  38. 1 1
      templates/dist/spa/js/38.96100c54.js
  39. BIN
      templates/dist/spa/js/38.96100c54.js.gz
  40. 1 1
      templates/dist/spa/js/39.5872b98c.js
  41. 1 1
      templates/dist/spa/js/40.d194e156.js
  42. BIN
      templates/dist/spa/js/42.95116c09.js.gz
  43. 1 1
      templates/dist/spa/js/41.eed3ba0d.js
  44. 1 1
      templates/dist/spa/js/42.95116c09.js
  45. BIN
      templates/dist/spa/js/43.b9cd2573.js.gz
  46. 1 1
      templates/dist/spa/js/43.b1600ae8.js
  47. 1 1
      templates/dist/spa/js/44.29b294d8.js
  48. BIN
      templates/dist/spa/js/45.85676583.js.gz
  49. 1 1
      templates/dist/spa/js/45.85676583.js
  50. BIN
      templates/dist/spa/js/46.076c5ed4.js.gz
  51. 1 1
      templates/dist/spa/js/46.1e66a908.js
  52. 1 1
      templates/dist/spa/js/47.a5d0cc0c.js
  53. BIN
      templates/dist/spa/js/48.f5f2c346.js.gz
  54. 1 1
      templates/dist/spa/js/48.f5f2c346.js
  55. BIN
      templates/dist/spa/js/49.902b6c7e.js.gz
  56. 1 1
      templates/dist/spa/js/49.05df0690.js
  57. 1 1
      templates/dist/spa/js/50.b2e076a3.js
  58. BIN
      templates/dist/spa/js/50.b2e076a3.js.gz
  59. BIN
      templates/dist/spa/js/51.f4758ae8.js.gz
  60. BIN
      templates/dist/spa/js/52.3e375429.js.gz
  61. 1 1
      templates/dist/spa/js/51.f4758ae8.js
  62. BIN
      templates/dist/spa/js/52.4dece508.js.gz
  63. BIN
      templates/dist/spa/js/53.4bd72b33.js.gz
  64. 1 1
      templates/dist/spa/js/52.3e375429.js
  65. BIN
      templates/dist/spa/js/53.ebc1d2e4.js.gz
  66. BIN
      templates/dist/spa/js/54.13366a89.js.gz
  67. 1 1
      templates/dist/spa/js/53.4bd72b33.js
  68. BIN
      templates/dist/spa/js/54.245bcbe5.js.gz
  69. 1 1
      templates/dist/spa/js/54.13366a89.js
  70. BIN
      templates/dist/spa/js/55.f01fcdbb.js.gz
  71. BIN
      templates/dist/spa/js/55.f93ef6f0.js.gz
  72. 1 1
      templates/dist/spa/js/55.f93ef6f0.js
  73. BIN
      templates/dist/spa/js/56.18593e4b.js.gz
  74. BIN
      templates/dist/spa/js/56.8954c039.js.gz
  75. 1 1
      templates/dist/spa/js/56.8954c039.js
  76. BIN
      templates/dist/spa/js/57.19369fb4.js.gz
  77. BIN
      templates/dist/spa/js/57.21c754a8.js.gz
  78. 1 1
      templates/dist/spa/js/57.21c754a8.js
  79. BIN
      templates/dist/spa/js/58.95ed7156.js.gz
  80. BIN
      templates/dist/spa/js/58.b42c1c9b.js.gz
  81. 1 1
      templates/dist/spa/js/58.b42c1c9b.js
  82. BIN
      templates/dist/spa/js/59.766cb550.js.gz
  83. BIN
      templates/dist/spa/js/59.9471f3f5.js.gz
  84. BIN
      templates/dist/spa/js/60.09da2c41.js.gz
  85. 1 1
      templates/dist/spa/js/59.9471f3f5.js
  86. BIN
      templates/dist/spa/js/60.a3cbc9ce.js.gz
  87. 1 1
      templates/dist/spa/js/60.09da2c41.js
  88. BIN
      templates/dist/spa/js/61.50192f42.js.gz
  89. BIN
      templates/dist/spa/js/61.e06a8283.js.gz
  90. 1 1
      templates/dist/spa/js/61.e06a8283.js
  91. BIN
      templates/dist/spa/js/62.5061b3f4.js.gz
  92. 1 1
      templates/dist/spa/js/62.0b2ea8c5.js
  93. 1 1
      templates/dist/spa/js/63.9083683a.js
  94. 1 1
      templates/dist/spa/js/64.d54e6cb7.js
  95. 1 1
      templates/dist/spa/js/65.104af83e.js
  96. 1 1
      templates/dist/spa/js/66.def1e808.js
  97. 1 1
      templates/dist/spa/js/67.1d2c8112.js
  98. 1 1
      templates/dist/spa/js/68.348fe91e.js
  99. 1 1
      templates/dist/spa/js/69.42a4eaa4.js
  100. 0 0
      templates/dist/spa/js/70.f5f7974b.js.gz

+ 24 - 1
container/filter.py

@@ -1,5 +1,5 @@
 from django_filters import FilterSet, NumberFilter, CharFilter
-from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel,ContainerWCSModel,ContainerDetailLogModel,batchLogModel
+from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel,ContainerWCSModel,ContainerDetailLogModel,batchLogModel,WCSTaskLogModel
 
 from django_filters import rest_framework as filters
 
@@ -139,4 +139,27 @@ class TaskFilter(FilterSet):
             "container_detail__goods_code": ['exact', 'icontains'],
             "container_detail__goods_desc": ['exact', 'icontains'],  
             }
+
+class WCSTaskLogFilter(FilterSet):
+    taskNumber = filters.CharFilter(field_name='task_number', lookup_expr='icontains')
+    class Meta:
+        model = WCSTaskLogModel
+        fields = {
+            "id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "task_number": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "container": ['exact', 'icontains'],
+            "current_location": ['exact', 'icontains'],
+            "target_location": ['exact', 'icontains'],
+            "location_group_id": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "access_priority": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "left_priority": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "right_priority": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "task_type": ['exact', 'icontains'],
+            "order_number": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "sequence": ['exact', 'gt', 'gte', 'lt', 'lte', 'isnull', 'in', 'range'],
+            "send_time": ['exact', 'gt', 'gte', 'lt', 'lte', 'range'],
+            "floor": ['exact', 'icontains'],
+            "log_type": ['exact', 'icontains'],
+            "is_completed": ['exact'],
+        }
         

+ 44 - 0
container/migrations/0035_dispatchconfig_and_more.py

@@ -0,0 +1,44 @@
+# Generated by Django 4.1.2 on 2025-11-17 10:08
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0034_materialchangehistory_count_time'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='DispatchConfig',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('cross_floor_concurrent_limit', models.PositiveIntegerField(default=2, verbose_name='跨楼层并发上限')),
+                ('intra_floor_order', models.CharField(choices=[('batch_then_sequence', '按批次优先,再按顺序')], default='batch_then_sequence', max_length=64, verbose_name='同层排序策略')),
+                ('enabled', models.BooleanField(default=True, verbose_name='是否启用')),
+                ('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
+            ],
+            options={
+                'verbose_name': '任务下发配置',
+                'verbose_name_plural': '任务下发配置',
+                'db_table': 'dispatch_config',
+                'ordering': ['-id'],
+            },
+        ),
+        migrations.AlterField(
+            model_name='containerdetaillogmodel',
+            name='new_status',
+            field=models.IntegerField(blank=True, choices=[(0, '空盘'), (2, '在盘'), (3, '离库')], null=True, verbose_name='新状态'),
+        ),
+        migrations.AlterField(
+            model_name='containerdetaillogmodel',
+            name='old_status',
+            field=models.IntegerField(blank=True, choices=[(0, '空盘'), (2, '在盘'), (3, '离库')], null=True, verbose_name='原状态'),
+        ),
+        migrations.AlterField(
+            model_name='containerdetailmodel',
+            name='status',
+            field=models.IntegerField(choices=[(0, '空盘'), (2, '在盘'), (3, '离库')], default=0, verbose_name='状态'),
+        ),
+    ]

+ 51 - 0
container/migrations/0036_wcstasklogmodel.py

@@ -0,0 +1,51 @@
+# Generated manually for WCSTaskLogModel
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0035_dispatchconfig_and_more'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='WCSTaskLogModel',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('task_number', models.BigIntegerField(verbose_name='任务号')),
+                ('container', models.CharField(max_length=50, verbose_name='托盘号')),
+                ('current_location', models.CharField(max_length=100, verbose_name='起始位置')),
+                ('target_location', models.CharField(max_length=100, verbose_name='目标位置')),
+                ('location_group_id', models.IntegerField(blank=True, null=True, verbose_name='库位组ID')),
+                ('access_priority', models.IntegerField(blank=True, null=True, verbose_name='靠里程度优先级')),
+                ('left_priority', models.IntegerField(blank=True, null=True, verbose_name='左侧优先级')),
+                ('right_priority', models.IntegerField(blank=True, null=True, verbose_name='右侧优先级')),
+                ('task_type', models.CharField(blank=True, max_length=50, null=True, verbose_name='任务类型')),
+                ('order_number', models.IntegerField(blank=True, null=True, verbose_name='订单号')),
+                ('sequence', models.IntegerField(blank=True, null=True, verbose_name='序列号')),
+                ('send_time', models.DateTimeField(auto_now_add=True, verbose_name='发送时间')),
+                ('response_data', models.JSONField(blank=True, null=True, verbose_name='WCS返回结果')),
+            ],
+            options={
+                'verbose_name': 'WCS任务发送日志',
+                'verbose_name_plural': 'WCS任务发送日志',
+                'db_table': 'wcs_task_log',
+                'ordering': ['-send_time'],
+            },
+        ),
+        migrations.AddIndex(
+            model_name='wcstasklogmodel',
+            index=models.Index(fields=['task_number'], name='wcs_task_l_task_nu_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='wcstasklogmodel',
+            index=models.Index(fields=['container'], name='wcs_task_l_contain_idx'),
+        ),
+        migrations.AddIndex(
+            model_name='wcstasklogmodel',
+            index=models.Index(fields=['send_time'], name='wcs_task_l_send_ti_idx'),
+        ),
+    ]
+

+ 28 - 0
container/migrations/0037_rename_wcs_task_l_task_nu_idx_wcs_task_lo_task_nu_968290_idx_and_more.py

@@ -0,0 +1,28 @@
+# Generated by Django 4.1.2 on 2025-11-17 10:22
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0036_wcstasklogmodel'),
+    ]
+
+    operations = [
+        migrations.RenameIndex(
+            model_name='wcstasklogmodel',
+            new_name='wcs_task_lo_task_nu_968290_idx',
+            old_name='wcs_task_l_task_nu_idx',
+        ),
+        migrations.RenameIndex(
+            model_name='wcstasklogmodel',
+            new_name='wcs_task_lo_contain_22390a_idx',
+            old_name='wcs_task_l_contain_idx',
+        ),
+        migrations.RenameIndex(
+            model_name='wcstasklogmodel',
+            new_name='wcs_task_lo_send_ti_fc233e_idx',
+            old_name='wcs_task_l_send_ti_idx',
+        ),
+    ]

+ 28 - 0
container/migrations/0038_wcstasklogmodel_floor_wcstasklogmodel_is_completed_and_more.py

@@ -0,0 +1,28 @@
+# Generated by Django 4.1.2 on 2025-11-17 16:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0037_rename_wcs_task_l_task_nu_idx_wcs_task_lo_task_nu_968290_idx_and_more'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='wcstasklogmodel',
+            name='floor',
+            field=models.CharField(blank=True, max_length=20, null=True, verbose_name='楼层'),
+        ),
+        migrations.AddField(
+            model_name='wcstasklogmodel',
+            name='is_completed',
+            field=models.BooleanField(default=False, verbose_name='是否完成'),
+        ),
+        migrations.AddField(
+            model_name='wcstasklogmodel',
+            name='log_type',
+            field=models.CharField(default='outbound', max_length=32, verbose_name='日志类型'),
+        ),
+    ]

+ 71 - 1
container/models.py

@@ -808,4 +808,74 @@ class out_batch_detail(models.Model):
         db_table = 'out_batch_detail'
         verbose_name = 'OutBatchDetail'
         verbose_name_plural = "OutBatchDetail"
-        ordering = ['container']
+        ordering = ['container']
+
+class DispatchConfig(models.Model):
+    """
+    任务下发调度配置
+    - cross_floor_concurrent_limit: 跨楼层并发下发上限(不同楼层同时下发的任务数)
+    - intra_floor_order: 同层排序策略(当前仅支持:batch_then_sequence)
+    - enabled: 是否启用
+    """
+    INTRA_FLOOR_ORDER_CHOICES = (
+        ('batch_then_sequence', '按批次优先,再按顺序'),
+    )
+    cross_floor_concurrent_limit = models.PositiveIntegerField(
+        default=2,
+        verbose_name='跨楼层并发上限'
+    )
+    intra_floor_order = models.CharField(
+        max_length=64,
+        choices=INTRA_FLOOR_ORDER_CHOICES,
+        default='batch_then_sequence',
+        verbose_name='同层排序策略'
+    )
+    enabled = models.BooleanField(default=True, verbose_name='是否启用')
+    update_time = models.DateTimeField(auto_now=True, verbose_name='更新时间')
+
+    class Meta:
+        db_table = 'dispatch_config'
+        verbose_name = '任务下发配置'
+        verbose_name_plural = "任务下发配置"
+        ordering = ['-id']
+
+    @classmethod
+    def get_active_config(cls):
+        config = cls.objects.filter(enabled=True).first()
+        if not config:
+            config = cls.objects.create()
+        return config
+
+
+class WCSTaskLogModel(models.Model):
+    """
+    WCS 任务发送日志记录
+    每当系统发送任务到 WCS 时异步记录(不阻塞发送)
+    """
+    task_number = models.BigIntegerField(verbose_name='任务号')
+    container = models.CharField(max_length=50, verbose_name='托盘号')
+    current_location = models.CharField(max_length=100, verbose_name='起始位置')
+    target_location = models.CharField(max_length=100, verbose_name='目标位置')
+    location_group_id = models.IntegerField(null=True, blank=True, verbose_name='库位组ID')
+    access_priority = models.IntegerField(null=True, blank=True, verbose_name='靠里程度优先级')
+    left_priority = models.IntegerField(null=True, blank=True, verbose_name='左侧优先级')
+    right_priority = models.IntegerField(null=True, blank=True, verbose_name='右侧优先级')
+    task_type = models.CharField(max_length=50, null=True, blank=True, verbose_name='任务类型')
+    order_number = models.IntegerField(null=True, blank=True, verbose_name='订单号')
+    sequence = models.IntegerField(null=True, blank=True, verbose_name='序列号')
+    send_time = models.DateTimeField(auto_now_add=True, verbose_name='发送时间')
+    response_data = models.JSONField(null=True, blank=True, verbose_name='WCS返回结果')
+    floor = models.CharField(max_length=20, null=True, blank=True, verbose_name='楼层')
+    is_completed = models.BooleanField(default=False, verbose_name='是否完成')
+    log_type = models.CharField(max_length=32, default='outbound', verbose_name='日志类型')
+    
+    class Meta:
+        db_table = 'wcs_task_log'
+        verbose_name = 'WCS任务发送日志'
+        verbose_name_plural = "WCS任务发送日志"
+        ordering = ['-send_time']
+        indexes = [
+            models.Index(fields=['task_number']),
+            models.Index(fields=['container']),
+            models.Index(fields=['send_time']),
+        ]

+ 22 - 1
container/serializers.py

@@ -1,6 +1,6 @@
 from rest_framework import serializers
 
-from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel,ContainerWCSModel,out_batch_detail,ContainerDetailLogModel,batchLogModel
+from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel,ContainerWCSModel,out_batch_detail,ContainerDetailLogModel,batchLogModel,DispatchConfig,WCSTaskLogModel
 from bound.models import BoundBatchModel,BoundDetailModel
 
 from utils import datasolve
@@ -367,3 +367,24 @@ class OutBoundFullDetailSerializer(serializers.ModelSerializer):
         model = out_batch_detail
         fields = '__all__'
         read_only_fields = ['id', 'out_bound', 'container', 'container_detail', 'working']
+
+class DispatchConfigSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = DispatchConfig
+        fields = ['id', 'cross_floor_concurrent_limit', 'intra_floor_order', 'enabled', 'update_time']
+        read_only_fields = ['id', 'update_time']
+
+
+class WCSTaskLogSerializer(serializers.ModelSerializer):
+    time = serializers.DateTimeField(source='send_time', format='%Y-%m-%d %H:%M:%S', read_only=True)
+    taskNumber = serializers.IntegerField(source='task_number', read_only=True)
+    
+    class Meta:
+        model = WCSTaskLogModel
+        fields = [
+            'id', 'time', 'taskNumber', 'container', 'current_location', 
+            'target_location', 'location_group_id', 'access_priority', 
+            'left_priority', 'right_priority', 'task_type', 'order_number', 
+            'sequence', 'response_data', 'send_time', 'floor', 'is_completed', 'log_type'
+        ]
+        read_only_fields = ['id', 'send_time']

+ 5 - 0
container/urls.py

@@ -38,6 +38,11 @@ re_path(r'^operate/(?P<pk>\d+)/$', views.ContainerOperateViewSet.as_view({
 
 path(r'wcs_task/', views.WCSTaskViewSet.as_view({"get": "list", "post": "create"}), name="Task"),
 path(r'send_again/', views.WCSTaskViewSet.as_view({"post": "send_task_to_wcs"}), name="Task"),
+path(r'dispatch_config/', views.DispatchConfigView.as_view(), name='dispatch_config'),
+path(r'wcs/logs/', views.WCSTaskLogViewSet.as_view({"get": "list"}), name='wcs_task_logs'),
+re_path(r'^wcs/logs/(?P<pk>\d+)/$', views.WCSTaskLogViewSet.as_view({
+    'get': 'retrieve',
+}), name="wcs_task_logs_1"),
 
 path(r'task/', views.TaskViewSet.as_view({"get": "list", "post": "create"}), name="Task"),
 re_path(r'^task/(?P<pk>\d+)/$', views.TaskViewSet.as_view({

+ 333 - 45
container/views.py

@@ -14,21 +14,26 @@ import requests
 from django.db import transaction
 import logging
 from rest_framework import status
-from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,ContainerWCSModel,TaskModel,out_batch_detail,ContainerDetailLogModel,batchLogModel
+from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,ContainerWCSModel,TaskModel,out_batch_detail,ContainerDetailLogModel,batchLogModel,WCSTaskLogModel
 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,OutBatchModel,BatchOperateLogModel
+from django.conf import settings
+from rest_framework.views import APIView
+from rest_framework.response import Response as DRFResponse
+import os
+import re
 
 from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSerializer,ContainerDetailSimpleGetSerializer,ContainerDetailPutSerializer
 from .serializers import ContainerListGetSerializer,ContainerListPostSerializer
 from .serializers import ContainerOperationGetSerializer,ContainerOperationPostSerializer
 from .serializers import TaskGetSerializer,TaskPostSerializer
-from .serializers import WCSTaskGetSerializer
+from .serializers import WCSTaskGetSerializer,WCSTaskLogSerializer
 from .serializers import OutBoundFullDetailSerializer,OutBoundDetailSerializer
 from .serializers import ContainerDetailLogSerializer
 from .serializers import batchLogModelSerializer
-from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter,WCSTaskFilter,ContainerDetailLogFilter,batchLogFilter
+from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter,WCSTaskFilter,ContainerDetailLogFilter,batchLogFilter,WCSTaskLogFilter
 
 from rest_framework.permissions import AllowAny
 import threading
@@ -40,6 +45,8 @@ from staff.models import ListModel as StaffListModel
 from operation_log.views import log_success_operation, log_failure_operation, log_operation
 logger = logging.getLogger(__name__)
 loggertask = logging.getLogger('wms.WCSTask')
+from .models import DispatchConfig
+from .serializers import DispatchConfigSerializer
 
 
 # 托盘分类视图
@@ -1315,14 +1322,30 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
             if not success:
                 return Response({'code': '500', 'message': '出库状态更新失败', 'data': None},
                                 status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-            OutboundService.process_next_task()
+            # WCS完成一条任务后,只下发一条新任务,优先同楼层
+            # 从刚完成的任务中提取楼层信息
+            preferred_layer = None
+            try:
+                parts = str(task.current_location).split('-')
+                preferred_layer = parts[3] if len(parts) >= 4 else None
+            except Exception:
+                pass
+            OutboundService.process_next_task(single_task=True, preferred_layer=preferred_layer)
 
         if task and task.tasktype == 'check' and task.status == 300:
             success = self.handle_outbound_completion(container_obj, task)
             if not success:
                 return Response({'code': '500', 'message': '出库状态更新失败', 'data': None},
                                 status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-            OutboundService.process_next_task()
+            # WCS完成一条任务后,只下发一条新任务,优先同楼层
+            # 从刚完成的任务中提取楼层信息
+            preferred_layer = None
+            try:
+                parts = str(task.current_location).split('-')
+                preferred_layer = parts[3] if len(parts) >= 4 else None
+            except Exception:
+                pass
+            OutboundService.process_next_task(single_task=True, preferred_layer=preferred_layer)
         
         return Response({
             'code': '200',
@@ -1348,6 +1371,14 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
             task.message = '任务已完成'
             task.working = 0
             task.save()
+            try:
+                original_task_number = data.get('taskNumber')
+                if original_task_number is not None:
+                    WCSTaskLogModel.objects.filter(
+                        task_number=original_task_number
+                    ).update(is_completed=True)
+            except Exception as log_error:
+                logger.warning(f"更新任务日志完成状态失败: {log_error}")
 
         return task
 
@@ -2140,6 +2171,24 @@ class OutboundService:
         }
         loggertask.info(f"任务号:{task.tasknumber-20000000000}任务发送请求:{task.container},起始位置:{task.current_location},目标位置:{task.target_location},返回结果:{task_data}")
         
+        # 异步记录日志到数据库(不阻塞发送)
+        log_thread = threading.Thread(
+            target=OutboundService._async_log_handler,
+            kwargs={
+                'task_number': task.tasknumber - 20000000000,
+                'container': str(task.container),
+                'current_location': task.current_location,
+                'target_location': task.target_location,
+                'task_type': task.tasktype,
+                'order_number': task.order_number,
+                'sequence': task.sequence,
+                'response_data': task_data,
+                'log_type': task.tasktype or 'outbound',
+            },
+            daemon=True
+        )
+        log_thread.start()
+        
         # 创建并启动线程
         thread = threading.Thread(
             target=OutboundService._async_send_handler,
@@ -2149,6 +2198,57 @@ class OutboundService:
         thread.start()
         return True  # 立即返回表示已开始处理
 
+    @staticmethod
+    def _async_log_handler(task_number, container, current_location, target_location, task_type, order_number, sequence, response_data, log_type='outbound'):
+        """异步记录 WCS 任务发送日志到数据库(不阻塞发送)"""
+        try:
+            close_old_connections()
+            
+            # 解析库位组与优先级
+            group_id = None
+            access_priority = None
+            left_priority = None
+            right_priority = None
+            floor = None
+            try:
+                parts = current_location.split('-')
+                if len(parts) >= 4:
+                    row = int(parts[1])
+                    col = int(parts[2])
+                    layer = int(parts[3])
+                    floor = parts[3]
+                    loc = LocationModel.objects.filter(row=row, col=col, layer=layer).first()
+                    if loc:
+                        group_code = loc.location_group
+                        group = LocationGroupModel.objects.filter(group_code=group_code).first()
+                        if group:
+                            group_id = group.id
+                            access_priority = loc.c_number
+                            left_priority = group.left_priority
+                            right_priority = group.right_priority
+            except Exception as e:
+                logger.error(f"解析库位组信息失败: {e}")
+            
+            # 创建日志记录
+            WCSTaskLogModel.objects.create(
+                task_number=task_number,
+                container=container,
+                current_location=current_location,
+                target_location=target_location,
+                location_group_id=group_id,
+                access_priority=access_priority,
+                left_priority=left_priority,
+                right_priority=right_priority,
+                task_type=task_type,
+                order_number=order_number,
+                sequence=sequence,
+                response_data=response_data,
+                floor=floor,
+                log_type=log_type or task_type or 'outbound',
+            )
+        except Exception as e:
+            logger.error(f"记录 WCS 任务日志失败: {e}", exc_info=True)
+
     @staticmethod
     def _async_send_handler(task_id, send_data):
         """异步处理的实际工作函数"""
@@ -2160,12 +2260,12 @@ class OutboundService:
             task = ContainerWCSModel.objects.get(pk=task_id)
             
             # 发送第一个请求(不处理结果)
-            requests.post(
-                "http://127.0.0.1:8008/container/batch/",
-                json=send_data,
-                timeout=10
-            )
-            
+            # requests.post(
+                # "http://127.0.0.1:8008/container/batch/",
+                # json=send_data,
+                # timeout=10
+            # )
+            # 
             # 发送关键请求
             response = requests.post(
                 "http://192.168.18.200:1616/wcs/WebApi/getOutTask",
@@ -2190,25 +2290,59 @@ class OutboundService:
 
     @staticmethod
     def create_initial_tasks(container_list,bound_list_id):
-        """生成初始任务队列"""
+        """生成初始任务队列,返回楼层信息用于初始化发送"""
         with transaction.atomic():
-            current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id).first()
+            current_WCS = ContainerWCSModel.objects.filter(
+                tasktype='outbound',
+                bound_list_id=bound_list_id,
+                is_delete=False,
+                status__lt=300
+            ).first()
             if current_WCS:
                 logger.error(f"当前{bound_list_id}已有出库任务")
-                return False
+                return {
+                    "success": False,
+                    "msg": f"出库申请 {bound_list_id} 仍有待完成任务",
+                }
             tasks = []
+            task_layers = set()
             start_sequence = ContainerWCSModel.objects.filter(tasktype='outbound').count() + 1
             tasknumber = ContainerWCSModel.objects.filter().count() 
             tasknumber_index = 1
             for index, container in enumerate(container_list, start=start_sequence):
                 container_obj = ContainerListModel.objects.filter(id =container['container_number']).first()
+                if not container_obj:
+                    logger.error(f"托盘记录 {container['container_number']} 不存在")
+                    return {
+                        "success": False,
+                        "msg": "托盘信息缺失,无法创建任务",
+                    }
                 if container_obj.current_location != container_obj.target_location:
                     logger.error(f"托盘 {container_obj.container_code} 未到达目的地,不生成任务")
-                    return False
+                    return {
+                        "success": False,
+                        "msg": f"托盘 {container_obj.container_code} 未处于可出库状态",
+                    }
+                # 检查前序作业
+                existing_task = ContainerWCSModel.objects.filter(
+                    container=container_obj.container_code,
+                    working=1,
+                    status__lt=300,
+                    is_delete=False
+                ).exists()
+                if existing_task:
+                    logger.error(f"托盘 {container_obj.container_code} 仍有未完成任务")
+                    return {
+                        "success": False,
+                        "msg": f"托盘 {container_obj.container_code} 仍有未完成任务",
+                    }
                 OutBoundDetail_obj = OutBoundDetailModel.objects.filter(bound_list=bound_list_id,bound_batch_number_id=container['batch_id']).first()
                 if not OutBoundDetail_obj:
                     logger.error(f"批次 {container['batch_id']} 不存在")
-                    return False
+                    return {
+                        "success": False,
+                        "msg": f"批次 {container['batch_id']} 不存在",
+                    }
                 month = int(timezone.now().strftime("%Y%m"))
                 task = ContainerWCSModel(
                     taskid=OutboundService.generate_task_id(),
@@ -2227,6 +2361,15 @@ class OutboundService:
                     message="等待出库",
                     status=100,
                 )
+                layer = None
+                try:
+                    parts = str(task.current_location).split('-')
+                    if len(parts) >= 4:
+                        layer = parts[3]
+                except Exception:
+                    layer = None
+                if layer:
+                    task_layers.add(layer)
                 tasknumber_index += 1
                 tasks.append(task)
                 container_obj = ContainerListModel.objects.filter(container_code=task.container).first()
@@ -2234,6 +2377,11 @@ class OutboundService:
                 container_obj.save()
             ContainerWCSModel.objects.bulk_create(tasks)
             logger.info(f"已创建 {len(tasks)} 个初始任务")
+            return {
+                "success": True,
+                "layers": sorted(task_layers),
+                "task_count": len(tasks),
+            }
     
     @staticmethod
     def create_initial_check_tasks(container_list,batch_id):
@@ -2319,11 +2467,19 @@ class OutboundService:
             logger.info(f"已插入 {len(new_tasks)} 个新任务")
 
     @staticmethod
-    def process_next_task():
-        """处理下一个任务 - 优化:同一批次连续出,支持一次下发两条任务"""
+    def process_next_task(single_task=False, preferred_layer=None, initial_layers=None):
+        """处理下一个任务 - 支持前端可配置的跨楼层并发与同层排序
+        
+        Args:
+            single_task: 如果为True,只下发一条任务(用于WCS完成回调场景)
+                        如果为False,批量下发多条任务(用于初始下发场景)
+            preferred_layer: 优先选择的楼层(用于single_task=True时,优先同楼层任务)
+        """
         # 获取待处理任务,优先按批次排序(同一批次连续出),同一批次内按sequence排序
         # 使用Case处理batch_out为None的情况,确保有批次的任务优先
         from django.db.models import F, Case, When, IntegerField
+        from django.conf import settings
+        from .models import DispatchConfig
         
         def get_pending_tasks():
             """获取待处理任务查询集"""
@@ -2340,28 +2496,97 @@ class OutboundService:
                 )
             ).order_by('batch_out_id_for_sort', 'sequence')
         
+        def extract_layer(location):
+            """从位置字符串中提取楼层信息"""
+            try:
+                parts = str(location).split('-')
+                return parts[3] if len(parts) >= 4 else None
+            except Exception:
+                return None
+        
         pending_tasks = get_pending_tasks()
         
         if not pending_tasks.exists():
             logger.info("没有待处理任务")
             return
         
-        # 处理任务列表(最多处理2条,保证WCS有缓冲)
+        # 读取调度配置(默认2条跨楼层并发)
+        cfg = DispatchConfig.get_active_config()
+        cross_floor_limit = max(1, int(cfg.cross_floor_concurrent_limit or 2))
+        desired_layers = set(initial_layers or [])
+        # 处理任务列表
+        # 如果single_task=True,只下发1条;否则批量下发(最多cross_floor_limit条)
         processed_count = 0
-        max_tasks = 2
+        if desired_layers:
+            max_tasks = max(len(desired_layers), 1)
+        else:
+            max_tasks = 1 if single_task else cross_floor_limit
         skip_count = 0
-        max_skip = 5  # 最多跳过5个任务,避免无限循环
+        max_skip = max(20, len(desired_layers) * 5)
         dispatched_ids = set()
+        used_layers = set()
         
         while processed_count < max_tasks and skip_count < max_skip:
             # 重新获取待处理任务(因为可能有任务被跳过)
             pending_tasks = get_pending_tasks().exclude(pk__in=dispatched_ids)
             if not pending_tasks.exists():
                 break
+            if desired_layers and used_layers.issuperset(desired_layers):
+                logger.info("已完成初始多楼层任务的分发")
+                break
+            
+            # 如果single_task=True且提供了preferred_layer,优先选择同楼层的任务
+            next_task = None
+            if desired_layers:
+                remaining_layers = desired_layers - used_layers
+                target_layers = remaining_layers if remaining_layers else desired_layers
+                prioritized = []
+                fallback = []
+                for task in pending_tasks:
+                    task_layer = extract_layer(task.current_location)
+                    if task_layer in target_layers:
+                        prioritized.append(task)
+                    else:
+                        fallback.append(task)
+                if prioritized:
+                    next_task = prioritized[0]
+                elif remaining_layers:
+                    skip_count += 1
+                    continue
+                elif fallback:
+                    next_task = fallback[0]
+            elif single_task and preferred_layer:
+                # 先尝试找同楼层的任务(在同楼层任务中,仍然按批次和sequence排序)
+                same_layer_tasks = []
+                other_layer_tasks = []
+                for task in pending_tasks:
+                    task_layer = extract_layer(task.current_location)
+                    if task_layer == preferred_layer:
+                        same_layer_tasks.append(task)
+                    else:
+                        other_layer_tasks.append(task)
+                
+                # 优先从同楼层任务中选择
+                if same_layer_tasks:
+                    next_task = same_layer_tasks[0]
+                    logger.info(f"优先选择同楼层任务,楼层: {preferred_layer}, 任务: {next_task.taskid}")
+                # 如果没找到同楼层的任务,使用第一个任务(按批次和sequence排序)
+                elif other_layer_tasks:
+                    next_task = other_layer_tasks[0]
+                    logger.info(f"未找到同楼层任务,使用其他楼层任务,任务: {next_task.taskid}")
+                else:
+                    next_task = pending_tasks.first()
+            else:
+                # 根据同层排序策略,仍旧使用 batch_then_sequence(已在order_by体现)
+                next_task = pending_tasks.first()
             
-            next_task = pending_tasks.first()
+            if not next_task:
+                break
+                
             dispatched_ids.add(next_task.pk)
             location = next_task.current_location
+            # 解析楼层(假设格式 Wxx-row-col-layer)
+            task_layer = extract_layer(location)
             
             if location == '103' or location == '203':
                 logger.info(f"需要跳过该任务: {next_task.taskid}, 位置: {location}")
@@ -2372,9 +2597,14 @@ class OutboundService:
                 # 跳过这个任务后,继续处理下一个
                 continue
             
+            # 跨楼层并发控制:同一轮不允许重复楼层(仅批量下发时生效)
+            if not single_task and not desired_layers and task_layer and task_layer in used_layers:
+                skip_count += 1
+                continue
+            
             try:
                 allocator = LocationAllocation()
-                allocation_success = OutboundService.perform_initial_allocation(
+                allocation_success = perform_initial_allocation(
                     allocator,
                     next_task.current_location
                 )
@@ -2387,6 +2617,8 @@ class OutboundService:
                 next_task.status = 150
                 next_task.save(update_fields=['status'])
                 processed_count += 1
+                if task_layer:
+                    used_layers.add(task_layer)
                 logger.info(f"成功下发任务: {next_task.taskid}, 批次: {next_task.batch_out_id if next_task.batch_out else '无批次'}")
             except Exception as e:
                 logger.error(f"任务处理失败: {next_task.taskid}, 错误: {str(e)}")
@@ -2404,31 +2636,77 @@ class OutboundService:
         try:
             task = ContainerWCSModel.objects.get(taskid=task_id)
             allocator = LocationAllocation()
-            OutboundService.perform_initial_allocation(allocator, task.current_location)
+            perform_initial_allocation(allocator, task.current_location)
             OutboundService.send_task_to_wcs(task)
         except Exception as e:
             logger.error(f"任务处理失败: {str(e)}")
       
 
 
-    def perform_initial_allocation(allocator, location):
-        """执行初始库位分配操作"""
-        location_row = location.split('-')[1]
-        location_col = location.split('-')[2]
-        location_layer = location.split('-')[3]
-        location_code = LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first().location_code
-        if not location_code:
-            logger.error(f"未找到库位: {location}")
-        operations = [
-            (allocator.update_location_status,location_code, 'reserved'),
-            (allocator.update_location_group_status,location_code)
-        ]
-        
-        for func, *args in operations:
-            if not func(*args):
-                logger.error(f"分配操作失败: {func.__name__}")
-                return False
-        return True
+class DispatchConfigView(APIView):
+    """
+    获取/更新任务下发调度配置
+    GET: 返回当前启用的配置
+    PUT: 更新配置(cross_floor_concurrent_limit, intra_floor_order, enabled)
+    """
+
+    def get(self, request):
+        cfg = DispatchConfig.get_active_config()
+        return DRFResponse(DispatchConfigSerializer(cfg).data, status=200)
+
+    def put(self, request):
+        cfg = DispatchConfig.get_active_config()
+        serializer = DispatchConfigSerializer(cfg, data=request.data, partial=True)
+        serializer.is_valid(raise_exception=True)
+        serializer.save()
+        return DRFResponse(serializer.data, status=200)
+
+
+class WCSTaskLogViewSet(viewsets.ModelViewSet):
+    """
+        retrieve:
+            Response a data list(get)
+        list:
+            Response a data list(all)
+    """
+    pagination_class = MyPageNumberPagination   
+    filter_backends = [DjangoFilterBackend, OrderingFilter, ]
+    ordering_fields = ['-id', "-send_time", "send_time", ]
+    filter_class = WCSTaskLogFilter
+
+    def get_queryset(self):
+        if self.request.user:
+            return WCSTaskLogModel.objects.all()
+        else:
+            return WCSTaskLogModel.objects.none()
+
+    def get_serializer_class(self):
+        if self.action in ['list', 'retrieve']:
+            return WCSTaskLogSerializer
+        else:
+            return self.http_method_not_allowed(request=self.request)
+
+
+def perform_initial_allocation(allocator, location):
+    """执行初始库位分配操作"""
+    location_row = location.split('-')[1]
+    location_col = location.split('-')[2]
+    location_layer = location.split('-')[3]
+    location_obj = LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first()
+    if not location_obj:
+        logger.error(f"未找到库位: {location}")
+        return False
+    location_code = location_obj.location_code
+    operations = [
+        (allocator.update_location_status, location_code, 'reserved'),
+        (allocator.update_location_group_status, location_code)
+    ]
+    
+    for func, *args in operations:
+        if not func(*args):
+            logger.error(f"分配操作失败: {func.__name__}")
+            return False
+    return True
 
 
 # 出库任务下发
@@ -2555,10 +2833,20 @@ class OutTaskViewSet(ViewSet):
             container_list = generate_result['data']
 
             #  2. 生成初始任务
-            OutboundService.create_initial_tasks(container_list,bound_list_id)
+            creation_result = OutboundService.create_initial_tasks(container_list,bound_list_id)
+            if not creation_result.get("success"):
+                return Response(
+                    {"code": 400, "msg": creation_result.get("msg", "创建任务失败")},
+                    status=400
+                )
             
-            # 3. 立即发送第一个任务
-            OutboundService.process_next_task()
+            # 3. 根据楼层信息初始化下发
+            initial_layers = creation_result.get("layers", [])
+            if creation_result.get("task_count", 0) > 0:
+                if len(initial_layers) > 1:
+                    OutboundService.process_next_task(initial_layers=initial_layers)
+                else:
+                    OutboundService.process_next_task()
             
             # 记录成功日志
             try:

+ 0 - 1
templates/dist/spa/css/34.5557cd4a.css

@@ -1 +0,0 @@
-.q-date__calendar-item--selected[data-v-878c80e6]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-878c80e6]{background-color:rgba(25,118,210,0.1)}

+ 1 - 0
templates/dist/spa/css/34.6a5a5158.css

@@ -0,0 +1 @@
+.q-date__calendar-item--selected[data-v-46690258]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-46690258]{background-color:rgba(25,118,210,0.1)}

+ 1 - 0
templates/dist/spa/css/35.56a2e799.css

@@ -0,0 +1 @@
+.q-date__calendar-item--selected[data-v-3f8b1aee]{transition:all 0.3s ease;background-color:#1976d2!important}.q-date__range[data-v-3f8b1aee]{background-color:rgba(25,118,210,0.1)}

templates/dist/spa/css/35.8f3f6188.css → templates/dist/spa/css/36.8f3f6188.css


templates/dist/spa/css/36.44ddcebd.css → templates/dist/spa/css/37.44ddcebd.css


templates/dist/spa/css/37.2ac1dad1.css → templates/dist/spa/css/38.2ac1dad1.css


templates/dist/spa/css/38.12670fd1.css → templates/dist/spa/css/39.12670fd1.css


templates/dist/spa/css/39.9478c981.css → templates/dist/spa/css/40.9478c981.css


templates/dist/spa/css/40.c4652654.css → templates/dist/spa/css/41.c4652654.css


templates/dist/spa/css/41.7a23b7fb.css → templates/dist/spa/css/42.7a23b7fb.css


templates/dist/spa/css/42.07732723.css → templates/dist/spa/css/43.07732723.css


templates/dist/spa/css/43.2594d0b9.css → templates/dist/spa/css/44.2594d0b9.css


templates/dist/spa/css/44.0faa4aeb.css → templates/dist/spa/css/45.0faa4aeb.css


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/index.html


File diff suppressed because it is too large
+ 1 - 0
templates/dist/spa/js/34.2315457a.js


BIN
templates/dist/spa/js/34.2315457a.js.gz


File diff suppressed because it is too large
+ 0 - 1
templates/dist/spa/js/34.cb30dfa4.js


BIN
templates/dist/spa/js/34.cb30dfa4.js.gz


BIN
templates/dist/spa/js/35.73cf8fc6.js.gz


File diff suppressed because it is too large
+ 1 - 0
templates/dist/spa/js/35.83bfe8ac.js


BIN
templates/dist/spa/js/35.83bfe8ac.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/35.73cf8fc6.js


BIN
templates/dist/spa/js/36.0ef8719a.js.gz


BIN
templates/dist/spa/js/37.54c90e2b.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/36.04984167.js


BIN
templates/dist/spa/js/36.04984167.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/37.54c90e2b.js


BIN
templates/dist/spa/js/38.51ed54fd.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/38.96100c54.js


BIN
templates/dist/spa/js/38.96100c54.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/39.5872b98c.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/40.d194e156.js


BIN
templates/dist/spa/js/42.95116c09.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/41.eed3ba0d.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/42.95116c09.js


BIN
templates/dist/spa/js/43.b9cd2573.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/43.b1600ae8.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/44.29b294d8.js


BIN
templates/dist/spa/js/45.85676583.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/45.85676583.js


BIN
templates/dist/spa/js/46.076c5ed4.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/46.1e66a908.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/47.a5d0cc0c.js


BIN
templates/dist/spa/js/48.f5f2c346.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/48.f5f2c346.js


BIN
templates/dist/spa/js/49.902b6c7e.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/49.05df0690.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/50.b2e076a3.js


BIN
templates/dist/spa/js/50.b2e076a3.js.gz


BIN
templates/dist/spa/js/51.f4758ae8.js.gz


BIN
templates/dist/spa/js/52.3e375429.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/51.f4758ae8.js


BIN
templates/dist/spa/js/52.4dece508.js.gz


BIN
templates/dist/spa/js/53.4bd72b33.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/52.3e375429.js


BIN
templates/dist/spa/js/53.ebc1d2e4.js.gz


BIN
templates/dist/spa/js/54.13366a89.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/53.4bd72b33.js


BIN
templates/dist/spa/js/54.245bcbe5.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/54.13366a89.js


BIN
templates/dist/spa/js/55.f01fcdbb.js.gz


BIN
templates/dist/spa/js/55.f93ef6f0.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/55.f93ef6f0.js


BIN
templates/dist/spa/js/56.18593e4b.js.gz


BIN
templates/dist/spa/js/56.8954c039.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/56.8954c039.js


BIN
templates/dist/spa/js/57.19369fb4.js.gz


BIN
templates/dist/spa/js/57.21c754a8.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/57.21c754a8.js


BIN
templates/dist/spa/js/58.95ed7156.js.gz


BIN
templates/dist/spa/js/58.b42c1c9b.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/58.b42c1c9b.js


BIN
templates/dist/spa/js/59.766cb550.js.gz


BIN
templates/dist/spa/js/59.9471f3f5.js.gz


BIN
templates/dist/spa/js/60.09da2c41.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/59.9471f3f5.js


BIN
templates/dist/spa/js/60.a3cbc9ce.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/60.09da2c41.js


BIN
templates/dist/spa/js/61.50192f42.js.gz


BIN
templates/dist/spa/js/61.e06a8283.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/61.e06a8283.js


BIN
templates/dist/spa/js/62.5061b3f4.js.gz


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/62.0b2ea8c5.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/63.9083683a.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/64.d54e6cb7.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/65.104af83e.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/66.def1e808.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/67.1d2c8112.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/68.348fe91e.js


File diff suppressed because it is too large
+ 1 - 1
templates/dist/spa/js/69.42a4eaa4.js


+ 0 - 0
templates/dist/spa/js/70.f5f7974b.js.gz


Some files were not shown because too many files changed in this diff