views.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. from rest_framework import viewsets
  2. from utils.page import MyPageNumberPagination
  3. from utils.datasolve import sumOfList, transportation_calculate
  4. from utils.md5 import Md5
  5. from rest_framework.filters import OrderingFilter
  6. from django_filters.rest_framework import DjangoFilterBackend
  7. from rest_framework.response import Response
  8. from rest_framework.exceptions import APIException
  9. from django.utils import timezone
  10. from django.db import transaction
  11. import logging
  12. from rest_framework import status
  13. from .models import DeviceModel,LocationModel,LocationGroupModel,LocationContainerLink,LocationChangeLog,alloction_pre,base_location
  14. from bound.models import BoundBatchModel,BoundDetailModel,BoundListModel
  15. from .filter import DeviceFilter,LocationFilter,LocationContainerLinkFilter,LocationChangeLogFilter,LocationGroupFilter
  16. from .serializers import LocationListSerializer,LocationPostSerializer
  17. from .serializers import LocationGroupListSerializer,LocationGroupPostSerializer
  18. # 以后添加模块时,只需要在这里添加即可
  19. from rest_framework.permissions import AllowAny
  20. from container.models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel
  21. import copy
  22. import json
  23. from collections import defaultdict
  24. logger = logging.getLogger(__name__)
  25. # 库位分配
  26. # 入库规则函数
  27. # 逻辑根据批次下的托盘数目来找满足区间范围的库位,按照优先级排序,
  28. class locationViewSet(viewsets.ModelViewSet):
  29. """
  30. retrieve:
  31. Response a data list(get)
  32. list:
  33. Response a data list(all)
  34. create:
  35. Create a data line(post)
  36. delete:
  37. Delete a data line(delete)
  38. """
  39. # authentication_classes = [] # 禁用所有认证类
  40. # permission_classes = [AllowAny] # 允许任意访问
  41. pagination_class = MyPageNumberPagination
  42. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  43. ordering_fields = ['id', "create_time", "update_time", ]
  44. filter_class = LocationFilter
  45. def get_project(self):
  46. try:
  47. id = self.kwargs.get('pk')
  48. return id
  49. except:
  50. return None
  51. def get_queryset(self):
  52. id = self.get_project()
  53. if self.request.user:
  54. if id is None:
  55. return LocationModel.objects.filter()
  56. else:
  57. return LocationModel.objects.filter( id=id)
  58. else:
  59. return LocationModel.objects.none()
  60. def get_serializer_class(self):
  61. if self.action == 'list':
  62. return LocationListSerializer
  63. elif self.action == 'update':
  64. return LocationPostSerializer
  65. elif self.action =='retrieve':
  66. return LocationListSerializer
  67. def update(self, request, *args, **kwargs):
  68. qs = self.get_object()
  69. data = self.request.data
  70. location_code = data.get('location_code')
  71. # 处理库位对象
  72. location_obj = LocationModel.objects.filter(location_code=location_code).first()
  73. if not location_obj:
  74. logger.info(f"库位 {location_code} 不存在")
  75. return Response(
  76. {'code': '400', 'message': '库位不存在', 'data': None},
  77. status=status.HTTP_400_BAD_REQUEST
  78. )
  79. else:
  80. data['id'] = location_obj.id
  81. logger.info(f"库位 {location_code} 已存在")
  82. serializer = self.get_serializer(qs, data=data)
  83. serializer.is_valid(raise_exception=True)
  84. serializer.save()
  85. headers = self.get_success_headers(serializer.data)
  86. self.handle_group_location_status(location_code,location_obj.location_group)
  87. return Response(serializer.data, status=200, headers=headers)
  88. def handle_group_location_status(self,location_code,location_group):
  89. """
  90. 处理库位组和库位的关联关系
  91. :param location_code: 库位编码
  92. :param location_group: 库位组编码
  93. :return:
  94. """
  95. # 1. 获取库位空闲状态的库位数目
  96. location_obj_number = LocationModel.objects.filter(
  97. location_group=location_group,
  98. status='available'
  99. ).all().count()
  100. # 2. 获取库位组对象
  101. logger.info(f"库位组 {location_group} 下的库位数目:{location_obj_number}")
  102. # 1. 获取库位和库位组的关联关系
  103. location_group_obj = LocationGroupModel.objects.filter(
  104. group_code=location_group
  105. ).first()
  106. if not location_group_obj:
  107. logger.info(f"库位组 {location_group} 不存在")
  108. return None
  109. else:
  110. if location_obj_number == 0:
  111. # 库位组库位已满,更新库位组状态为full
  112. location_group_obj.status = 'full'
  113. location_group_obj.save()
  114. elif location_obj_number < location_group_obj.max_capacity:
  115. location_group_obj.status = 'occupied'
  116. location_group_obj.save()
  117. else:
  118. location_group_obj.status = 'available'
  119. location_group_obj.save()
  120. class locationGroupViewSet(viewsets.ModelViewSet):
  121. """
  122. retrieve:
  123. Response a data list(get)
  124. list:
  125. Response a data list(all)
  126. create:
  127. Create a data line(post)
  128. delete:
  129. Delete a data line(delete)
  130. """
  131. # authentication_classes = [] # 禁用所有认证类
  132. # permission_classes = [AllowAny] # 允许任意访问
  133. pagination_class = MyPageNumberPagination
  134. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  135. ordering_fields = ['id', "create_time", "update_time", ]
  136. filter_class = LocationGroupFilter
  137. def get_project(self):
  138. try:
  139. id = self.kwargs.get('pk')
  140. return id
  141. except:
  142. return None
  143. def get_queryset(self):
  144. id = self.get_project()
  145. if self.request.user:
  146. if id is None:
  147. return LocationGroupModel.objects.filter()
  148. else:
  149. return LocationGroupModel.objects.filter(id=id)
  150. else:
  151. return LocationGroupModel.objects.none()
  152. def get_serializer_class(self):
  153. if self.action == 'list':
  154. return LocationGroupListSerializer
  155. elif self.action == 'update':
  156. return LocationGroupPostSerializer
  157. elif self.action =='retrieve':
  158. return LocationGroupListSerializer
  159. def update(self, request, *args, **kwargs):
  160. data = self.request.data
  161. order_month = str(timezone.now().strftime('%Y%m'))
  162. data['month'] = order_month
  163. group_code = data.get('group_code')
  164. # 处理库位组对象
  165. group_obj = LocationGroupModel.objects.filter(group_code=group_code).first()
  166. if group_obj:
  167. data['id'] = group_obj.id
  168. logger.info(f"库位组 {group_code} 已存在")
  169. else:
  170. logger.info(f"库位组 {group_code} 不存在,创建库位组对象")
  171. serializer_list = LocationGroupPostSerializer(data=data)
  172. serializer_list.is_valid(raise_exception=True)
  173. serializer_list.save()
  174. data['id'] = serializer_list.data.get('id')
  175. return Response(data, status=status.HTTP_201_CREATED)
  176. class LocationAllocation:
  177. # 入库规则函数
  178. # fun:get_pallet_count_by_batch: 根据托盘码查询批次下托盘总数
  179. # fun:get_left_locationGroup_number_by_type: 获取每层库位组剩余数量
  180. # fun:get_location_type: 根据托盘数目获取库位类型
  181. # fun:update_location_container_link: 更新库位和托盘的关联关系
  182. # fun:update_location_group_batch: 更新库位组的批次
  183. # fun:update_batch_status: 更新批次状态yes/no
  184. # fun:update_location_status: 更新库位状态和
  185. # fun:up
  186. # fun:get_batch_status: 获取批次状态
  187. # fun:get_batch: 获取批次
  188. # fun:get_location_list_remainder: 获取可用库位的c_number列表
  189. # fun
  190. # fun:get_location_by_type_remainder: 根据库位类型获取库位
  191. # fun:get_location_by_type: 第一次入库,根据库位类型获取库位
  192. # fun:get_location_by_status: 根据库位状态获取库位
  193. @transaction.atomic
  194. def get_pallet_count_by_batch(self, container_code):
  195. """
  196. 根据托盘码查询批次下托盘总数
  197. :param container_code: 要查询的托盘码
  198. :return: 所属批次下的托盘总数
  199. """
  200. # 1. 通过托盘码获取容器详情
  201. container = ContainerListModel.objects.filter(
  202. container_code=container_code
  203. ).first()
  204. if not container:
  205. logger.error(f"托盘 {container_code} 不存在")
  206. return None
  207. # 2. 获取关联的批次明细
  208. container_detail = ContainerDetailModel.objects.filter(
  209. container=container.id,
  210. status=1
  211. ).first()
  212. if not container_detail:
  213. logger.error(f"托盘 {container_code} 未组盘")
  214. return None
  215. batch_container = ContainerDetailModel.objects.filter(
  216. batch = container_detail.batch.id,
  217. status = 1
  218. ).all()
  219. # 统计批次下的不同托盘 item.contianer_id
  220. batch_container_count = 0
  221. container_ids = []
  222. for item in batch_container:
  223. if item.container_id not in container_ids:
  224. batch_container_count = batch_container_count + 1
  225. container_ids.append(item.container_id)
  226. batch_item = BoundBatchModel.objects.filter( bound_number = container_detail.batch.bound_number).first()
  227. if not batch_item:
  228. print(f"批次号获取失败!")
  229. return None
  230. batch_item.container_number = batch_container_count
  231. batch_item.save()
  232. return batch_container_count
  233. def get_left_locationGroup_number_by_type(self):
  234. """
  235. 获取每层库位组剩余数量
  236. :return:
  237. """
  238. try:
  239. # 定义库位组和层号
  240. group = ['T1', 'T2', 'S4', 'T4', 'T5']
  241. layer = [1, 2, 3]
  242. # 初始化结果列表,包含三个空字典对应三个层
  243. left_number = [{} for _ in layer]
  244. for item in group:
  245. for idx, layer_num in enumerate(layer):
  246. # 检查库位组是否存在(不考虑状态)
  247. exists = LocationGroupModel.objects.filter(
  248. group_type=item,
  249. layer=layer_num
  250. ).exists()
  251. if not exists:
  252. print(f"库位组 {item}_{layer_num} 不存在")
  253. left_number[idx][item] = 0
  254. else:
  255. # 统计可用状态的库位组数量
  256. count = LocationGroupModel.objects.filter(
  257. group_type=item,
  258. layer=layer_num,
  259. status='available'
  260. ).count()
  261. left_number[idx][item] = count
  262. return left_number
  263. except Exception as e:
  264. logger.error(f"获取库位组剩余数量失败:{str(e)}")
  265. print(f"获取库位组剩余数量失败:{str(e)}")
  266. return None
  267. @transaction.atomic
  268. def update_location_container_link(self,location_code,container_code):
  269. """
  270. 更新库位和托盘的关联关系
  271. :param location_code: 库位编码
  272. :param container_code: 托盘编码
  273. :return:
  274. """
  275. try:
  276. # 1. 获取库位和托盘的关联关系
  277. location = LocationModel.objects.filter(
  278. location_code=location_code
  279. ).first()
  280. container = ContainerListModel.objects.filter(
  281. container_code=container_code
  282. ).first()
  283. # 2. 如果库位和托盘的关联关系不存在,创建新的关联关系
  284. if not LocationContainerLink.objects.filter(location=location).exists():
  285. location_container_link = LocationContainerLink(
  286. location=location,
  287. container=container
  288. )
  289. location_container_link.save()
  290. print(f"更新库位和托盘的关联关系成功!")
  291. return True
  292. # 3. 更新库位和托盘的关联关系
  293. else:
  294. LocationContainerLink.objects.filter(location=location).update(location=location, container=container)
  295. print(f"更新库位和托盘的关联关系成功!")
  296. return True
  297. except Exception as e:
  298. logger.error(f"更新库位和托盘的关联关系失败:{str(e)}")
  299. print(f"更新库位和托盘的关联关系失败:{str(e)}")
  300. return False
  301. def update_location_group_batch(self,location,container_code):
  302. """
  303. :param location: 库位对象
  304. :param container_code: 托盘码
  305. :return:
  306. """
  307. try:
  308. # 1. 获取库位组
  309. location_group = LocationGroupModel.objects.filter(
  310. group_code=location.location_group
  311. ).first()
  312. if not location_group:
  313. print(f"库位组获取失败!")
  314. return False
  315. # 2. 更新库位组的批次
  316. bound_number=self.get_batch(container_code)
  317. if not bound_number:
  318. print(f"批次号获取失败!")
  319. return False
  320. location_group.current_batch = bound_number
  321. location_group.save()
  322. print(f"更新库位组的批次成功!")
  323. return True
  324. except Exception as e:
  325. logger.error(f"更新库位组的批次失败:{str(e)}")
  326. print(f"更新库位组的批次失败:{str(e)}")
  327. return False
  328. def update_location_status(self,location_code,status):
  329. """
  330. 更新库位状态
  331. :param location_code: 库位编码
  332. :param status: 库位状态
  333. :return:
  334. """
  335. try:
  336. # 1. 获取库位
  337. location = LocationModel.objects.filter(
  338. location_code=location_code
  339. ).first()
  340. if not location:
  341. print(f"库位获取失败!")
  342. return False
  343. # 2. 更新库位状态
  344. location.status = status
  345. location.save()
  346. print(f"更新库位状态成功!")
  347. return True
  348. except Exception as e:
  349. logger.error(f"更新库位状态失败:{str(e)}")
  350. print(f"更新库位状态失败:{str(e)}")
  351. return False
  352. def update_group_status_reserved(self,location_group_list):
  353. """
  354. 更新库位组状态
  355. :param location_group_list: 库位组对象列表
  356. :return:
  357. """
  358. try:
  359. for location_group in location_group_list:
  360. # 1. 获取库位组
  361. if not location_group:
  362. print(f"库位组获取失败!")
  363. return False
  364. # 2. 更新库位组状态
  365. location_group_id = location_group.split('_')[1]
  366. location_group_item = LocationGroupModel.objects.filter(
  367. id=location_group_id
  368. ).first()
  369. if not location_group_item:
  370. print(f"库位组 {location_group} 不存在")
  371. return False
  372. # 3. 更新库位组状态
  373. location_group_item.status = 'reserved'
  374. location_group_item.save()
  375. return True
  376. except Exception as e:
  377. logger.error(f"更新库位组状态失败:{str(e)}")
  378. print(f"更新库位组状态失败:{str(e)}")
  379. return False
  380. @transaction.atomic
  381. def update_location_group_status(self, location_code):
  382. """
  383. 更新库位组状态
  384. :param location_code: 库位编码
  385. :return:
  386. """
  387. try:
  388. # 1. 获取库位
  389. location = LocationModel.objects.filter(
  390. location_code=location_code
  391. ).first()
  392. if not location:
  393. print(f"库位获取失败!")
  394. return False
  395. # 2. 获取库位组
  396. location_group = LocationGroupModel.objects.filter(
  397. group_code=location.location_group
  398. ).first()
  399. if not location_group:
  400. print(f"库位组获取失败!")
  401. return False
  402. current=0
  403. for location_item in location_group.location_items.all():
  404. if location_item.status != 'available':
  405. current=current + 1
  406. # 3. 更新库位组状态
  407. if current == 0:
  408. location_group.status = 'available'
  409. elif current == location_group.max_capacity:
  410. location_group.status = 'full'
  411. else:
  412. location_group.status = 'occupied'
  413. location_group.current_goods_quantity = sum(
  414. [loc.current_quantity for loc in location_group.location_items.all()]
  415. )
  416. location_group.current_quantity = current
  417. location_group.save()
  418. print(f"更新库位组状态成功!")
  419. return True
  420. except Exception as e:
  421. logger.error(f"更新库位组状态失败:{str(e)}")
  422. print(f"更新库位组状态失败:{str(e)}")
  423. def update_batch_status(self,container_code,status):
  424. """
  425. 更新批次状态
  426. :param batch_id: 批次id
  427. :param status: 批次状态
  428. :return:
  429. """
  430. try:
  431. # 1. 通过托盘码获取容器详情
  432. container = ContainerListModel.objects.filter(
  433. container_code=container_code
  434. ).first()
  435. if not container:
  436. logger.error(f"托盘 {container_code} 不存在")
  437. print(f"托盘 {container_code} 不存在")
  438. return None
  439. # 2. 获取关联的批次明细
  440. container_detail = ContainerDetailModel.objects.filter(
  441. container=container.id,
  442. status=1
  443. ).first()
  444. if not container_detail:
  445. print (f"托盘 {container_code} 未组盘")
  446. logger.error(f"托盘 {container_code} 未组盘")
  447. return None
  448. # 3. 更新批次状态
  449. batch = container_detail.batch
  450. batch.status = status
  451. batch.save()
  452. print(f"更新批次状态成功!")
  453. return True
  454. except Exception as e:
  455. logger.error(f"更新批次状态失败:{str(e)}")
  456. print(f"更新批次状态失败:{str(e)}")
  457. return False
  458. def get_batch_status(self,container_code):
  459. """
  460. 获取批次状态
  461. :param container_code: 托盘码
  462. :return: 批次状态
  463. """
  464. # 1. 通过托盘码获取容器详情
  465. container = ContainerListModel.objects.filter(
  466. container_code=container_code
  467. ).first()
  468. if not container:
  469. logger.error(f"托盘 {container_code} 不存在")
  470. print(f"托盘 {container_code} 不存在")
  471. return None
  472. # 2. 获取关联的批次明细
  473. container_detail = ContainerDetailModel.objects.filter(
  474. container=container.id,
  475. status=1
  476. ).first()
  477. if not container_detail:
  478. print (f"托盘 {container_code} 未组盘")
  479. logger.error(f"托盘 {container_code} 未组盘")
  480. return None
  481. batch_status = container_detail.batch.status
  482. return batch_status
  483. def get_batch(self,container_code):
  484. """
  485. 获取批次
  486. :param container_code: 托盘码
  487. :return: 批次
  488. """
  489. # 1. 通过托盘码获取容器详情
  490. container = ContainerListModel.objects.filter(
  491. container_code=container_code
  492. ).first()
  493. if not container:
  494. logger.error(f"托盘 {container_code} 不存在")
  495. print(f"托盘 {container_code} 不存在")
  496. return None
  497. # 2. 获取关联的批次明细
  498. container_detail = ContainerDetailModel.objects.filter(
  499. container=container.id,
  500. status=1
  501. ).first()
  502. if not container_detail:
  503. print (f"托盘 {container_code} 未组盘")
  504. logger.error(f"托盘 {container_code} 未组盘")
  505. return None
  506. batch = container_detail.batch.bound_number
  507. return batch
  508. @transaction.atomic
  509. def get_location_list_remainder(self, location_group_list,container_code):
  510. """
  511. 获取可用库位的c_number列表
  512. :param location_list: 库位对象列表
  513. :return: 可用库位编号列表
  514. """
  515. if not location_group_list:
  516. return None
  517. min_c_number=1000
  518. min_c_number_index=1000
  519. current_task = self.get_current_finish_task(container_code)
  520. print(f"[1]当前已完成任务: {current_task}")
  521. # 按压力排序
  522. sorted_pressure = sorted(
  523. [(0, current_task[0]), (1, current_task[1]), (2, current_task[2])],
  524. key=lambda x: (-x[1], x[0])
  525. )
  526. # 交换第一和第二个元素的位置
  527. sorted_pressure[0], sorted_pressure[1] = sorted_pressure[1], sorted_pressure[0]
  528. print(f"[2]任务排序: {sorted_pressure}")
  529. print(f"[3]当前选择:{sorted_pressure[0][0]+1}")
  530. location_type_dict = json.loads(self.divide_solution_by_layer(location_group_list))
  531. # print(f"库位类型分配方案: {location_type_dict}")
  532. for layer, _ in sorted_pressure:
  533. if not location_type_dict.get(str(layer+1)):
  534. continue
  535. # print(f"当前层: {layer+1}")
  536. # print(f"当前层库位组: {location_type_dict[str(layer+1)].keys()}")
  537. for group_id in location_type_dict[str(layer+1)].keys():
  538. location_group = LocationGroupModel.objects.filter(
  539. id=group_id,
  540. ).first()
  541. if not location_group:
  542. continue
  543. location_list = location_group.location_items.filter(
  544. status='available'
  545. ).all().order_by('c_number')
  546. if not location_list:
  547. print(f"当前层库位组 {location_group.group_code} 可用库位: None")
  548. continue
  549. # 提取所有库位的 c_number
  550. c_numbers = [loc.c_number for loc in location_list]
  551. print(f"当前层库位组 {location_group.group_code} 可用库位: {c_numbers}")
  552. # 更新任务完成数目
  553. current_task[layer] = current_task[layer] + 1
  554. self.update_current_finish_task(container_code,current_task)
  555. return location_list[0]
  556. @transaction.atomic
  557. def get_location_type(self, container_code):
  558. """
  559. 智能库位分配核心算法
  560. :param container_code: 托盘码
  561. :return: 库位类型分配方案
  562. """
  563. try:
  564. batch = self.get_batch(container_code)
  565. if not batch:
  566. logger.error("批次信息获取失败")
  567. return None
  568. # 检查已有分配方案
  569. existing_solution = alloction_pre.objects.filter(batch_number=batch).first()
  570. if existing_solution:
  571. return existing_solution.layer_pre_type
  572. # 获取关键参数
  573. total_pallets = self.get_pallet_count_by_batch(container_code)
  574. layer_capacity = self.get_left_locationGroup_number_by_type()
  575. current_pressure = self.get_current_pressure()
  576. # 测试参数
  577. # total_pallets = 30
  578. # 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}]
  579. # current_pressure = [1,0,0]
  580. print(f"[1]托盘数目: {total_pallets}")
  581. print(f"[2]层容量: {layer_capacity}")
  582. # print(f"[3]当前压力: {current_pressure}")
  583. # 定义库位容量表
  584. LOCATION_CAPACITY = {'T1':1, 'T2':2, 'T4':4, 'S4':4, 'T5':5}
  585. def allocate(remain, path, pressure,real_pressure,layer_capacity_state, depth=0):
  586. # 终止条件
  587. if remain <= 0:
  588. return [path,real_pressure]
  589. # 深拷贝当前层容量状态
  590. new_layer_capacity = copy.deepcopy(layer_capacity_state)
  591. # print(f"[2]当前剩余: {new_layer_capacity}")
  592. # 压力平衡系数
  593. balance_factor = 1.0 - (0.1 * min(depth, 5))
  594. # 层选择策略
  595. print (f"[3]当前压力: {pressure}")
  596. layer_priority = sorted(
  597. [(0, pressure[0]), (1, pressure[1]), (2, pressure[2])],
  598. key=lambda x: (x[1] * balance_factor, x[0])
  599. )
  600. for layer, _ in layer_priority:
  601. # 生成候选库位类型(按效率和容量排序)
  602. # 排序键函数 :
  603. # min(x[1], remain) 计算当前库位类型的容量 c 和剩余数量 remain 中的较小值。
  604. # -min(x[1], remain) 和 -x[1] 都使用了负号,这意味着排序是按降序进行的。
  605. # 首先按 -min(x[1], remain) 排序,即优先选择容量与剩余数量更接近的库位类型。
  606. # 如果有多个库位类型的容量与剩余数量相同,则按 -x[1] 排序,即优先选择容量更大的库位类型。
  607. print(f"[4]当前层: {layer+1}, 剩余: {remain}, 容量状态: {new_layer_capacity[layer]}")
  608. candidates = sorted(
  609. [(t, c) for t, c in LOCATION_CAPACITY.items()
  610. if new_layer_capacity[layer].get(t,0) > 0],
  611. key=lambda x: (abs(x[1]-remain), -x[1])
  612. )
  613. print(f"[4]候选库位类型: {candidates}")
  614. for loc_type, cap in candidates:
  615. # 更新容量状态
  616. updated_capacity = copy.deepcopy(new_layer_capacity)
  617. updated_capacity[layer][loc_type] -= 1 # 占用一个库位组
  618. # 允许适度空间浪费(当剩余<2时)
  619. # effective_cap = min(cap, remain) if (cap - remain) < 2 else cap
  620. effective_cap = min(cap, remain)
  621. if effective_cap <= remain:
  622. new_remain = remain - effective_cap
  623. new_pressure = pressure.copy()
  624. for i in range(0, 3):
  625. new_pressure[i] -=1 if new_pressure[i] > 0 else 0
  626. new_pressure[layer] += effective_cap # 按实际存放数计算压力,此时别的楼层压力可能降下来了
  627. real_pressure[layer] += effective_cap # 实际压力
  628. result = allocate(
  629. new_remain,
  630. path + [f"{layer+1}_{loc_type}"],
  631. new_pressure,
  632. real_pressure,
  633. updated_capacity,
  634. depth + 1
  635. )
  636. if result:
  637. print (f"[5]分配方案: {result}")
  638. return result
  639. return None
  640. # 执行分配
  641. allocation = allocate(total_pallets, [], [current_pressure[0], current_pressure[1],current_pressure[2]],[current_pressure[0], current_pressure[1],current_pressure[2]], layer_capacity)
  642. if not allocation:
  643. logger.error("无法生成有效分配方案")
  644. return None
  645. # 保存分配方案
  646. allocation_json = self.divide_solution_by_layer(allocation[0])
  647. print(f"[6]分配方案: {allocation_json}")
  648. solution = alloction_pre(
  649. batch_number=batch,
  650. layer_pre_type =allocation_json
  651. )
  652. solution_pressure, created = base_location.objects.get_or_create(
  653. id=1,
  654. defaults={
  655. 'layer1_pressure': 0,
  656. 'layer2_pressure': 0,
  657. 'layer3_pressure': 0
  658. }
  659. )
  660. solution_pressure.layer1_pressure = allocation[1][0]
  661. solution_pressure.layer2_pressure = allocation[1][1]
  662. solution_pressure.layer3_pressure = allocation[1][2]
  663. solution.save()
  664. solution_pressure.save()
  665. return [loc.split('_')[1] for loc in allocation[0]]
  666. except Exception as e:
  667. logger.error(f"分配算法异常:{str(e)}")
  668. return None
  669. def divide_solution_by_layer(self, data):
  670. # 统计所有存在的层级
  671. layer_counts = defaultdict(lambda: defaultdict(int))
  672. existing_layers = set()
  673. for item in data:
  674. # 分割层级和类型
  675. try:
  676. layer, loc_type = item.split('_')
  677. layer_num = int(layer)
  678. existing_layers.add(layer_num)
  679. layer_counts[layer_num][loc_type] += 1
  680. except (ValueError, IndexError):
  681. continue # 跳过无效格式的数据
  682. # 确定最大层级(至少包含1层)
  683. max_layer = max(existing_layers) if existing_layers else 1
  684. # 构建包含所有层级的最终结果
  685. final_result = {}
  686. for layer in range(1, max_layer + 1):
  687. final_result[str(layer)] = dict(layer_counts.get(layer, {}))
  688. return json.dumps(final_result, indent=2)
  689. def get_current_pressure(self):
  690. """获取实时工作压力"""
  691. last_solution = base_location.objects.order_by('-id').first()
  692. if not last_solution:
  693. base_location.objects.create(
  694. layer1_pressure=0,
  695. layer2_pressure=0,
  696. layer3_pressure=0,
  697. ).save()
  698. return [
  699. last_solution.layer1_pressure if last_solution else 0,
  700. last_solution.layer2_pressure if last_solution else 0,
  701. last_solution.layer3_pressure if last_solution else 0,
  702. ]
  703. def get_current_finish_task(self,container):
  704. batch = self.get_batch(container)
  705. if not batch:
  706. return None
  707. solution = alloction_pre.objects.filter(batch_number=batch).first()
  708. if not solution:
  709. return None
  710. return [solution.layer1_task_finish_number,solution.layer2_task_finish_number,solution.layer3_task_finish_number]
  711. def update_current_finish_task(self,container,task_finish_number):
  712. batch = self.get_batch(container)
  713. if not batch:
  714. return None
  715. solution = alloction_pre.objects.filter(batch_number=batch).first()
  716. if not solution:
  717. return None
  718. solution.layer1_task_finish_number = task_finish_number[0]
  719. solution.layer2_task_finish_number = task_finish_number[1]
  720. solution.layer3_task_finish_number = task_finish_number[2]
  721. solution.save()
  722. return True
  723. @transaction.atomic
  724. def get_location_by_type(self, location_type_list, start_location, container_code):
  725. """
  726. 根据库位类型获取库位,先根据工作压力找出最空闲的层,看下这层有没有工作,如果有,就从里面找,如果没有,则跳过该层,继续找下一层
  727. :param location_type_list: 库位分配方案
  728. {
  729. "1": {
  730. "S4": 1
  731. },
  732. "2": {},
  733. "3": {
  734. "T5": 1
  735. }
  736. }
  737. :param start_location: 起始位置(决定优先级排序方式)
  738. :return: 符合条件的库位列表
  739. """
  740. locations = []
  741. # 检查已有分配方案
  742. existing_solution = alloction_pre.objects.filter(batch_number=self.get_batch(container_code)).first()
  743. if existing_solution.layer_solution_type:
  744. print(f"[0]已有库位分配方案:{existing_solution.layer_solution_type}")
  745. return existing_solution.layer_solution_type
  746. for layer, location_type_dict in location_type_list.items():
  747. if not location_type_dict:
  748. continue
  749. # 获取库位类型列表
  750. location_type = list(location_type_dict.keys())
  751. demand_number = sum(location_type_dict.values())
  752. print (f"[1]层{layer} 需求数量: {demand_number}, 库位: {location_type}")
  753. location_groups = LocationGroupModel.objects.filter(
  754. group_type__in=location_type,
  755. layer=layer,
  756. status='available'
  757. )
  758. if not location_groups:
  759. print(f"层{layer} 无库位")
  760. # 根据起始位置选择排序字段
  761. if start_location == 'in1':
  762. ordered_groups = location_groups.order_by('left_priority')
  763. elif start_location == 'in2':
  764. ordered_groups = location_groups.order_by('right_priority')
  765. else:
  766. ordered_groups = location_groups.none()
  767. number = 0
  768. for location_group in ordered_groups:
  769. if number >= demand_number:
  770. break
  771. locations.append(f"{layer}_{location_group.id}")
  772. number += 1
  773. existing_solution.layer_solution_type = locations
  774. existing_solution.save()
  775. print(f"[2]分配方案: {locations}")
  776. return locations if locations else None
  777. @transaction.atomic
  778. def get_location_by_status(self,container_code,start_location):
  779. """
  780. 根据库位状态获取库位
  781. :param location_type: 库位类型
  782. :param start_location: 起始库位 if in1 优先考虑left_priority, if in2 优先考虑right_priority 就是获取库位组列表之后进行排序
  783. :return: 库位列表
  784. """
  785. # 1. 获取批次状态 1 为已组盘 2 为部分入库 3 为全部入库
  786. status = self.get_batch_status(container_code)
  787. #
  788. if status == 1:
  789. # 2. 获取库位组
  790. print(f"[1]第一次入库")
  791. # 重新获取最新数据
  792. self.get_location_type(container_code)
  793. location_type_list = json.loads(alloction_pre.objects.filter(batch_number=self.get_batch(container_code)).first().layer_pre_type)
  794. location_list = self.get_location_by_type(location_type_list,start_location,container_code)
  795. # 预定这些库组
  796. self.update_group_status_reserved(location_list)
  797. location_min_value = self.get_location_list_remainder(location_list,container_code)
  798. print(f"库位安排到第{location_min_value.c_number}个库位:{location_min_value}")
  799. # if not location_list[location_min_index]:
  800. # # 库位已满,返回None
  801. # return None
  802. # else:
  803. # return location_list[location_min_index]
  804. return location_min_value
  805. elif status == 2:
  806. # 3. 获取部分入库库位
  807. print (f"部分入库")
  808. location_list = alloction_pre.objects.filter(batch_number=self.get_batch(container_code)).first().layer_solution_type
  809. location_min_value = self.get_location_list_remainder(location_list,container_code)
  810. print(f"库位安排到第{location_min_value.c_number}个库位:{location_min_value}")
  811. return location_min_value