views.py 45 KB

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