|
@@ -21,6 +21,9 @@ from .serializers import LocationGroupListSerializer,LocationGroupPostSerializer
|
|
|
# 以后添加模块时,只需要在这里添加即可
|
|
|
from rest_framework.permissions import AllowAny
|
|
|
from container.models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel
|
|
|
+import copy
|
|
|
+import json
|
|
|
+from collections import defaultdict
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# 库位分配
|
|
@@ -208,7 +211,7 @@ class LocationAllocation:
|
|
|
# fun:get_batch_status: 获取批次状态
|
|
|
# fun:get_batch: 获取批次
|
|
|
# fun:get_location_list_remainder: 获取可用库位的c_number列表
|
|
|
-
|
|
|
+ # fun
|
|
|
# fun:get_location_by_type_remainder: 根据库位类型获取库位
|
|
|
# fun:get_location_by_type: 第一次入库,根据库位类型获取库位
|
|
|
# fun:get_location_by_status: 根据库位状态获取库位
|
|
@@ -227,7 +230,6 @@ class LocationAllocation:
|
|
|
|
|
|
if not container:
|
|
|
logger.error(f"托盘 {container_code} 不存在")
|
|
|
- print(f"托盘 {container_code} 不存在")
|
|
|
return None
|
|
|
# 2. 获取关联的批次明细
|
|
|
container_detail = ContainerDetailModel.objects.filter(
|
|
@@ -235,11 +237,9 @@ class LocationAllocation:
|
|
|
status=1
|
|
|
).first()
|
|
|
if not container_detail:
|
|
|
- print (f"容器 {container_code} 未组盘")
|
|
|
logger.error(f"容器 {container_code} 未组盘")
|
|
|
return None
|
|
|
- else:
|
|
|
- print (f"容器 {container_code} 已组盘")
|
|
|
+
|
|
|
batch_container = ContainerDetailModel.objects.filter(
|
|
|
batch = container_detail.batch.id,
|
|
|
status = 1
|
|
@@ -297,51 +297,7 @@ class LocationAllocation:
|
|
|
print(f"获取库位组剩余数量失败:{str(e)}")
|
|
|
return None
|
|
|
|
|
|
- def get_location_type(self,container_code):
|
|
|
- """
|
|
|
- :param container_code: 托盘码
|
|
|
- :return: 库位类型列表
|
|
|
- """
|
|
|
- batch = self.get_batch(container_code)
|
|
|
- if not batch:
|
|
|
- print(f"类型分配中批次号获取失败!")
|
|
|
- return None
|
|
|
- location_solution = alloction_pre.objects.filter(batch_number=batch).first()
|
|
|
- if not location_solution:
|
|
|
- # 1. 获取托盘码对应批次号下所有的托盘数目
|
|
|
- container_number = self.get_pallet_count_by_batch(container_code)
|
|
|
- print (f"该批次下托盘总数:{container_number}")
|
|
|
- # 2. 计算每层剩余的库位数目
|
|
|
- layer_number=self.get_pallet_count_by_batch()
|
|
|
- # 3. 查看最新库位分配方案
|
|
|
- location_solution_last = alloction_pre.objects.filter().order_by('-id').first()
|
|
|
- if not location_solution_last:
|
|
|
- layer1_pressure =0
|
|
|
- layer2_pressure =0
|
|
|
- layer3_pressure =0
|
|
|
- else:
|
|
|
- layer1_pressure = location_solution_last.layer1_pressure
|
|
|
- layer2_pressure = location_solution_last.layer2_pressure
|
|
|
- layer3_pressure = location_solution_last.layer3_pressure
|
|
|
- # 4. 根据工作压力和库位类型,使用贪心算法,(AGV只有两个库位,故只给考虑1 2 层工作压力,第三层仅作为1,2层的补充,不考虑压力)获取库位类型列表,完成任务之后更新压力/TODO:需要考虑库位类型和库位数量的关系
|
|
|
- if layer1_pressure <=layer2_pressure:
|
|
|
-
|
|
|
- print(f"库位类型:{location_type}")
|
|
|
- # 5. 保存库位分配方案
|
|
|
- alloction_pre.objects.create(
|
|
|
- batch_number=batch,
|
|
|
- layer1_pressure=layer1_pressure,
|
|
|
- layer2_pressure=layer2_pressure,
|
|
|
- layer3_pressure=layer3_pressure,
|
|
|
-
|
|
|
- location_type=','.join(location_type)
|
|
|
- )
|
|
|
- return location_type
|
|
|
- else:
|
|
|
- print(f"库位类型:{location_solution.location_type}")
|
|
|
- return location_solution.location_type.split(",")
|
|
|
|
|
|
-
|
|
|
@transaction.atomic
|
|
|
def update_location_container_link(self,location_code,container_code):
|
|
|
"""
|
|
@@ -511,7 +467,6 @@ class LocationAllocation:
|
|
|
logger.error(f"更新批次状态失败:{str(e)}")
|
|
|
print(f"更新批次状态失败:{str(e)}")
|
|
|
return False
|
|
|
-
|
|
|
def get_batch_status(self,container_code):
|
|
|
"""
|
|
|
获取批次状态
|
|
@@ -636,8 +591,159 @@ class LocationAllocation:
|
|
|
ordered_groups = location_groups.none()
|
|
|
|
|
|
locations = []
|
|
|
- locations.extend(ordered_groups.first().location_items.all()) # 假设location_items是关联字段
|
|
|
+ locations.extend(ordered_groups.first().location_items.all())
|
|
|
return locations if locations else None
|
|
|
+
|
|
|
+ def get_location_type(self, container_code):
|
|
|
+ """
|
|
|
+ 智能库位分配核心算法
|
|
|
+ :param container_code: 托盘码
|
|
|
+ :return: 库位类型分配方案
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ batch = self.get_batch(container_code)
|
|
|
+ if not batch:
|
|
|
+ logger.error("批次信息获取失败")
|
|
|
+ return None
|
|
|
+
|
|
|
+ # 检查已有分配方案
|
|
|
+ existing_solution = alloction_pre.objects.filter(batch_number=batch).first()
|
|
|
+ if existing_solution:
|
|
|
+ return existing_solution.layer_pre_type
|
|
|
+
|
|
|
+ # 获取关键参数
|
|
|
+ total_pallets = self.get_pallet_count_by_batch(container_code)
|
|
|
+ layer_capacity = self.get_left_locationGroup_number_by_type()
|
|
|
+ current_pressure = self.get_current_pressure()
|
|
|
+
|
|
|
+ # 测试参数
|
|
|
+ # total_pallets = 3
|
|
|
+ # layer_capacity = [{'T1': 29, 'T2': 14, 'S4': 10, 'T4': 27, 'T5': 27}, {'T1': 0, 'T2': 0, 'S4': 0, 'T4': 0, 'T5': 21}, {'T1': 29, 'T2': 14, 'S4': 10, 'T4': 27, 'T5': 27}]
|
|
|
+ # current_pressure = [1,0,0]
|
|
|
+ print(f"[1]托盘数目: {total_pallets}")
|
|
|
+ print(f"[2]层容量: {layer_capacity}")
|
|
|
+ # print(f"[3]当前压力: {current_pressure}")
|
|
|
+
|
|
|
+ # 定义库位容量表
|
|
|
+ LOCATION_CAPACITY = {'T1':1, 'T2':2, 'S4':4, 'T4':4, 'T5':5}
|
|
|
+
|
|
|
+ def allocate(remain, path, pressure,layer_capacity_state, depth=0):
|
|
|
+ # 终止条件
|
|
|
+ if remain <= 0:
|
|
|
+ return [path,pressure]
|
|
|
+ # 深拷贝当前层容量状态
|
|
|
+ new_layer_capacity = copy.deepcopy(layer_capacity_state)
|
|
|
+ # print(f"[2]当前剩余: {new_layer_capacity}")
|
|
|
+ # 压力平衡系数
|
|
|
+ balance_factor = 1.0 - (0.1 * min(depth, 5))
|
|
|
+ # 层选择策略
|
|
|
+ print (f"[3]当前压力: {pressure}")
|
|
|
+ layer_priority = sorted(
|
|
|
+ [(0, pressure[0]), (1, pressure[1]), (2, pressure[2])],
|
|
|
+ key=lambda x: (x[1] * balance_factor, x[0])
|
|
|
+ )
|
|
|
+
|
|
|
+ for layer, _ in layer_priority:
|
|
|
+ # 生成候选库位类型(按效率和容量排序)
|
|
|
+ # 排序键函数 :
|
|
|
+ # min(x[1], remain) 计算当前库位类型的容量 c 和剩余数量 remain 中的较小值。
|
|
|
+ # -min(x[1], remain) 和 -x[1] 都使用了负号,这意味着排序是按降序进行的。
|
|
|
+ # 首先按 -min(x[1], remain) 排序,即优先选择容量与剩余数量更接近的库位类型。
|
|
|
+ # 如果有多个库位类型的容量与剩余数量相同,则按 -x[1] 排序,即优先选择容量更大的库位类型。
|
|
|
+ print(f"[4]当前层: {layer+1}, 剩余: {remain}, 容量状态: {new_layer_capacity[layer]}")
|
|
|
+ candidates = sorted(
|
|
|
+ [(t, c) for t, c in LOCATION_CAPACITY.items()
|
|
|
+ if new_layer_capacity[layer].get(t,0) > 0],
|
|
|
+ key=lambda x: (abs(x[1]-remain), -x[1])
|
|
|
+ )
|
|
|
+
|
|
|
+ print(f"[4]候选库位类型: {candidates}")
|
|
|
+ for loc_type, cap in candidates:
|
|
|
+ # 更新容量状态
|
|
|
+ updated_capacity = copy.deepcopy(new_layer_capacity)
|
|
|
+ updated_capacity[layer][loc_type] -= 1 # 占用一个库位组
|
|
|
+ # 允许适度空间浪费(当剩余<2时)
|
|
|
+ # effective_cap = min(cap, remain) if (cap - remain) < 2 else cap
|
|
|
+ effective_cap = min(cap, remain)
|
|
|
+
|
|
|
+ if effective_cap <= remain:
|
|
|
+ new_remain = remain - effective_cap
|
|
|
+ new_pressure = pressure.copy()
|
|
|
+ new_pressure[layer] += effective_cap # 按实际存放数计算压力
|
|
|
+ result = allocate(
|
|
|
+ new_remain,
|
|
|
+ path + [f"{layer+1}_{loc_type}"],
|
|
|
+ new_pressure,
|
|
|
+ updated_capacity,
|
|
|
+ depth + 1
|
|
|
+ )
|
|
|
+ if result:
|
|
|
+ print (f"[5]分配方案: {result}")
|
|
|
+ return result
|
|
|
+
|
|
|
+ return None
|
|
|
+
|
|
|
+ # 执行分配
|
|
|
+ allocation = allocate(total_pallets, [], [current_pressure[0], current_pressure[1],current_pressure[2]], layer_capacity)
|
|
|
+
|
|
|
+ if not allocation:
|
|
|
+ logger.error("无法生成有效分配方案")
|
|
|
+ return None
|
|
|
+
|
|
|
+ # 保存分配方案
|
|
|
+ allocation_json = self.divide_solution_by_layer(allocation[0])
|
|
|
+ print(f"[6]分配方案: {allocation_json}")
|
|
|
+ solution = alloction_pre(
|
|
|
+ batch_number=batch,
|
|
|
+ layer_pre_type =allocation_json,
|
|
|
+ layer1_pressure=allocation[1][0],
|
|
|
+ layer2_pressure=allocation[1][1],
|
|
|
+ layer3_pressure=allocation[1][2],
|
|
|
+ )
|
|
|
+
|
|
|
+ solution.save()
|
|
|
+
|
|
|
+ return [loc.split('_')[1] for loc in allocation[0]]
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"分配算法异常:{str(e)}")
|
|
|
+ return None
|
|
|
+
|
|
|
+ def divide_solution_by_layer(self, data):
|
|
|
+
|
|
|
+ # 统计所有存在的层级
|
|
|
+ layer_counts = defaultdict(lambda: defaultdict(int))
|
|
|
+ existing_layers = set()
|
|
|
+
|
|
|
+ for item in data:
|
|
|
+ # 分割层级和类型
|
|
|
+ try:
|
|
|
+ layer, loc_type = item.split('_')
|
|
|
+ layer_num = int(layer)
|
|
|
+ existing_layers.add(layer_num)
|
|
|
+ layer_counts[layer_num][loc_type] += 1
|
|
|
+ except (ValueError, IndexError):
|
|
|
+ continue # 跳过无效格式的数据
|
|
|
+
|
|
|
+ # 确定最大层级(至少包含1层)
|
|
|
+ max_layer = max(existing_layers) if existing_layers else 1
|
|
|
+
|
|
|
+ # 构建包含所有层级的最终结果
|
|
|
+ final_result = {}
|
|
|
+ for layer in range(1, max_layer + 1):
|
|
|
+ final_result[str(layer)] = dict(layer_counts.get(layer, {}))
|
|
|
+
|
|
|
+ return json.dumps(final_result, indent=2)
|
|
|
+
|
|
|
+ def get_current_pressure(self):
|
|
|
+ """获取实时工作压力"""
|
|
|
+ last_solution = alloction_pre.objects.order_by('-id').first()
|
|
|
+ return [
|
|
|
+ last_solution.layer1_pressure if last_solution else 0,
|
|
|
+ last_solution.layer2_pressure if last_solution else 0,
|
|
|
+ last_solution.layer3_pressure if last_solution else 0,
|
|
|
+ ]
|
|
|
+
|
|
|
@transaction.atomic
|
|
|
def get_location_by_status(self,container_code,start_location,layer):
|
|
|
"""
|
|
@@ -647,13 +753,12 @@ class LocationAllocation:
|
|
|
:param layer: 层数 限定层数
|
|
|
:return: 库位列表
|
|
|
"""
|
|
|
- # 1. 获取批次状态 102 为已组盘 103 为部分入库 104 为全部入库
|
|
|
+ # 1. 获取批次状态 1 为已组盘 2 为部分入库 3 为全部入库
|
|
|
status = self.get_batch_status(container_code)
|
|
|
#
|
|
|
if status == 1:
|
|
|
# 2. 获取库位组
|
|
|
print (f"第一次入库")
|
|
|
-
|
|
|
location_type_list = self.get_location_type(container_code)
|
|
|
print(f"库位类型:{location_type_list}")
|
|
|
location_list = self.get_location_by_type(location_type_list,start_location,layer)
|
|
@@ -685,46 +790,5 @@ class LocationAllocation:
|
|
|
return location_list
|
|
|
|
|
|
|
|
|
- """
|
|
|
- 根据库位状态清除库位
|
|
|
- :param container_code: 托盘码
|
|
|
- :param start_location: 起始库位 if in1 优先考虑left_priority, if in2 优先考虑right_priority 就是获取库位组列表之后进行排序
|
|
|
- :param layer: 层数 限定层数
|
|
|
- :return: 库位列表
|
|
|
- """
|
|
|
- # 1. 获取批次状态 102 为已组盘 103 为部分入库 104 为全部入库
|
|
|
- status = self.get_batch_status(container_code)
|
|
|
- #
|
|
|
- if status == 1:
|
|
|
- # 2. 获取库位组
|
|
|
- print (f"第一次入库")
|
|
|
- container_number = self.get_pallet_count_by_batch(container_code)
|
|
|
- print (f"该批次下托盘总数:{container_number}")
|
|
|
- location_type_list = self.get_location_type(container_number)
|
|
|
- print(f"库位类型:{location_type_list}")
|
|
|
- location_list = self.get_location_by_type(location_type_list,start_location,layer)
|
|
|
- print(f"库位列表:{location_list}")
|
|
|
- location_min_index = self.get_location_list_remainder(location_list)
|
|
|
- print(f"库位安排到第{location_min_index+1}个库位:{location_list[location_min_index]}")
|
|
|
- if not location_list[location_min_index]:
|
|
|
- # 库位已满,返回None
|
|
|
- return None
|
|
|
- else:
|
|
|
- location_code = location_list[location_min_index].location_code
|
|
|
- self.update_location_status(location_code,'occupied')
|
|
|
- return location_list[location_min_index]
|
|
|
- elif status == 2:
|
|
|
- print(f"部分入库")
|
|
|
- # 2. 获取库位组
|
|
|
- location_list = self.get_location_by_type_remainder(self.get_batch(container_code),layer)
|
|
|
- print(f"库位列表:{location_list}")
|
|
|
- location_min_index = 1
|
|
|
- print(f"库位安排到第{location_min_index+1}个库位:{location_list[location_min_index]}")
|
|
|
- if not location_list[location_min_index]:
|
|
|
- # 库位已满,返回None
|
|
|
- return None
|
|
|
- else:
|
|
|
- location_code = location_list[location_min_index].location_code
|
|
|
- self.update_location_status(location_code,'occupied')
|
|
|
- return location_list[location_min_index]
|
|
|
+
|
|
|
|