|
@@ -728,12 +728,12 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
}
|
|
}
|
|
|
else:
|
|
else:
|
|
|
# 生成任务
|
|
# 生成任务
|
|
|
|
|
+ # 查询移库任务,排除已完成和已取消的任务
|
|
|
current_task = ContainerWCSModel.objects.filter(
|
|
current_task = ContainerWCSModel.objects.filter(
|
|
|
container=container,
|
|
container=container,
|
|
|
tasktype='inbound',
|
|
tasktype='inbound',
|
|
|
- working = 1,
|
|
|
|
|
-
|
|
|
|
|
- ).exclude(status=300).first()
|
|
|
|
|
|
|
+ working=1
|
|
|
|
|
+ ).exclude(status=300).exclude(status=400).first() # 排除已完成和已取消的任务
|
|
|
|
|
|
|
|
if current_task:
|
|
if current_task:
|
|
|
data_return = {
|
|
data_return = {
|
|
@@ -856,12 +856,12 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
}
|
|
}
|
|
|
else:
|
|
else:
|
|
|
# 生成任务
|
|
# 生成任务
|
|
|
|
|
+ # 查询出库任务,排除已完成和已取消的任务
|
|
|
current_task = ContainerWCSModel.objects.filter(
|
|
current_task = ContainerWCSModel.objects.filter(
|
|
|
container=container,
|
|
container=container,
|
|
|
tasktype='outbound',
|
|
tasktype='outbound',
|
|
|
- working = 1,
|
|
|
|
|
-
|
|
|
|
|
- ).exclude(status=300).first()
|
|
|
|
|
|
|
+ working=1
|
|
|
|
|
+ ).exclude(status=300).exclude(status=400).first() # 排除已完成和已取消的任务
|
|
|
|
|
|
|
|
if current_task:
|
|
if current_task:
|
|
|
data_return = {
|
|
data_return = {
|
|
@@ -991,12 +991,12 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
'data': data
|
|
'data': data
|
|
|
}
|
|
}
|
|
|
else:
|
|
else:
|
|
|
|
|
+ # 查询入库任务,排除已完成和已取消的任务
|
|
|
current_task = ContainerWCSModel.objects.filter(
|
|
current_task = ContainerWCSModel.objects.filter(
|
|
|
container=container,
|
|
container=container,
|
|
|
tasktype='inbound',
|
|
tasktype='inbound',
|
|
|
- working = 1,
|
|
|
|
|
-
|
|
|
|
|
- ).exclude(status=300).first()
|
|
|
|
|
|
|
+ working=1
|
|
|
|
|
+ ).exclude(status=300).exclude(status=400).first() # 排除已完成和已取消的任务
|
|
|
|
|
|
|
|
if current_task:
|
|
if current_task:
|
|
|
|
|
|
|
@@ -1378,6 +1378,446 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
return Response({'code': '500', 'message': '服务器内部错误', 'data': None},
|
|
return Response({'code': '500', 'message': '服务器内部错误', 'data': None},
|
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
|
|
|
|
+ def cancel_task(self, request, *args, **kwargs):
|
|
|
|
|
+ """取消任务并回滚相关数据 - 支持所有任务类型(入库、移库、出库、检查)"""
|
|
|
|
|
+ data = self.request.data
|
|
|
|
|
+ task_number = data.get('task_number')
|
|
|
|
|
+ container_code = data.get('container_code')
|
|
|
|
|
+ task_type = data.get('task_type', 'inbound')
|
|
|
|
|
+
|
|
|
|
|
+ if not task_number:
|
|
|
|
|
+ return Response({
|
|
|
|
|
+ 'code': '400',
|
|
|
|
|
+ 'message': '缺少任务号参数',
|
|
|
|
|
+ 'success': False
|
|
|
|
|
+ }, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
+
|
|
|
|
|
+ if not container_code:
|
|
|
|
|
+ return Response({
|
|
|
|
|
+ 'code': '400',
|
|
|
|
|
+ 'message': '缺少托盘编码参数',
|
|
|
|
|
+ 'success': False
|
|
|
|
|
+ }, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 转换任务号格式(前端传入的是减去20000000000后的值)
|
|
|
|
|
+ full_task_number = task_number + 20000000000
|
|
|
|
|
+
|
|
|
|
|
+ # 查找任务
|
|
|
|
|
+ task = ContainerWCSModel.objects.filter(
|
|
|
|
|
+ tasknumber=full_task_number,
|
|
|
|
|
+ container=container_code,
|
|
|
|
|
+ tasktype=task_type,
|
|
|
|
|
+ working=1
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if not task:
|
|
|
|
|
+ return Response({
|
|
|
|
|
+ 'code': '404',
|
|
|
|
|
+ 'message': '任务不存在或已完成',
|
|
|
|
|
+ 'success': False
|
|
|
|
|
+ }, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
+
|
|
|
|
|
+ # 获取托盘对象
|
|
|
|
|
+ container_obj = ContainerListModel.objects.filter(
|
|
|
|
|
+ container_code=container_code
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if not container_obj:
|
|
|
|
|
+ return Response({
|
|
|
|
|
+ 'code': '404',
|
|
|
|
|
+ 'message': '托盘不存在',
|
|
|
|
|
+ 'success': False
|
|
|
|
|
+ }, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
|
+
|
|
|
|
|
+ # 使用事务确保原子性
|
|
|
|
|
+ rollback_details = {} # 收集回滚详情
|
|
|
|
|
+ with transaction.atomic():
|
|
|
|
|
+ # 1. 更新任务状态为取消
|
|
|
|
|
+ task.status = 400 # 使用400表示已取消
|
|
|
|
|
+ task.message = '任务已取消'
|
|
|
|
|
+ task.working = 0
|
|
|
|
|
+ task.save()
|
|
|
|
|
+
|
|
|
|
|
+ allocator = LocationAllocation()
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 根据任务类型执行不同的回滚逻辑
|
|
|
|
|
+ if task_type == 'inbound':
|
|
|
|
|
+ # 入库任务:回滚库位绑定
|
|
|
|
|
+ rollback_details = self._rollback_inbound_task(task, container_obj, allocator)
|
|
|
|
|
+
|
|
|
|
|
+ elif task_type == 'move':
|
|
|
|
|
+ # 移库任务:回滚库位状态(如果已更新)
|
|
|
|
|
+ rollback_details = self._rollback_move_task(task, container_obj, allocator)
|
|
|
|
|
+
|
|
|
|
|
+ elif task_type == 'outbound':
|
|
|
|
|
+ # 出库任务:回滚出库数量和出库明细
|
|
|
|
|
+ rollback_details = self._rollback_outbound_task(task, container_obj, allocator)
|
|
|
|
|
+
|
|
|
|
|
+ elif task_type == 'check':
|
|
|
|
|
+ # 检查任务:回滚库位状态(如果已更新)
|
|
|
|
|
+ rollback_details = self._rollback_check_task(task, container_obj, allocator)
|
|
|
|
|
+
|
|
|
|
|
+ # 3. 更新托盘目标位置为当前位置
|
|
|
|
|
+ old_target_location = container_obj.target_location
|
|
|
|
|
+ container_obj.target_location = container_obj.current_location
|
|
|
|
|
+ container_obj.save()
|
|
|
|
|
+
|
|
|
|
|
+ # 记录托盘位置变化
|
|
|
|
|
+ if old_target_location != container_obj.target_location:
|
|
|
|
|
+ if 'container_changes' not in rollback_details:
|
|
|
|
|
+ rollback_details['container_changes'] = []
|
|
|
|
|
+ rollback_details['container_changes'].append({
|
|
|
|
|
+ 'field': 'target_location',
|
|
|
|
|
+ 'old_value': old_target_location,
|
|
|
|
|
+ 'new_value': container_obj.target_location,
|
|
|
|
|
+ 'description': f'托盘目标位置已更新为当前位置'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ # 4. 记录取消任务日志
|
|
|
|
|
+ try:
|
|
|
|
|
+ log_success_operation(
|
|
|
|
|
+ request=request,
|
|
|
|
|
+ operation_content=f"取消任务,任务号:{task_number},托盘:{container_code},任务类型:{task_type}",
|
|
|
|
|
+ operation_level="update",
|
|
|
|
|
+ operator=request.auth.name if hasattr(request, 'auth') and request.auth else None,
|
|
|
|
|
+ object_id=task.id,
|
|
|
|
|
+ module_name="WCS任务管理"
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as log_error:
|
|
|
|
|
+ logger.error(f"记录取消任务日志失败: {str(log_error)}")
|
|
|
|
|
+
|
|
|
|
|
+ return Response({
|
|
|
|
|
+ 'code': '200',
|
|
|
|
|
+ 'message': '任务已取消,相关数据已回滚',
|
|
|
|
|
+ 'success': True,
|
|
|
|
|
+ 'data': {
|
|
|
|
|
+ 'task_number': task_number,
|
|
|
|
|
+ 'container_code': container_code,
|
|
|
|
|
+ 'task_type': task_type,
|
|
|
|
|
+ 'rollback_details': rollback_details
|
|
|
|
|
+ }
|
|
|
|
|
+ }, status=status.HTTP_200_OK)
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.error(f"取消任务失败: {str(e)}", exc_info=True)
|
|
|
|
|
+ try:
|
|
|
|
|
+ log_failure_operation(
|
|
|
|
|
+ request=request,
|
|
|
|
|
+ operation_content=f"取消任务失败:任务号 {task_number},托盘 {container_code},错误:{str(e)}",
|
|
|
|
|
+ operation_level="update",
|
|
|
|
|
+ operator=request.auth.name if hasattr(request, 'auth') and request.auth else None,
|
|
|
|
|
+ module_name="WCS任务管理"
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as log_error:
|
|
|
|
|
+ logger.error(f"记录取消任务失败日志失败: {str(log_error)}")
|
|
|
|
|
+
|
|
|
|
|
+ return Response({
|
|
|
|
|
+ 'code': '500',
|
|
|
|
|
+ 'message': f'取消任务失败: {str(e)}',
|
|
|
|
|
+ 'success': False
|
|
|
|
|
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
+
|
|
|
|
|
+ def _rollback_inbound_task(self, task, container_obj, allocator):
|
|
|
|
|
+ """回滚入库任务相关数据,返回详细的回滚信息"""
|
|
|
|
|
+ rollback_details = {
|
|
|
|
|
+ 'location_changes': [],
|
|
|
|
|
+ 'link_changes': []
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if not task.target_location:
|
|
|
|
|
+ return rollback_details
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 获取目标库位编码
|
|
|
|
|
+ location_code = self.get_location_code(task.target_location)
|
|
|
|
|
+
|
|
|
|
|
+ # 解除库位-托盘关联
|
|
|
|
|
+ link = LocationContainerLink.objects.filter(
|
|
|
|
|
+ location__location_code=location_code,
|
|
|
|
|
+ container=container_obj,
|
|
|
|
|
+ is_active=True
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if link:
|
|
|
|
|
+ link.is_active = False
|
|
|
|
|
+ link.save()
|
|
|
|
|
+ rollback_details['link_changes'].append({
|
|
|
|
|
+ 'location_code': location_code,
|
|
|
|
|
+ 'action': '解除关联',
|
|
|
|
|
+ 'description': f'已解除库位 {location_code} 与托盘 {container_obj.container_code} 的绑定关系'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ # 更新库位状态为可用
|
|
|
|
|
+ location = LocationModel.objects.filter(
|
|
|
|
|
+ location_code=location_code
|
|
|
|
|
+ ).first()
|
|
|
|
|
+ if location and location.status in ['occupied', 'reserved']:
|
|
|
|
|
+ old_status = location.status
|
|
|
|
|
+ location.status = 'available'
|
|
|
|
|
+ location.save()
|
|
|
|
|
+ # 更新库位组状态
|
|
|
|
|
+ allocator.update_location_group_status(location_code)
|
|
|
|
|
+
|
|
|
|
|
+ rollback_details['location_changes'].append({
|
|
|
|
|
+ 'location_code': location_code,
|
|
|
|
|
+ 'old_status': old_status,
|
|
|
|
|
+ 'new_status': 'available',
|
|
|
|
|
+ 'description': f'库位 {location_code} 状态已从 {old_status} 恢复为 available'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"入库任务 {task.taskid} 取消成功,已回滚库位绑定关系")
|
|
|
|
|
+ return rollback_details
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as rollback_error:
|
|
|
|
|
+ logger.error(f"回滚入库任务失败: {str(rollback_error)}")
|
|
|
|
|
+ raise
|
|
|
|
|
+
|
|
|
|
|
+ def _rollback_move_task(self, task, container_obj, allocator):
|
|
|
|
|
+ """回滚移库任务相关数据,返回详细的回滚信息"""
|
|
|
|
|
+ rollback_details = {
|
|
|
|
|
+ 'location_changes': [],
|
|
|
|
|
+ 'link_changes': []
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 移库任务如果已经更新了目标库位,需要回滚
|
|
|
|
|
+ if task.target_location and task.target_location not in ['103', '203']:
|
|
|
|
|
+ try:
|
|
|
|
|
+ location_code = self.get_location_code(task.target_location)
|
|
|
|
|
+ location = LocationModel.objects.filter(
|
|
|
|
|
+ location_code=location_code
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if location and location.status in ['occupied', 'reserved']:
|
|
|
|
|
+ old_status = location.status
|
|
|
|
|
+ # 检查是否有活跃的关联
|
|
|
|
|
+ active_link = LocationContainerLink.objects.filter(
|
|
|
|
|
+ location=location,
|
|
|
|
|
+ container=container_obj,
|
|
|
|
|
+ is_active=True
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if active_link:
|
|
|
|
|
+ active_link.is_active = False
|
|
|
|
|
+ active_link.save()
|
|
|
|
|
+ rollback_details['link_changes'].append({
|
|
|
|
|
+ 'location_code': location_code,
|
|
|
|
|
+ 'action': '解除关联',
|
|
|
|
|
+ 'description': f'已解除目标库位 {location_code} 与托盘 {container_obj.container_code} 的绑定关系'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ location.status = 'available'
|
|
|
|
|
+ location.save()
|
|
|
|
|
+ allocator.update_location_group_status(location_code)
|
|
|
|
|
+
|
|
|
|
|
+ rollback_details['location_changes'].append({
|
|
|
|
|
+ 'location_code': location_code,
|
|
|
|
|
+ 'old_status': old_status,
|
|
|
|
|
+ 'new_status': 'available',
|
|
|
|
|
+ 'description': f'目标库位 {location_code} 状态已从 {old_status} 恢复为 available'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"移库任务 {task.taskid} 取消成功,已回滚目标库位状态")
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning(f"回滚移库任务目标库位失败: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+ # 回滚起始库位状态(如果已更新为reserved)
|
|
|
|
|
+ if task.current_location and task.current_location not in ['103', '203']:
|
|
|
|
|
+ try:
|
|
|
|
|
+ current_location_code = self.get_location_code(task.current_location)
|
|
|
|
|
+ current_location = LocationModel.objects.filter(
|
|
|
|
|
+ location_code=current_location_code
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if current_location and current_location.status == 'reserved':
|
|
|
|
|
+ old_status = current_location.status
|
|
|
|
|
+ current_location.status = 'available'
|
|
|
|
|
+ current_location.save()
|
|
|
|
|
+ allocator.update_location_group_status(current_location_code)
|
|
|
|
|
+
|
|
|
|
|
+ rollback_details['location_changes'].append({
|
|
|
|
|
+ 'location_code': current_location_code,
|
|
|
|
|
+ 'old_status': old_status,
|
|
|
|
|
+ 'new_status': 'available',
|
|
|
|
|
+ 'description': f'起始库位 {current_location_code} 状态已从 {old_status} 恢复为 available'
|
|
|
|
|
+ })
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning(f"回滚移库任务起始库位失败: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as rollback_error:
|
|
|
|
|
+ logger.error(f"回滚移库任务失败: {str(rollback_error)}")
|
|
|
|
|
+ raise
|
|
|
|
|
+
|
|
|
|
|
+ return rollback_details
|
|
|
|
|
+
|
|
|
|
|
+ def _rollback_outbound_task(self, task, container_obj, allocator):
|
|
|
|
|
+ """回滚出库任务相关数据 - 包括出库数量和出库明细,返回详细的回滚信息"""
|
|
|
|
|
+ rollback_details = {
|
|
|
|
|
+ 'batch_changes': [],
|
|
|
|
|
+ 'out_detail_changes': [],
|
|
|
|
|
+ 'container_status_changes': [],
|
|
|
|
|
+ 'total_rollback_qty': 0
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ from decimal import Decimal
|
|
|
|
|
+
|
|
|
|
|
+ # 1. 回滚出库明细(out_batch_detail)
|
|
|
|
|
+ out_details = out_batch_detail.objects.filter(
|
|
|
|
|
+ container=container_obj,
|
|
|
|
|
+ working=1,
|
|
|
|
|
+ is_delete=False
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ total_rollback_qty = Decimal('0')
|
|
|
|
|
+
|
|
|
|
|
+ for out_detail in out_details:
|
|
|
|
|
+ # 回滚托盘明细的出库数量
|
|
|
|
|
+ container_detail = out_detail.container_detail
|
|
|
|
|
+ if container_detail:
|
|
|
|
|
+ # 回滚到上次的出库数量
|
|
|
|
|
+ rollback_qty = out_detail.out_goods_qty
|
|
|
|
|
+ old_out_qty = container_detail.goods_out_qty
|
|
|
|
|
+ container_detail.goods_out_qty = out_detail.last_out_goods_qty
|
|
|
|
|
+ container_detail.save()
|
|
|
|
|
+
|
|
|
|
|
+ total_rollback_qty += rollback_qty
|
|
|
|
|
+
|
|
|
|
|
+ batch_id = container_detail.batch.id if container_detail.batch else '未知'
|
|
|
|
|
+ batch_number = container_detail.batch.bound_number if container_detail.batch else '未知'
|
|
|
|
|
+
|
|
|
|
|
+ rollback_details['batch_changes'].append({
|
|
|
|
|
+ 'batch_id': batch_id,
|
|
|
|
|
+ 'batch_number': batch_number,
|
|
|
|
|
+ 'rollback_qty': float(rollback_qty),
|
|
|
|
|
+ 'old_out_qty': float(old_out_qty),
|
|
|
|
|
+ 'new_out_qty': float(container_detail.goods_out_qty),
|
|
|
|
|
+ 'description': f'批次 {batch_number} 出库数量已从 {old_out_qty} 回滚到 {container_detail.goods_out_qty},回滚数量: {rollback_qty}'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ # 创建批次操作日志
|
|
|
|
|
+ try:
|
|
|
|
|
+ BatchOperateLogModel.objects.create(
|
|
|
|
|
+ batch_id=container_detail.batch,
|
|
|
|
|
+ log_type=1, # 出库日志类型
|
|
|
|
|
+ log_date=timezone.now(),
|
|
|
|
|
+ goods_code=container_detail.batch.goods_code if container_detail.batch else '',
|
|
|
|
|
+ goods_desc=container_detail.batch.goods_desc if container_detail.batch else '',
|
|
|
|
|
+ goods_qty=-rollback_qty, # 负数表示回滚
|
|
|
|
|
+ log_content=f"取消出库任务回滚:托盘 {container_obj.container_code} 批次 {batch_id} 回滚数量 {rollback_qty}",
|
|
|
|
|
+ creater="WMS",
|
|
|
|
|
+ openid="WMS"
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as log_error:
|
|
|
|
|
+ logger.warning(f"创建批次操作日志失败: {str(log_error)}")
|
|
|
|
|
+
|
|
|
|
|
+ # 标记出库明细为已取消
|
|
|
|
|
+ out_detail.working = 0
|
|
|
|
|
+ out_detail.is_delete = True
|
|
|
|
|
+ out_detail.save()
|
|
|
|
|
+
|
|
|
|
|
+ rollback_details['out_detail_changes'].append({
|
|
|
|
|
+ 'out_detail_id': out_detail.id,
|
|
|
|
|
+ 'action': '已取消',
|
|
|
|
|
+ 'description': f'出库明细 ID {out_detail.id} 已标记为取消'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ rollback_details['total_rollback_qty'] = float(total_rollback_qty)
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 回滚库位状态(如果已更新)
|
|
|
|
|
+ if task.current_location and task.current_location not in ['103', '203']:
|
|
|
|
|
+ try:
|
|
|
|
|
+ location_code = self.get_location_code(task.current_location)
|
|
|
|
|
+ location = LocationModel.objects.filter(
|
|
|
|
|
+ location_code=location_code
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if location:
|
|
|
|
|
+ # 检查是否有活跃的关联
|
|
|
|
|
+ active_link = LocationContainerLink.objects.filter(
|
|
|
|
|
+ location=location,
|
|
|
|
|
+ container=container_obj,
|
|
|
|
|
+ is_active=True
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if not active_link:
|
|
|
|
|
+ # 如果没有关联,说明可能已经解除了,需要重新绑定
|
|
|
|
|
+ # 或者库位状态需要恢复
|
|
|
|
|
+ if location.status == 'available':
|
|
|
|
|
+ # 如果库位是可用状态,可能需要恢复为占用(如果托盘还在库位)
|
|
|
|
|
+ pass
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning(f"回滚出库任务库位状态失败: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+ # 3. 恢复托盘状态(如果已更新为已出库)
|
|
|
|
|
+ if container_obj.status == 3: # 3表示已出库
|
|
|
|
|
+ # 根据业务逻辑决定是否恢复状态
|
|
|
|
|
+ # 如果托盘还有在库的明细,应该恢复为在库状态
|
|
|
|
|
+ has_in_stock_details = ContainerDetailModel.objects.filter(
|
|
|
|
|
+ container=container_obj,
|
|
|
|
|
+ status=2, # 2表示在库
|
|
|
|
|
+ is_delete=False
|
|
|
|
|
+ ).exists()
|
|
|
|
|
+
|
|
|
|
|
+ if has_in_stock_details:
|
|
|
|
|
+ old_status = container_obj.status
|
|
|
|
|
+ container_obj.status = 2 # 2表示在库
|
|
|
|
|
+ container_obj.save()
|
|
|
|
|
+
|
|
|
|
|
+ rollback_details['container_status_changes'].append({
|
|
|
|
|
+ 'field': 'status',
|
|
|
|
|
+ 'old_value': old_status,
|
|
|
|
|
+ 'new_value': 2,
|
|
|
|
|
+ 'description': f'托盘状态已从 已出库({old_status}) 恢复为 在库(2)'
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"出库任务 {task.taskid} 取消成功,已回滚出库数量: {total_rollback_qty}")
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as rollback_error:
|
|
|
|
|
+ logger.error(f"回滚出库任务失败: {str(rollback_error)}")
|
|
|
|
|
+ raise
|
|
|
|
|
+
|
|
|
|
|
+ return rollback_details
|
|
|
|
|
+
|
|
|
|
|
+ def _rollback_check_task(self, task, container_obj, allocator):
|
|
|
|
|
+ """回滚检查任务相关数据,返回详细的回滚信息"""
|
|
|
|
|
+ rollback_details = {
|
|
|
|
|
+ 'location_changes': []
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 检查任务通常不涉及库位状态变更,但如果有,需要回滚
|
|
|
|
|
+ if task.target_location and task.target_location not in ['103', '203']:
|
|
|
|
|
+ try:
|
|
|
|
|
+ location_code = self.get_location_code(task.target_location)
|
|
|
|
|
+ location = LocationModel.objects.filter(
|
|
|
|
|
+ location_code=location_code
|
|
|
|
|
+ ).first()
|
|
|
|
|
+
|
|
|
|
|
+ if location and location.status == 'reserved':
|
|
|
|
|
+ old_status = location.status
|
|
|
|
|
+ location.status = 'available'
|
|
|
|
|
+ location.save()
|
|
|
|
|
+ allocator.update_location_group_status(location_code)
|
|
|
|
|
+
|
|
|
|
|
+ rollback_details['location_changes'].append({
|
|
|
|
|
+ 'location_code': location_code,
|
|
|
|
|
+ 'old_status': old_status,
|
|
|
|
|
+ 'new_status': 'available',
|
|
|
|
|
+ 'description': f'库位 {location_code} 状态已从 {old_status} 恢复为 available'
|
|
|
|
|
+ })
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning(f"回滚检查任务库位状态失败: {str(e)}")
|
|
|
|
|
+
|
|
|
|
|
+ logger.info(f"检查任务 {task.taskid} 取消成功")
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as rollback_error:
|
|
|
|
|
+ logger.error(f"回滚检查任务失败: {str(rollback_error)}")
|
|
|
|
|
+ raise
|
|
|
|
|
+
|
|
|
|
|
+ return rollback_details
|
|
|
|
|
+
|
|
|
# ---------- 辅助函数 ----------
|
|
# ---------- 辅助函数 ----------
|
|
|
def validate_container(self, data):
|
|
def validate_container(self, data):
|
|
|
"""验证托盘是否存在"""
|
|
"""验证托盘是否存在"""
|
|
@@ -2462,12 +2902,14 @@ class OutboundService:
|
|
|
def create_initial_tasks(container_list,bound_list_id):
|
|
def create_initial_tasks(container_list,bound_list_id):
|
|
|
"""生成初始任务队列,返回楼层信息用于初始化发送"""
|
|
"""生成初始任务队列,返回楼层信息用于初始化发送"""
|
|
|
with transaction.atomic():
|
|
with transaction.atomic():
|
|
|
|
|
+ # 查询出库任务,排除已取消的任务(status=400表示已取消)
|
|
|
|
|
+ # status__lt=300 已经排除了 status=400,但为了明确性,仍然添加 exclude
|
|
|
current_WCS = ContainerWCSModel.objects.filter(
|
|
current_WCS = ContainerWCSModel.objects.filter(
|
|
|
tasktype='outbound',
|
|
tasktype='outbound',
|
|
|
bound_list_id=bound_list_id,
|
|
bound_list_id=bound_list_id,
|
|
|
is_delete=False,
|
|
is_delete=False,
|
|
|
status__lt=300
|
|
status__lt=300
|
|
|
- ).first()
|
|
|
|
|
|
|
+ ).exclude(status=400).first() # 明确排除已取消的任务
|
|
|
if current_WCS:
|
|
if current_WCS:
|
|
|
logger.error(f"当前{bound_list_id}已有出库任务")
|
|
logger.error(f"当前{bound_list_id}已有出库任务")
|
|
|
return {
|
|
return {
|
|
@@ -2563,7 +3005,13 @@ class OutboundService:
|
|
|
def create_initial_check_tasks(container_list,batch_id):
|
|
def create_initial_check_tasks(container_list,batch_id):
|
|
|
"""生成初始任务队列"""
|
|
"""生成初始任务队列"""
|
|
|
with transaction.atomic():
|
|
with transaction.atomic():
|
|
|
- current_WCS = ContainerWCSModel.objects.filter(tasktype='check',batch_id = batch_id,is_delete=False,working=1).first()
|
|
|
|
|
|
|
+ # 查询检查任务,排除已取消的任务(status=400表示已取消)
|
|
|
|
|
+ current_WCS = ContainerWCSModel.objects.filter(
|
|
|
|
|
+ tasktype='check',
|
|
|
|
|
+ batch_id=batch_id,
|
|
|
|
|
+ is_delete=False,
|
|
|
|
|
+ working=1
|
|
|
|
|
+ ).exclude(status=400).first() # 排除已取消的任务
|
|
|
if current_WCS:
|
|
if current_WCS:
|
|
|
logger.error(f"当前{batch_id}已有检查任务")
|
|
logger.error(f"当前{batch_id}已有检查任务")
|
|
|
return False
|
|
return False
|
|
@@ -2943,7 +3391,13 @@ class OutTaskViewSet(ViewSet):
|
|
|
except Exception as log_error:
|
|
except Exception as log_error:
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
- current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id,is_delete=False).first()
|
|
|
|
|
|
|
+ # 查询出库任务,排除已取消的任务(status=400表示已取消)
|
|
|
|
|
+ current_WCS = ContainerWCSModel.objects.filter(
|
|
|
|
|
+ tasktype='outbound',
|
|
|
|
|
+ bound_list_id=bound_list_id,
|
|
|
|
|
+ is_delete=False
|
|
|
|
|
+ ).exclude(status=400).first() # 排除已取消的任务
|
|
|
|
|
+
|
|
|
if current_WCS:
|
|
if current_WCS:
|
|
|
logger.info(f"当前{bound_list_id}已有出库任务{current_WCS.taskid}")
|
|
logger.info(f"当前{bound_list_id}已有出库任务{current_WCS.taskid}")
|
|
|
|
|
|
|
@@ -3113,7 +3567,13 @@ class OutTaskViewSet(ViewSet):
|
|
|
except Exception as log_error:
|
|
except Exception as log_error:
|
|
|
pass
|
|
pass
|
|
|
return Response({"code": "400", "msg": "缺少抽检数目或批次号"}, status=200)
|
|
return Response({"code": "400", "msg": "缺少抽检数目或批次号"}, status=200)
|
|
|
- current_WCS = ContainerWCSModel.objects.filter(batch=batch_id,tasktype='check',is_delete=False,working=1).first()
|
|
|
|
|
|
|
+ # 查询检查任务,排除已取消的任务(status=400表示已取消)
|
|
|
|
|
+ current_WCS = ContainerWCSModel.objects.filter(
|
|
|
|
|
+ batch=batch_id,
|
|
|
|
|
+ tasktype='check',
|
|
|
|
|
+ is_delete=False,
|
|
|
|
|
+ working=1
|
|
|
|
|
+ ).exclude(status=400).first() # 排除已取消的任务
|
|
|
if current_WCS:
|
|
if current_WCS:
|
|
|
logger.info(f"当前{batch_id}已有出库抽检任务{current_WCS.taskid}")
|
|
logger.info(f"当前{batch_id}已有出库抽检任务{current_WCS.taskid}")
|
|
|
if current_WCS.working == 1:
|
|
if current_WCS.working == 1:
|