|
@@ -1,15 +1,12 @@
|
|
|
from rest_framework import viewsets
|
|
|
from utils.page import MyPageNumberPagination
|
|
|
-from utils.datasolve import sumOfList, transportation_calculate
|
|
|
-from utils.md5 import Md5
|
|
|
+
|
|
|
from rest_framework.filters import OrderingFilter
|
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
|
from rest_framework.response import Response
|
|
|
-from rest_framework.exceptions import APIException
|
|
|
+
|
|
|
from django.utils import timezone
|
|
|
import requests
|
|
|
-import json
|
|
|
-from django.conf import settings
|
|
|
|
|
|
from django.db import transaction
|
|
|
import logging
|
|
@@ -17,17 +14,15 @@ from rest_framework import status
|
|
|
from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,ContainerWCSModel,TaskModel
|
|
|
from bound.models import BoundBatchModel,BoundDetailModel,BoundListModel,OutBoundDetailModel
|
|
|
from bin.views import LocationAllocation,base_location
|
|
|
-from bin.models import LocationModel,LocationContainerLink
|
|
|
-
|
|
|
+from bin.models import LocationModel,LocationContainerLink,LocationGroupModel
|
|
|
+from bound.models import BoundBatchModel
|
|
|
|
|
|
from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSerializer
|
|
|
from .serializers import ContainerListGetSerializer,ContainerListPostSerializer
|
|
|
from .serializers import ContainerOperationGetSerializer,ContainerOperationPostSerializer
|
|
|
from .serializers import TaskGetSerializer,TaskPostSerializer
|
|
|
from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter
|
|
|
-
|
|
|
-from warehouse.models import ListModel as warehouse
|
|
|
-from staff.models import ListModel as staff
|
|
|
+
|
|
|
from rest_framework.permissions import AllowAny
|
|
|
import threading
|
|
|
from django.db import close_old_connections
|
|
@@ -132,7 +127,7 @@ class TaskViewSet(viewsets.ModelViewSet):
|
|
|
if id is None:
|
|
|
return TaskModel.objects.filter()
|
|
|
else:
|
|
|
- return TaskModel.objects.filter( id=id)
|
|
|
+ return TaskModel.objects.filter(id=id)
|
|
|
else:
|
|
|
return TaskModel.objects.none()
|
|
|
|
|
@@ -212,14 +207,12 @@ class TaskRollbackMixin:
|
|
|
|
|
|
|
|
|
allocator.update_container_detail_status(container_code, 1)
|
|
|
-
|
|
|
+
|
|
|
|
|
|
container_obj.target_location = task.current_location
|
|
|
container_obj.save()
|
|
|
-
|
|
|
|
|
|
task.delete()
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
@@ -285,7 +278,7 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
serializer.save()
|
|
|
|
|
|
|
|
|
- if current_location == str(container_obj.target_location):
|
|
|
+ if current_location == str(container_obj.target_location) and current_location!= '203' and current_location!= '103':
|
|
|
logger.info(f"托盘 {container} 已在目标位置")
|
|
|
data_return = {
|
|
|
'code': '200',
|
|
@@ -295,8 +288,9 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
else:
|
|
|
current_task = ContainerWCSModel.objects.filter(
|
|
|
container=container,
|
|
|
- tasktype='inbound'
|
|
|
- ).first()
|
|
|
+ tasktype='inbound',
|
|
|
+
|
|
|
+ ).exclude(status=300).first()
|
|
|
|
|
|
if current_task:
|
|
|
|
|
@@ -412,19 +406,20 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
|
|
|
last_task = ContainerWCSModel.objects.filter(
|
|
|
month=data_tosave['month'],
|
|
|
-
|
|
|
- ).order_by('-taskid').first()
|
|
|
+ ).order_by('-tasknumber').first()
|
|
|
|
|
|
if last_task:
|
|
|
- last_id = int(last_task.taskid.split('-')[-1])
|
|
|
- new_id = f"{last_id + 1:05}"
|
|
|
+
|
|
|
+ number_id = last_task.tasknumber + 1
|
|
|
+ new_id = f"{number_id:05d}"
|
|
|
else:
|
|
|
new_id = "00001"
|
|
|
-
|
|
|
+ number_id = f"{data_tosave['month']}{new_id}"
|
|
|
+
|
|
|
data_tosave['taskid'] = f"inbound-{data_tosave['month']}-{new_id}"
|
|
|
logger.info(f"生成入库任务: {data_tosave['taskid']}")
|
|
|
|
|
|
- data_tosave['tasknumber'] = f"{data_tosave['month']}{new_id}"
|
|
|
+ data_tosave['tasknumber'] = number_id
|
|
|
ContainerWCSModel.objects.create(**data_tosave)
|
|
|
|
|
|
def update_container_wcs(self, request, *args, **kwargs):
|
|
@@ -439,12 +434,26 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
|
|
|
|
|
|
if not self.update_container_data(container_obj, data):
|
|
|
- return Response({'code': '400', 'message': '数据更新失败', 'data': data},
|
|
|
- status=status.HTTP_400_BAD_REQUEST)
|
|
|
+ return Response(
|
|
|
+ {'code': '400', 'message': '数据更新失败', 'data': data},
|
|
|
+ status=status.HTTP_400_BAD_REQUEST
|
|
|
+ )
|
|
|
|
|
|
|
|
|
+ task = ContainerWCSModel.objects.filter(
|
|
|
+ container=container_obj.container_code,
|
|
|
+ tasktype='inbound'
|
|
|
+ ).first()
|
|
|
if self.is_already_at_target(container_obj, data.get('current_location')):
|
|
|
return self.handle_target_reached(container_obj, data)
|
|
|
+ elif task:
|
|
|
+
|
|
|
+ data_return = {
|
|
|
+ 'code': '200',
|
|
|
+ 'message': '任务已存在,重新下发',
|
|
|
+ 'data': task.to_dict()
|
|
|
+ }
|
|
|
+ return Response(data_return, status=status.HTTP_200_OK)
|
|
|
else:
|
|
|
return self.handle_new_allocation(container_obj, data)
|
|
|
|
|
@@ -494,7 +503,11 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
if task and task.tasktype == 'inbound':
|
|
|
self.update_storage_system(container_obj)
|
|
|
|
|
|
- if task and task.tasktype == 'outbound':
|
|
|
+ if task and task.tasktype == 'outbound' 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()
|
|
|
|
|
|
return Response({
|
|
@@ -518,6 +531,7 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
task = ContainerWCSModel.objects.filter(tasknumber=taskNumber).first()
|
|
|
if task:
|
|
|
task.status = 300
|
|
|
+ task.message = '任务已完成'
|
|
|
task.working = 0
|
|
|
task.save()
|
|
|
|
|
@@ -651,6 +665,77 @@ class ContainerWCSViewSet(viewsets.ModelViewSet):
|
|
|
)
|
|
|
|
|
|
|
|
|
+ def handle_outbound_completion(self, container_obj, task):
|
|
|
+ """处理出库完成后的库位释放和状态更新"""
|
|
|
+ try:
|
|
|
+ allocator = LocationAllocation()
|
|
|
+ location_task = task.current_location
|
|
|
+ location_row = location_task.split('-')[1]
|
|
|
+ location_col = location_task.split('-')[2]
|
|
|
+ location_layer = location_task.split('-')[3]
|
|
|
+ location= LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first()
|
|
|
+ location_code = location.location_code
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ with transaction.atomic():
|
|
|
+
|
|
|
+ if not allocator.release_location(location_code):
|
|
|
+ raise Exception("解除库位关联失败")
|
|
|
+
|
|
|
+
|
|
|
+ if not allocator.update_location_status(location_code, 'available'):
|
|
|
+ raise Exception("库位状态更新失败")
|
|
|
+
|
|
|
+
|
|
|
+ self.handle_group_location_status(location_code, location.location_group)
|
|
|
+
|
|
|
+
|
|
|
+ container_obj.status = 3
|
|
|
+ container_obj.save()
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ return True
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"出库完成处理失败: {str(e)}")
|
|
|
+ return False
|
|
|
+
|
|
|
+ def handle_group_location_status(self,location_code,location_group):
|
|
|
+ """
|
|
|
+ 处理库位组和库位的关联关系
|
|
|
+ :param location_code: 库位编码
|
|
|
+ :param location_group: 库位组编码
|
|
|
+ :return:
|
|
|
+ """
|
|
|
+
|
|
|
+ location_obj_number = LocationModel.objects.filter(
|
|
|
+ location_group=location_group,
|
|
|
+ status='available'
|
|
|
+ ).all().count()
|
|
|
+
|
|
|
+ logger.info(f"库位组 {location_group} 下的库位数目:{location_obj_number}")
|
|
|
+
|
|
|
+ location_group_obj = LocationGroupModel.objects.filter(
|
|
|
+ group_code=location_group
|
|
|
+ ).first()
|
|
|
+ if not location_group_obj:
|
|
|
+ logger.info(f"库位组 {location_group} 不存在")
|
|
|
+ return None
|
|
|
+ else:
|
|
|
+ if location_obj_number == 0:
|
|
|
+
|
|
|
+ location_group_obj.status = 'full'
|
|
|
+ location_group_obj.save()
|
|
|
+ elif location_obj_number < location_group_obj.max_capacity:
|
|
|
+ location_group_obj.status = 'occupied'
|
|
|
+ location_group_obj.save()
|
|
|
+ else:
|
|
|
+ location_group_obj.status = 'available'
|
|
|
+ location_group_obj.current_batch = ''
|
|
|
+ location_group_obj.current_goods_code = ''
|
|
|
+ location_group_obj.save()
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -926,7 +1011,6 @@ class ContainerOperateViewSet(viewsets.ModelViewSet):
|
|
|
headers = self.get_success_headers(serializer.data)
|
|
|
return Response(serializer.data, status=200, headers=headers)
|
|
|
|
|
|
-
|
|
|
class OutboundService:
|
|
|
@staticmethod
|
|
|
def generate_task_id():
|
|
@@ -939,41 +1023,8 @@ class OutboundService:
|
|
|
sequence = last_task.sequence + 1 if last_task else 1
|
|
|
return f"outbound-{month}-{sequence:05d}"
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
@staticmethod
|
|
|
- def send_task_to_wcs(task):
|
|
|
+ def send_task_to_wcs(task):
|
|
|
"""异步发送任务到WCS(非阻塞版本)"""
|
|
|
|
|
|
task_data = {
|
|
@@ -996,9 +1047,7 @@ class OutboundService:
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- container_obj = ContainerListModel.objects.filter(container_code=task.container).first()
|
|
|
- container_obj.target_location = task.target_location
|
|
|
- container_obj.save()
|
|
|
+
|
|
|
|
|
|
thread = threading.Thread(
|
|
|
target=OutboundService._async_send_handler,
|
|
@@ -1054,7 +1103,6 @@ class OutboundService:
|
|
|
current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id).first()
|
|
|
if current_WCS:
|
|
|
logger.error(f"当前{bound_list_id}已有出库任务")
|
|
|
-
|
|
|
return False
|
|
|
tasks = []
|
|
|
start_sequence = ContainerWCSModel.objects.filter(tasktype='outbound').count() + 1
|
|
@@ -1089,6 +1137,9 @@ class OutboundService:
|
|
|
)
|
|
|
tasknumber_index += 1
|
|
|
tasks.append(task)
|
|
|
+ container_obj = ContainerListModel.objects.filter(container_code=task.container).first()
|
|
|
+ container_obj.target_location = task.target_location
|
|
|
+ container_obj.save()
|
|
|
ContainerWCSModel.objects.bulk_create(tasks)
|
|
|
logger.info(f"已创建 {len(tasks)} 个初始任务")
|
|
|
|
|
@@ -1138,8 +1189,28 @@ class OutboundService:
|
|
|
if not next_task:
|
|
|
logger.info("没有待处理任务")
|
|
|
return
|
|
|
+ allocator = LocationAllocation()
|
|
|
+ OutboundService.perform_initial_allocation(allocator, next_task.current_location)
|
|
|
OutboundService.send_task_to_wcs(next_task)
|
|
|
|
|
|
+ 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 OutTaskViewSet(viewsets.ModelViewSet):
|
|
|
|
|
@@ -1163,12 +1234,18 @@ class OutTaskViewSet(viewsets.ModelViewSet):
|
|
|
|
|
|
logger.info(f"出库批次数量: {batch_count}")
|
|
|
|
|
|
- generate_result = self.generate_location_by_demand(batch_count)
|
|
|
+ generate_result = self.generate_location_by_demand(batch_count,bound_list_id)
|
|
|
if generate_result['code'] != '200':
|
|
|
+ current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id).first()
|
|
|
+ if current_WCS:
|
|
|
+ OutboundService.process_next_task()
|
|
|
+ return Response({"code": "200", "msg": "Success 再次发送任务"}, status=200)
|
|
|
+
|
|
|
return Response(generate_result, status=500)
|
|
|
container_list = generate_result['data']
|
|
|
|
|
|
logger.info(f"生成出库任务: {container_list}")
|
|
|
+
|
|
|
|
|
|
OutboundService.create_initial_tasks(container_list,bound_list_id)
|
|
|
|
|
@@ -1180,6 +1257,8 @@ class OutTaskViewSet(viewsets.ModelViewSet):
|
|
|
logger.error(f"任务生成失败: {str(e)}")
|
|
|
return Response({"code": "500", "msg": str(e)}, status=500)
|
|
|
|
|
|
+
|
|
|
+
|
|
|
|
|
|
def get_batch_count_by_boundlist(self,bound_list_id):
|
|
|
try:
|
|
@@ -1251,13 +1330,14 @@ class OutTaskViewSet(viewsets.ModelViewSet):
|
|
|
logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
|
|
|
return {}
|
|
|
|
|
|
- def generate_location_by_demand(self,demand_list):
|
|
|
+ def generate_location_by_demand(self,demand_list,bound_list_id):
|
|
|
|
|
|
try:
|
|
|
return_location =[]
|
|
|
for demand_id, demand_qty in demand_list.items():
|
|
|
container_list = self.get_location_by_status_and_batch(2, demand_id)
|
|
|
if not container_list:
|
|
|
+
|
|
|
return {"code": "500", "msg": f"批次 {demand_id} 不存在"}
|
|
|
container_id_list = container_list.keys()
|
|
|
container_order = self.get_order_by_batch(container_id_list,demand_id)
|
|
@@ -1273,22 +1353,93 @@ class OutTaskViewSet(viewsets.ModelViewSet):
|
|
|
current_qty = 0
|
|
|
for container in order:
|
|
|
container_detail_obj = ContainerDetailModel.objects.filter(container_id=container['container_number'],batch_id=demand_id,status=2).all()
|
|
|
+ container_obj = ContainerListModel.objects.filter(container_code=container['container_number']).first()
|
|
|
+ if not container_obj:
|
|
|
+ return {"code": "500", "msg": f"托盘 {container['container_number']} 不存在"}
|
|
|
if not container_detail_obj:
|
|
|
return {"code": "500", "msg": f"托盘上无该批次,请检查{container['container_number']} 不存在"}
|
|
|
goods_qty = 0
|
|
|
for obj in container_detail_obj:
|
|
|
goods_qty += obj.goods_qty
|
|
|
if current_qty < demand_qty:
|
|
|
+ now_qty = current_qty
|
|
|
current_qty += goods_qty
|
|
|
return_location.append(container)
|
|
|
+ logger.info(f"批次 {demand_id} 托盘 {container['container_number']} 当前数量 {current_qty}")
|
|
|
+ self.create_or_update_container_operation(container_obj,demand_id,bound_list_id,203,min(demand_qty-now_qty,goods_qty),min(demand_qty-now_qty,goods_qty))
|
|
|
+ self.update_container_detail_out_qty(container_obj,demand_id)
|
|
|
else:
|
|
|
break
|
|
|
return {"code": "200", "msg": "Success", "data": return_location}
|
|
|
except Exception as e:
|
|
|
return {"code": "500", "msg": str(e)}
|
|
|
|
|
|
+ def create_or_update_container_operation(self,container_obj,batch_id,bound_id,to_location,goods_qty,goods_weight):
|
|
|
+ try:
|
|
|
+ container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,bound_id=bound_id,operation_type="outbound").first()
|
|
|
+
|
|
|
+ if container_operation_obj:
|
|
|
+ logger.info(f"[0]查询出库任务: {container_operation_obj.operation_type} ")
|
|
|
+ logger.info(f"更新出库任务: {container_obj.container_code} 批次 {batch_id} 出库需求: {bound_id} 数量: {goods_qty} 重量: {goods_weight}")
|
|
|
+ container_operation_obj.to_location = to_location
|
|
|
+ container_operation_obj.goods_qty = goods_qty
|
|
|
+ container_operation_obj.goods_weight = goods_weight
|
|
|
+ container_operation_obj.save()
|
|
|
+
|
|
|
+ else:
|
|
|
+ logger.info(f"创建出库任务: {container_obj.container_code} 批次 {batch_id} 出库需求: {bound_id} 数量: {goods_qty} 重量: {goods_weight}")
|
|
|
+ batch = BoundBatchModel.objects.filter(id=batch_id).first()
|
|
|
+ if not batch:
|
|
|
+ return {"code": "500", "msg": f"批次 {batch_id} 不存在"}
|
|
|
+ ContainerOperationModel.objects.create(
|
|
|
+ month = int(timezone.now().strftime("%Y%m")),
|
|
|
+ container = container_obj,
|
|
|
+ goods_code = batch.goods_code,
|
|
|
+ goods_desc = batch.goods_desc,
|
|
|
+ operation_type ="outbound",
|
|
|
+ batch_id = batch_id,
|
|
|
+ bound_id = bound_id,
|
|
|
+ goods_qty = goods_qty,
|
|
|
+ goods_weight = goods_weight,
|
|
|
+ from_location = container_obj.current_location,
|
|
|
+ to_location= to_location,
|
|
|
+ timestamp=timezone.now(),
|
|
|
+ operator="WMS",
|
|
|
+ memo=f"出库需求: {bound_id}, 批次: {batch_id}, 数量: {goods_qty}"
|
|
|
+ )
|
|
|
+
|
|
|
+ return {"code": "200", "msg": "Success"}
|
|
|
+ except Exception as e:
|
|
|
+ return {"code": "500", "msg": str(e)}
|
|
|
|
|
|
|
|
|
+ def update_container_detail_out_qty(self,container_obj,batch_id):
|
|
|
+ try:
|
|
|
+ logger.info(f"[1]更新托盘出库数量: {container_obj.container_code} 批次 {batch_id} ")
|
|
|
+ container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,operation_type="outbound").all()
|
|
|
+ if not container_operation_obj:
|
|
|
+ logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无出库任务")
|
|
|
+ return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无出库任务"}
|
|
|
+ container_detail_obj = ContainerDetailModel.objects.filter(container=container_obj,batch_id=batch_id,status=2).first()
|
|
|
+ if not container_detail_obj:
|
|
|
+ logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无批次信息")
|
|
|
+ return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无批次信息"}
|
|
|
+ out_qty = 0
|
|
|
+ for obj in container_operation_obj:
|
|
|
+ out_qty += obj.goods_qty
|
|
|
+ if out_qty >= container_detail_obj.goods_qty:
|
|
|
+ out_qty = container_detail_obj.goods_qty
|
|
|
+ container_detail_obj.status = 3
|
|
|
+ break
|
|
|
+ if out_qty == 0:
|
|
|
+ logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无出库数量")
|
|
|
+ return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无出库数量"}
|
|
|
+ container_detail_obj.goods_out_qty = out_qty
|
|
|
+ container_detail_obj.save()
|
|
|
+ return {"code": "200", "msg": "Success"}
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ return {"code": "500", "msg": str(e)}
|
|
|
|
|
|
class BatchViewSet(viewsets.ModelViewSet):
|
|
|
authentication_classes = []
|