views.py 40 KB

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