|
|
@@ -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:
|