views.py 72 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642
  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. from operation_log.views import log_operation,log_failure_operation,log_success_operation
  27. from operation_log.models import OperationLog
  28. # 库位分配
  29. # 入库规则函数
  30. # 逻辑根据批次下的托盘数目来找满足区间范围的库位,按照优先级排序,
  31. class locationViewSet(viewsets.ModelViewSet):
  32. """
  33. retrieve:
  34. Response a data list(get)
  35. list:
  36. Response a data list(all)
  37. create:
  38. Create a data line(post)
  39. delete:
  40. Delete a data line(delete)
  41. """
  42. # authentication_classes = [] # 禁用所有认证类
  43. # permission_classes = [AllowAny] # 允许任意访问
  44. pagination_class = MyPageNumberPagination
  45. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  46. ordering_fields = ['id', "create_time", "update_time", ]
  47. filter_class = LocationFilter
  48. def get_project(self):
  49. try:
  50. id = self.kwargs.get('pk')
  51. return id
  52. except:
  53. return None
  54. def get_queryset(self):
  55. id = self.get_project()
  56. prefetch_containers = Prefetch(
  57. 'container_links',
  58. queryset=LocationContainerLink.objects.filter(
  59. is_active=True
  60. ).select_related('container'), # 加载关联的托盘对象
  61. to_attr='active_links' # 新的属性名称
  62. )
  63. if self.request.auth:
  64. if id is None:
  65. log_operation(
  66. request=self.request,
  67. operation_content="查看库位列表",
  68. operation_level="view",
  69. operator=self.request.auth.name if self.request.auth else None,
  70. module_name="库位"
  71. )
  72. return LocationModel.objects.prefetch_related(prefetch_containers).all()
  73. else:
  74. log_operation(
  75. request=self.request,
  76. operation_content=f"查看库位详情 ID:{id}",
  77. operation_level="view",
  78. operator=self.request.auth.name if self.request.auth else None,
  79. object_id=id,
  80. module_name="库位"
  81. )
  82. return LocationModel.objects.prefetch_related(prefetch_containers).filter(id=id)
  83. else:
  84. return LocationModel.objects.none()
  85. def get_serializer_class(self):
  86. if self.action == 'list':
  87. return LocationListSerializer
  88. elif self.action == 'create':
  89. return LocationPostSerializer
  90. elif self.action == 'update':
  91. return LocationPostSerializer
  92. elif self.action =='retrieve':
  93. return LocationListSerializer
  94. def create(self, request, *args, **kwargs):
  95. """创建库位"""
  96. serializer = self.get_serializer(data=request.data)
  97. try:
  98. serializer.is_valid(raise_exception=True)
  99. instance = serializer.save()
  100. log_success_operation(
  101. request=self.request,
  102. operation_content=f"创建库位成功,库位编码: {instance.location_code}",
  103. operation_level="new",
  104. operator=self.request.auth.name if self.request.auth else None,
  105. object_id=instance.id,
  106. module_name="库位"
  107. )
  108. headers = self.get_success_headers(serializer.data)
  109. return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
  110. except Exception as e:
  111. log_failure_operation(
  112. request=self.request,
  113. operation_content=f"创建库位失败: {str(e)}",
  114. operation_level="new",
  115. operator=self.request.auth.name if self.request.auth else None,
  116. module_name="库位"
  117. )
  118. raise
  119. def destroy(self, request, *args, **kwargs):
  120. """删除库位"""
  121. instance = self.get_object()
  122. location_code = instance.location_code
  123. object_id = instance.id
  124. try:
  125. self.perform_destroy(instance)
  126. log_success_operation(
  127. request=self.request,
  128. operation_content=f"删除库位成功,库位编码: {location_code}",
  129. operation_level="delete",
  130. operator=self.request.auth.name if self.request.auth else None,
  131. object_id=object_id,
  132. module_name="库位"
  133. )
  134. return Response(status=status.HTTP_204_NO_CONTENT)
  135. except Exception as e:
  136. log_failure_operation(
  137. request=self.request,
  138. operation_content=f"删除库位失败,库位编码: {location_code}, 错误: {str(e)}",
  139. operation_level="delete",
  140. operator=self.request.auth.name if self.request.auth else None,
  141. object_id=object_id,
  142. module_name="库位"
  143. )
  144. raise
  145. def update(self, request, *args, **kwargs):
  146. qs = self.get_object()
  147. data = self.request.data
  148. location_code = data.get('location_code')
  149. # 处理库位对象
  150. location_obj = LocationModel.objects.filter(location_code=location_code).first()
  151. if not location_obj:
  152. log_failure_operation(
  153. request=self.request,
  154. operation_content=f"更新库位失败,库位 {location_code} 不存在",
  155. operation_level="update",
  156. operator=self.request.auth.name if self.request.auth else None,
  157. module_name="库位"
  158. )
  159. logger.info(f"库位 {location_code} 不存在")
  160. return Response(
  161. {'code': '400', 'message': '库位不存在', 'data': None},
  162. status=status.HTTP_400_BAD_REQUEST
  163. )
  164. else:
  165. data['id'] = location_obj.id
  166. logger.info(f"库位 {location_code} 已存在")
  167. serializer = self.get_serializer(qs, data=data)
  168. serializer.is_valid(raise_exception=True)
  169. serializer.save()
  170. log_success_operation(
  171. request=self.request,
  172. operation_content=f"更新库位成功,库位 {location_code} 已更新",
  173. operation_level="update",
  174. operator=self.request.auth.name if self.request.auth else None,
  175. object_id=location_obj.id,
  176. module_name="库位"
  177. )
  178. headers = self.get_success_headers(serializer.data)
  179. self.handle_group_location_status(location_code,location_obj.location_group)
  180. return Response(serializer.data, status=200, headers=headers)
  181. def handle_group_location_status(self,location_code,location_group):
  182. """
  183. 处理库位组和库位的关联关系
  184. :param location_code: 库位编码
  185. :param location_group: 库位组编码
  186. :return:
  187. """
  188. # 1. 获取库位空闲状态的库位数目
  189. location_obj_number = LocationModel.objects.filter(
  190. location_group=location_group,
  191. status='available'
  192. ).all().count()
  193. # 2. 获取库位组对象
  194. logger.info(f"库位组 {location_group} 下的库位数目:{location_obj_number}")
  195. # 1. 获取库位和库位组的关联关系
  196. location_group_obj = LocationGroupModel.objects.filter(
  197. group_code=location_group
  198. ).first()
  199. if not location_group_obj:
  200. logger.info(f"库位组 {location_group} 不存在")
  201. return None
  202. else:
  203. if location_obj_number == 0:
  204. # 库位组库位已满,更新库位组状态为full
  205. location_group_obj.status = 'full'
  206. location_group_obj.save()
  207. elif location_obj_number < location_group_obj.max_capacity:
  208. location_group_obj.status = 'occupied'
  209. location_group_obj.save()
  210. else:
  211. location_group_obj.status = 'available'
  212. location_group_obj.save()
  213. def batch_status_location(self, request):
  214. """
  215. 优化版:批量获取库位批次状态
  216. 基于模型结构优化查询
  217. """
  218. layer = request.data.get('layer')
  219. # 使用反向关系名 'container_links' 进行预取
  220. locations = LocationModel.objects.filter(
  221. layer=layer
  222. ).prefetch_related(
  223. Prefetch(
  224. 'container_links', # 使用模型定义的 related_name
  225. queryset=LocationContainerLink.objects.filter(is_active=True)
  226. .select_related('container'),
  227. to_attr='active_links'
  228. )
  229. )
  230. # 收集所有激活链接的托盘ID
  231. container_ids = set()
  232. for loc in locations:
  233. if loc.active_links: # 每个库位最多只有一个激活链接
  234. container_ids.add(loc.active_links[0].container_id)
  235. # 批量查询托盘详情及其批次状态
  236. container_batch_status = defaultdict(dict) # 改为字典存储,避免重复记录
  237. if container_ids:
  238. container_details = ContainerDetailModel.objects.filter(
  239. container_id__in=container_ids ,
  240. is_delete=False
  241. ).select_related('batch').exclude(status=3) # 排除已删除或不合格的托盘
  242. for detail in container_details:
  243. if detail.batch_id:
  244. # 创建唯一标识的键
  245. status_key = (
  246. detail.batch.check_status if detail.batch else "404",
  247. detail.batch.bound_number if detail.batch else "no_batch"
  248. )
  249. # 如果这个状态尚未添加过,或者需要更新
  250. if status_key not in container_batch_status[detail.container_id]:
  251. container_batch_status[detail.container_id][status_key] = (
  252. detail.batch.check_status if detail.batch else "404",
  253. detail.batch.check_time if detail.batch else "no_check_time",
  254. detail.batch.bound_number if detail.batch else "no_batch",
  255. detail.goods_qty-detail.goods_out_qty if detail.goods_qty-detail.goods_out_qty > 0 else 0,
  256. )
  257. else:
  258. # 如果批次状态相同,则更新库位数量
  259. if container_batch_status[detail.container_id][status_key][0] == detail.batch.check_status:
  260. container_batch_status[detail.container_id][status_key] = (
  261. detail.batch.check_status,
  262. max(container_batch_status[detail.container_id][status_key][1], detail.batch.check_time),
  263. detail.batch.bound_number,
  264. 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)
  265. )
  266. # 构造返回数据
  267. return_data = []
  268. for loc in locations:
  269. batch_statuses = []
  270. if loc.active_links:
  271. container_id = loc.active_links[0].container_id
  272. # 从字典中提取值并转换为列表
  273. if container_id in container_batch_status:
  274. batch_statuses = list(container_batch_status[container_id].values())
  275. else:
  276. batch_statuses = [("404", "no_check_time", "no_batch")]
  277. # 使用Django模型自带的model_to_dict转换基础字段
  278. from django.forms.models import model_to_dict
  279. location_data = model_to_dict(loc, fields=[
  280. "id", "shelf_type", "row", "col", "layer", "update_time",
  281. "empty_label", "location_code", "location_group", "location_type",
  282. "status", "max_capacity", "current_quantity", "c_number",
  283. "coordinate", "access_priority", "is_active"
  284. ])
  285. # 添加批次状态字段 - 存储所有信息
  286. location_data["batch_statuses"] = batch_statuses
  287. return_data.append(location_data)
  288. data = {
  289. "code": "200",
  290. "msg": "Success Create",
  291. "data": return_data
  292. }
  293. log_operation(
  294. request=self.request,
  295. operation_content=f"批量获取库位批次状态,层号:{layer}",
  296. operation_level="view",
  297. operator=self.request.auth.name if self.request.auth else None,
  298. module_name="库位"
  299. )
  300. return Response(data, status=200)
  301. class locationGroupViewSet(viewsets.ModelViewSet):
  302. """
  303. retrieve:
  304. Response a data list(get)
  305. list:
  306. Response a data list(all)
  307. create:
  308. Create a data line(post)
  309. delete:
  310. Delete a data line(delete)
  311. """
  312. # authentication_classes = [] # 禁用所有认证类
  313. # permission_classes = [AllowAny] # 允许任意访问
  314. pagination_class = MyPageNumberPagination
  315. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  316. ordering_fields = ['id', "create_time", "update_time", ]
  317. filter_class = LocationGroupFilter
  318. def get_project(self):
  319. try:
  320. id = self.kwargs.get('pk')
  321. return id
  322. except:
  323. return None
  324. def get_queryset(self):
  325. id = self.get_project()
  326. if self.request.auth:
  327. if id is None:
  328. log_operation(
  329. request=self.request,
  330. operation_content="查看库位组列表",
  331. operation_level="view",
  332. operator=self.request.auth.name if self.request.auth else None,
  333. module_name="库位"
  334. )
  335. return LocationGroupModel.objects.filter()
  336. else:
  337. log_operation(
  338. request=self.request,
  339. operation_content=f"查看库位组详情 ID:{id}",
  340. operation_level="view",
  341. operator=self.request.auth.name if self.request.auth else None,
  342. object_id=id,
  343. module_name="库位"
  344. )
  345. return LocationGroupModel.objects.filter(id=id)
  346. else:
  347. return LocationGroupModel.objects.none()
  348. def get_serializer_class(self):
  349. if self.action == 'create':
  350. return LocationGroupPostSerializer
  351. if self.action == 'list':
  352. return LocationGroupListSerializer
  353. elif self.action == 'update':
  354. return LocationGroupPostSerializer
  355. elif self.action =='retrieve':
  356. return LocationGroupListSerializer
  357. def create(self, request, *args, **kwargs):
  358. """创建库位组"""
  359. data = self.request.data.copy()
  360. order_month = str(timezone.now().strftime('%Y%m'))
  361. data['month'] = order_month
  362. serializer = LocationGroupPostSerializer(data=data)
  363. try:
  364. serializer.is_valid(raise_exception=True)
  365. instance = serializer.save()
  366. log_success_operation(
  367. request=self.request,
  368. operation_content=f"创建库位组成功,库位组编码: {instance.group_code}",
  369. operation_level="new",
  370. operator=self.request.auth.name if self.request.auth else None,
  371. object_id=instance.id,
  372. module_name="库位"
  373. )
  374. headers = self.get_success_headers(serializer.data)
  375. return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
  376. except Exception as e:
  377. log_failure_operation(
  378. request=self.request,
  379. operation_content=f"创建库位组失败: {str(e)}",
  380. operation_level="new",
  381. operator=self.request.auth.name if self.request.auth else None,
  382. module_name="库位"
  383. )
  384. raise
  385. def destroy(self, request, *args, **kwargs):
  386. """删除库位组"""
  387. instance = self.get_object()
  388. group_code = instance.group_code
  389. object_id = instance.id
  390. try:
  391. self.perform_destroy(instance)
  392. log_success_operation(
  393. request=self.request,
  394. operation_content=f"删除库位组成功,库位组编码: {group_code}",
  395. operation_level="delete",
  396. operator=self.request.auth.name if self.request.auth else None,
  397. object_id=object_id,
  398. module_name="库位"
  399. )
  400. return Response(status=status.HTTP_204_NO_CONTENT)
  401. except Exception as e:
  402. log_failure_operation(
  403. request=self.request,
  404. operation_content=f"删除库位组失败,库位组编码: {group_code}, 错误: {str(e)}",
  405. operation_level="delete",
  406. operator=self.request.auth.name if self.request.auth else None,
  407. object_id=object_id,
  408. module_name="库位"
  409. )
  410. raise
  411. def update(self, request, *args, **kwargs):
  412. data = self.request.data
  413. order_month = str(timezone.now().strftime('%Y%m'))
  414. data['month'] = order_month
  415. group_code = data.get('group_code')
  416. # 处理库位组对象
  417. group_obj = LocationGroupModel.objects.filter(group_code=group_code).first()
  418. if group_obj:
  419. data['id'] = group_obj.id
  420. logger.info(f"库位组 {group_code} 已存在")
  421. # 更新现有库位组
  422. serializer = LocationGroupPostSerializer(group_obj, data=data)
  423. try:
  424. serializer.is_valid(raise_exception=True)
  425. serializer.save()
  426. log_success_operation(
  427. request=self.request,
  428. operation_content=f"更新库位组成功,库位组编码: {group_code}",
  429. operation_level="update",
  430. operator=self.request.auth.name if self.request.auth else None,
  431. object_id=group_obj.id,
  432. module_name="库位"
  433. )
  434. return Response(serializer.data, status=status.HTTP_200_OK)
  435. except Exception as e:
  436. log_failure_operation(
  437. request=self.request,
  438. operation_content=f"更新库位组失败,库位组编码: {group_code}, 错误: {str(e)}",
  439. operation_level="update",
  440. operator=self.request.auth.name if self.request.auth else None,
  441. object_id=group_obj.id,
  442. module_name="库位"
  443. )
  444. raise
  445. else:
  446. logger.info(f"库位组 {group_code} 不存在,创建库位组对象")
  447. serializer_list = LocationGroupPostSerializer(data=data)
  448. serializer_list.is_valid(raise_exception=True)
  449. serializer_list.save()
  450. log_success_operation(
  451. request=self.request,
  452. operation_content=f"创建库位组成功,库位组编码: {group_code}",
  453. operation_level="new",
  454. operator=self.request.auth.name if self.request.auth else None,
  455. object_id=serializer_list.data.get('id'),
  456. module_name="库位"
  457. )
  458. data['id'] = serializer_list.data.get('id')
  459. return Response(data, status=status.HTTP_201_CREATED)
  460. class LocationAllocation:
  461. # 入库规则函数
  462. # fun:get_pallet_count_by_batch: 根据托盘码查询批次下托盘总数
  463. # fun:get_left_locationGroup_number_by_type: 获取每层库位组剩余数量
  464. # fun:get_location_type: 根据托盘数目获取库位类型
  465. # fun:update_location_container_link: 更新库位和托盘的关联关系
  466. # fun:update_location_group_batch: 更新库位组的批次
  467. # fun:update_batch_status: 更新批次状态yes/no
  468. # fun:update_location_status: 更新库位状态和
  469. # fun:up
  470. # fun:get_batch_status: 获取批次状态
  471. # fun:get_batch: 获取批次
  472. # fun:get_location_list_remainder: 获取可用库位的c_number列表
  473. # fun
  474. # fun:get_location_by_type_remainder: 根据库位类型获取库位
  475. # fun:get_location_by_type: 第一次入库,根据库位类型获取库位
  476. # fun:get_location_by_status: 根据库位状态获取库位
  477. def get_pallet_count_by_batch(self, container_code):
  478. """
  479. 根据托盘码查询批次下托盘总数
  480. :param container_code: 要查询的托盘码
  481. :return: 所属批次下的托盘总数
  482. """
  483. # 1. 通过托盘码获取托盘详情
  484. container = ContainerListModel.objects.filter(
  485. container_code=container_code
  486. ).first()
  487. if not container:
  488. logger.error(f"托盘 {container_code} 不存在")
  489. return None
  490. # 2. 获取关联的批次明细
  491. container_detail = ContainerDetailModel.objects.filter(
  492. container=container.id,is_delete=False
  493. ).exclude(status = 3).first()
  494. if not container_detail:
  495. logger.error(f"托盘 {container_code} 未组盘")
  496. return None
  497. batch_container = ContainerDetailModel.objects.filter(
  498. batch = container_detail.batch.id,
  499. is_delete = False,
  500. status = 1
  501. ).all()
  502. # 统计批次下的不同托盘 item.contianer_id
  503. batch_container_count = 0
  504. container_ids = []
  505. for item in batch_container:
  506. if item.container_id not in container_ids:
  507. batch_container_count = batch_container_count + 1
  508. container_ids.append(item.container_id)
  509. batch_item = BoundBatchModel.objects.filter( bound_number = container_detail.batch.bound_number).first()
  510. if not batch_item:
  511. print(f"批次号获取失败!")
  512. return None
  513. batch_item.container_number = batch_container_count
  514. batch_item.save()
  515. return batch_container_count
  516. def get_left_locationGroup_number_by_type(self):
  517. """
  518. 获取每层库位组剩余数量
  519. :return:
  520. """
  521. try:
  522. # 定义库位组和层号
  523. group = ['T1', 'T2', 'S4', 'T4', 'T5']
  524. layer = [1, 2, 3]
  525. # 初始化结果列表,包含三个空字典对应三个层
  526. left_number = [{} for _ in layer]
  527. for item in group:
  528. for idx, layer_num in enumerate(layer):
  529. # 检查库位组是否存在(不考虑状态)
  530. exists = LocationGroupModel.objects.filter(
  531. group_type=item,
  532. layer=layer_num
  533. ).exists()
  534. if not exists:
  535. print(f"库位组 {item}_{layer_num} 不存在")
  536. left_number[idx][item] = 0
  537. else:
  538. # 统计可用状态的库位组数量
  539. count = LocationGroupModel.objects.filter(
  540. group_type=item,
  541. layer=layer_num,
  542. status='available'
  543. ).count()
  544. left_number[idx][item] = count
  545. return left_number
  546. except Exception as e:
  547. logger.error(f"获取库位组剩余数量失败:{str(e)}")
  548. print(f"获取库位组剩余数量失败:{str(e)}")
  549. return None
  550. def update_location_container_link(self,location_code,container_code, request=None):
  551. """
  552. 更新库位和托盘的关联关系
  553. :param location_code: 库位编码
  554. :param container_code: 托盘编码
  555. :param request: 请求对象(可选)
  556. :return:
  557. """
  558. try:
  559. # 1. 获取库位和托盘的关联关系
  560. location = LocationModel.objects.filter(
  561. location_code=location_code
  562. ).first()
  563. container = ContainerListModel.objects.filter(
  564. container_code=container_code
  565. ).first()
  566. # 2. 如果库位和托盘的关联关系不存在,创建新的关联关系
  567. if not LocationContainerLink.objects.filter(location=location).exists():
  568. location_container_link = LocationContainerLink(
  569. location=location,
  570. container=container
  571. )
  572. location_container_link.save()
  573. print(f"更新库位和托盘的关联关系成功!")
  574. # 记录操作日志
  575. try:
  576. if request:
  577. log_success_operation(
  578. request=request,
  579. operation_content=f"创建库位-托盘关联,库位编码: {location_code}, 托盘编码: {container_code}",
  580. operation_level="other",
  581. module_name="库位分配"
  582. )
  583. else:
  584. OperationLog.objects.create(
  585. operator="系统自动",
  586. operation_content=f"创建库位-托盘关联,库位编码: {location_code}, 托盘编码: {container_code}",
  587. operation_level="other",
  588. operation_result="success",
  589. module_name="库位分配"
  590. )
  591. except Exception as log_error:
  592. logger.error(f"记录库位-托盘关联日志失败: {str(log_error)}")
  593. return True
  594. # 3. 更新库位和托盘的关联关系
  595. else:
  596. LocationContainerLink.objects.filter(location=location).update(location=location, container=container)
  597. print(f"更新库位和托盘的关联关系成功!")
  598. # 记录操作日志
  599. try:
  600. if request:
  601. log_success_operation(
  602. request=request,
  603. operation_content=f"更新库位-托盘关联,库位编码: {location_code}, 托盘编码: {container_code}",
  604. operation_level="update",
  605. module_name="库位分配"
  606. )
  607. else:
  608. OperationLog.objects.create(
  609. operator="系统自动",
  610. operation_content=f"更新库位-托盘关联,库位编码: {location_code}, 托盘编码: {container_code}",
  611. operation_level="update",
  612. operation_result="success",
  613. module_name="库位分配"
  614. )
  615. except Exception as log_error:
  616. logger.error(f"记录库位-托盘关联日志失败: {str(log_error)}")
  617. return True
  618. except Exception as e:
  619. logger.error(f"更新库位和托盘的关联关系失败:{str(e)}")
  620. print(f"更新库位和托盘的关联关系失败:{str(e)}")
  621. # 记录失败日志
  622. try:
  623. if request:
  624. log_failure_operation(
  625. request=request,
  626. operation_content=f"更新库位-托盘关联失败,库位编码: {location_code}, 托盘编码: {container_code}, 错误: {str(e)}",
  627. operation_level="update",
  628. module_name="库位分配"
  629. )
  630. else:
  631. OperationLog.objects.create(
  632. operator="系统自动",
  633. operation_content=f"更新库位-托盘关联失败,库位编码: {location_code}, 托盘编码: {container_code}, 错误: {str(e)}",
  634. operation_level="update",
  635. operation_result="failure",
  636. module_name="库位分配"
  637. )
  638. except Exception as log_error:
  639. logger.error(f"记录失败日志失败: {str(log_error)}")
  640. return False
  641. def update_container_detail_status(self,container_code,status):
  642. try:
  643. # 1. 获取托盘
  644. container = ContainerListModel.objects.filter(
  645. container_code=container_code
  646. ).first()
  647. if not container:
  648. print(f"托盘 {container_code} 不存在")
  649. return False
  650. # 2. 更新托盘状态
  651. container_detail = ContainerDetailModel.objects.filter(
  652. container=container.id,is_delete=False
  653. ).exclude(status=3).first()
  654. if not container_detail:
  655. print(f"托盘 {container_code} 未组盘_from update_container_detail_status")
  656. return False
  657. container_detail.status = status
  658. container_detail.save()
  659. print(f"更新托盘状态成功!")
  660. return True
  661. except Exception as e:
  662. logger.error(f"更新托盘状态失败:{str(e)}")
  663. print(f"更新托盘状态失败:{str(e)}")
  664. return False
  665. def update_location_group_batch(self,location,container_code):
  666. """
  667. :param location: 库位对象
  668. :param container_code: 托盘码
  669. :return:
  670. """
  671. try:
  672. # 1. 获取库位组
  673. location_group = LocationGroupModel.objects.filter(
  674. group_code=location.location_group
  675. ).first()
  676. if not location_group:
  677. print(f"库位组获取失败!")
  678. return False
  679. # 2. 更新库位组的批次
  680. bound_number=self.get_batch(container_code)
  681. if not bound_number:
  682. print(f"批次号获取失败!")
  683. return False
  684. location_group.current_batch = bound_number
  685. location_group.save()
  686. print(f"更新库位组的批次成功!")
  687. return True
  688. except Exception as e:
  689. logger.error(f"更新库位组的批次失败:{str(e)}")
  690. print(f"更新库位组的批次失败:{str(e)}")
  691. return False
  692. def update_location_status(self,location_code,status, request=None):
  693. """
  694. 更新库位状态
  695. :param location_code: 库位编码
  696. :param status: 库位状态
  697. :param request: 请求对象(可选)
  698. :return:
  699. """
  700. try:
  701. # 1. 获取库位
  702. location = LocationModel.objects.filter(
  703. location_code=location_code
  704. ).first()
  705. if not location:
  706. print(f"库位获取失败!")
  707. return False
  708. # 2. 更新库位状态
  709. old_status = location.status
  710. location.status = status
  711. location.save()
  712. print(f"更新库位状态成功!")
  713. # 记录操作日志(状态变更敏感操作)
  714. try:
  715. if request:
  716. log_success_operation(
  717. request=request,
  718. operation_content=f"更新库位状态,库位编码: {location_code}, 状态: {old_status} -> {status}",
  719. operation_level="update",
  720. object_id=str(location.id),
  721. module_name="库位分配"
  722. )
  723. else:
  724. OperationLog.objects.create(
  725. operator="系统自动",
  726. operation_content=f"更新库位状态,库位编码: {location_code}, 状态: {old_status} -> {status}",
  727. operation_level="update",
  728. operation_result="success",
  729. object_id=str(location.id),
  730. module_name="库位分配"
  731. )
  732. except Exception as log_error:
  733. logger.error(f"记录库位状态更新日志失败: {str(log_error)}")
  734. return True
  735. except Exception as e:
  736. logger.error(f"更新库位状态失败:{str(e)}")
  737. print(f"更新库位状态失败:{str(e)}")
  738. # 记录失败日志
  739. try:
  740. if request:
  741. log_failure_operation(
  742. request=request,
  743. operation_content=f"更新库位状态失败,库位编码: {location_code}, 错误: {str(e)}",
  744. operation_level="update",
  745. module_name="库位分配"
  746. )
  747. else:
  748. OperationLog.objects.create(
  749. operator="系统自动",
  750. operation_content=f"更新库位状态失败,库位编码: {location_code}, 错误: {str(e)}",
  751. operation_level="update",
  752. operation_result="failure",
  753. module_name="库位分配"
  754. )
  755. except Exception as log_error:
  756. logger.error(f"记录失败日志失败: {str(log_error)}")
  757. return False
  758. def update_group_status_reserved(self,location_group_list, request=None):
  759. """
  760. 更新库位组状态为预留
  761. :param location_group_list: 库位组对象列表
  762. :param request: 请求对象(可选)
  763. :return:
  764. """
  765. try:
  766. reserved_groups = []
  767. for location_group in location_group_list:
  768. # 1. 获取库位组
  769. if not location_group:
  770. print(f"库位组获取失败!")
  771. return False
  772. # 2. 更新库位组状态
  773. location_group_id = location_group.split('_')[1]
  774. location_group_item = LocationGroupModel.objects.filter(
  775. id=location_group_id
  776. ).first()
  777. if not location_group_item:
  778. print(f"库位组 {location_group} 不存在")
  779. return False
  780. # 3. 更新库位组状态
  781. old_status = location_group_item.status
  782. location_group_item.status = 'reserved'
  783. location_group_item.save()
  784. reserved_groups.append(f"{location_group_item.group_code}({old_status}->reserved)")
  785. # 记录预留库位组操作日志(敏感操作)
  786. try:
  787. if request:
  788. log_success_operation(
  789. request=request,
  790. operation_content=f"预留库位组,共{len(reserved_groups)}个: {', '.join(reserved_groups)}",
  791. operation_level="update",
  792. module_name="库位分配"
  793. )
  794. else:
  795. OperationLog.objects.create(
  796. operator="系统自动",
  797. operation_content=f"预留库位组,共{len(reserved_groups)}个: {', '.join(reserved_groups)}",
  798. operation_level="update",
  799. operation_result="success",
  800. module_name="库位分配"
  801. )
  802. except Exception as log_error:
  803. logger.error(f"记录预留库位组日志失败: {str(log_error)}")
  804. return True
  805. except Exception as e:
  806. logger.error(f"更新库位组状态失败:{str(e)}")
  807. print(f"更新库位组状态失败:{str(e)}")
  808. # 记录失败日志
  809. try:
  810. if request:
  811. log_failure_operation(
  812. request=request,
  813. operation_content=f"预留库位组失败,错误: {str(e)}",
  814. operation_level="update",
  815. module_name="库位分配"
  816. )
  817. else:
  818. OperationLog.objects.create(
  819. operator="系统自动",
  820. operation_content=f"预留库位组失败,错误: {str(e)}",
  821. operation_level="update",
  822. operation_result="failure",
  823. module_name="库位分配"
  824. )
  825. except Exception as log_error:
  826. logger.error(f"记录失败日志失败: {str(log_error)}")
  827. return False
  828. def update_location_group_status(self, location_code):
  829. """
  830. 更新库位组状态
  831. :param location_code: 库位编码
  832. :return:
  833. """
  834. try:
  835. # 1. 获取库位
  836. location = LocationModel.objects.filter(
  837. location_code=location_code
  838. ).first()
  839. if not location:
  840. print(f"库位获取失败!")
  841. return False
  842. # 2. 获取库位组
  843. location_group = LocationGroupModel.objects.filter(
  844. group_code=location.location_group
  845. ).first()
  846. if not location_group:
  847. print(f"库位组获取失败!")
  848. return False
  849. current=0
  850. for location_item in location_group.location_items.all():
  851. if location_item.status != 'available':
  852. current=current + 1
  853. # 3. 更新库位组状态
  854. if current == 0:
  855. location_group.status = 'available'
  856. elif current == location_group.max_capacity:
  857. location_group.status = 'full'
  858. else:
  859. location_group.status = 'occupied'
  860. location_group.current_goods_quantity = sum(
  861. [loc.current_quantity for loc in location_group.location_items.all()]
  862. )
  863. location_group.current_quantity = current
  864. location_group.save()
  865. print(f"更新库位组状态成功!")
  866. return True
  867. except Exception as e:
  868. logger.error(f"更新库位组状态失败:{str(e)}")
  869. print(f"更新库位组状态失败:{str(e)}")
  870. def update_batch_status(self,container_code,status, request=None):
  871. """
  872. 更新批次状态
  873. :param container_code: 托盘编码
  874. :param status: 批次状态
  875. :param request: 请求对象(可选)
  876. :return:
  877. """
  878. try:
  879. # 1. 通过托盘码获取托盘详情
  880. container = ContainerListModel.objects.filter(
  881. container_code=container_code
  882. ).first()
  883. if not container:
  884. logger.error(f"托盘 {container_code} 不存在")
  885. print(f"托盘 {container_code} 不存在")
  886. return None
  887. # 2. 获取关联的批次明细
  888. container_detail = ContainerDetailModel.objects.filter(
  889. container=container.id,is_delete=False
  890. ).exclude(status=3).first()
  891. if not container_detail:
  892. print (f"托盘 {container_code} 未组盘")
  893. logger.error(f"托盘 {container_code} 未组盘_from update_batch_status")
  894. return None
  895. # 3. 更新批次状态
  896. batch = container_detail.batch
  897. old_status = batch.status
  898. batch.status = status
  899. batch.save()
  900. print(f"更新批次状态成功!")
  901. # 记录操作日志(批次状态变更敏感操作)
  902. try:
  903. if request:
  904. log_success_operation(
  905. request=request,
  906. operation_content=f"更新批次状态,托盘编码: {container_code}, 批次号: {batch.bound_number}, 状态: {old_status} -> {status}",
  907. operation_level="update",
  908. object_id=str(batch.id),
  909. module_name="库位分配"
  910. )
  911. else:
  912. OperationLog.objects.create(
  913. operator="系统自动",
  914. operation_content=f"更新批次状态,托盘编码: {container_code}, 批次号: {batch.bound_number}, 状态: {old_status} -> {status}",
  915. operation_level="update",
  916. operation_result="success",
  917. object_id=str(batch.id),
  918. module_name="库位分配"
  919. )
  920. except Exception as log_error:
  921. logger.error(f"记录批次状态更新日志失败: {str(log_error)}")
  922. return True
  923. except Exception as e:
  924. logger.error(f"更新批次状态失败:{str(e)}")
  925. print(f"更新批次状态失败:{str(e)}")
  926. # 记录失败日志
  927. try:
  928. if request:
  929. log_failure_operation(
  930. request=request,
  931. operation_content=f"更新批次状态失败,托盘编码: {container_code}, 错误: {str(e)}",
  932. operation_level="update",
  933. module_name="库位分配"
  934. )
  935. else:
  936. OperationLog.objects.create(
  937. operator="系统自动",
  938. operation_content=f"更新批次状态失败,托盘编码: {container_code}, 错误: {str(e)}",
  939. operation_level="update",
  940. operation_result="failure",
  941. module_name="库位分配"
  942. )
  943. except Exception as log_error:
  944. logger.error(f"记录失败日志失败: {str(log_error)}")
  945. return False
  946. # def update_batch_goods_in_location_qty(self,container_code,taskworking):
  947. # """
  948. # 更新批次库位入库数量
  949. # :param container_code: 托盘码
  950. # :param goods_in_location_qty: 库位入库数量
  951. # :return:
  952. # """
  953. # try:
  954. # # 1. 通过托盘码获取托盘详情
  955. # container = ContainerListModel.objects.filter(
  956. # container_code=container_code
  957. # ).first()
  958. # if not container:
  959. # logger.error(f"托盘 {container_code} 不存在")
  960. # print(f"托盘 {container_code} 不存在")
  961. # return None
  962. # # 2. 获取关联的批次明细
  963. # container_detail = ContainerDetailModel.objects.filter(
  964. # container=container.id,is_delete=False
  965. # ).exclude(status=3).all()
  966. # if not container_detail:
  967. # print (f"托盘 {container_code} 未组盘")
  968. # logger.error(f"托盘 {container_code} 未组盘_from update_batch_goods_in_location_qty")
  969. # return None
  970. # for item in container_detail:
  971. # if item.goods_class == 2:
  972. # continue
  973. # item.batch.goods_in_location_qty += item.goods_qty * taskworking
  974. # item.batch.save()
  975. # print(f"更新批次库位入库数量成功!")
  976. # return True
  977. # except Exception as e:
  978. # logger.error(f"更新批次库位入库数量失败:{str(e)}")
  979. # print(f"更新批次库位入库数量失败:{str(e)}")
  980. # return False
  981. def get_batch_status(self,container_code):
  982. """
  983. 获取批次状态
  984. :param container_code: 托盘码
  985. :return: 批次状态
  986. """
  987. # 1. 通过托盘码获取托盘详情
  988. container = ContainerListModel.objects.filter(
  989. container_code=container_code
  990. ).first()
  991. if not container:
  992. logger.error(f"托盘 {container_code} 不存在")
  993. print(f"托盘 {container_code} 不存在")
  994. return None
  995. # 2. 获取关联的批次明细
  996. container_detail = ContainerDetailModel.objects.filter(
  997. container=container.id,is_delete=False
  998. ).exclude(status=3).first()
  999. if not container_detail:
  1000. print (f"托盘 {container_code} 未组盘")
  1001. logger.error(f"托盘 {container_code} 未组盘_from get_batch_status")
  1002. return None
  1003. batch_status = container_detail.batch.status
  1004. return batch_status
  1005. def get_batch(self,container_code):
  1006. """
  1007. 获取批次
  1008. :param container_code: 托盘码
  1009. :return: 批次
  1010. """
  1011. # 1. 通过托盘码获取托盘详情
  1012. container = ContainerListModel.objects.filter(
  1013. container_code=container_code
  1014. ).first()
  1015. if not container:
  1016. logger.error(f"托盘 {container_code} 不存在")
  1017. print(f"托盘 {container_code} 不存在")
  1018. return None
  1019. # 2. 获取关联的批次明细
  1020. container_detail = ContainerDetailModel.objects.filter(
  1021. container=container.id,is_delete=False
  1022. ).exclude(status=3).first()
  1023. if not container_detail:
  1024. print (f"托盘 {container_code} 未组盘")
  1025. logger.error(f"托盘 {container_code} 未组盘_from get_batch")
  1026. return None
  1027. batch = container_detail.batch.bound_number
  1028. return batch
  1029. def get_location_list_remainder(self, location_group_list,container_code):
  1030. """
  1031. 获取可用库位的c_number列表
  1032. :param location_list: 库位对象列表
  1033. :return: 可用库位编号列表
  1034. """
  1035. if not location_group_list:
  1036. return None
  1037. min_c_number=1000
  1038. min_c_number_index=1000
  1039. current_task = self.get_current_finish_task(container_code)
  1040. print(f"[1]当前已完成任务: {current_task}")
  1041. # 按压力排序
  1042. sorted_pressure = sorted(
  1043. [(0, current_task[0]), (1, current_task[1]), (2, current_task[2])],
  1044. key=lambda x: (-x[1], x[0])
  1045. )
  1046. # 交换第一和第二个元素的位置
  1047. sorted_pressure[0], sorted_pressure[1] = sorted_pressure[1], sorted_pressure[0]
  1048. print(f"[2]任务排序: {sorted_pressure}")
  1049. print(f"[3]当前选择:{sorted_pressure[0][0]+1}")
  1050. location_type_dict = json.loads(self.divide_solution_by_layer(location_group_list))
  1051. # print(f"库位类型分配方案: {location_type_dict}")
  1052. for layer, _ in sorted_pressure:
  1053. if not location_type_dict.get(str(layer+1)):
  1054. continue
  1055. # print(f"当前层: {layer+1}")
  1056. # print(f"当前层库位组: {location_type_dict[str(layer+1)].keys()}")
  1057. for group_id in location_type_dict[str(layer+1)].keys():
  1058. location_group = LocationGroupModel.objects.filter(
  1059. id=group_id,
  1060. ).first()
  1061. if not location_group:
  1062. continue
  1063. location_list = location_group.location_items.filter(
  1064. status='available'
  1065. ).all().order_by('c_number')
  1066. if not location_list:
  1067. print(f"当前层库位组 {location_group.group_code} 可用库位: None")
  1068. continue
  1069. # 提取所有库位的 c_number
  1070. c_numbers = [loc.c_number for loc in location_list]
  1071. print(f"当前层库位组 {location_group.group_code} 可用库位: {c_numbers}")
  1072. # 更新任务完成数目
  1073. current_task[layer] = current_task[layer] + 1
  1074. self.update_current_finish_task(container_code,current_task)
  1075. return location_list[0]
  1076. def get_location_type(self, container_code, request=None):
  1077. """
  1078. 智能库位分配核心算法
  1079. :param container_code: 托盘码
  1080. :param request: 请求对象(可选)
  1081. :return: 库位类型分配方案
  1082. """
  1083. try:
  1084. batch = self.get_batch(container_code)
  1085. if not batch:
  1086. logger.error("批次信息获取失败")
  1087. # 记录失败日志
  1088. try:
  1089. if request:
  1090. log_failure_operation(
  1091. request=request,
  1092. operation_content=f"库位分配算法执行失败,托盘编码: {container_code}, 错误: 批次信息获取失败",
  1093. operation_level="other",
  1094. module_name="库位分配算法"
  1095. )
  1096. else:
  1097. OperationLog.objects.create(
  1098. operator="系统自动",
  1099. operation_content=f"库位分配算法执行失败,托盘编码: {container_code}, 错误: 批次信息获取失败",
  1100. operation_level="other",
  1101. operation_result="failure",
  1102. module_name="库位分配算法"
  1103. )
  1104. except Exception as log_error:
  1105. logger.error(f"记录算法失败日志失败: {str(log_error)}")
  1106. return None
  1107. # 检查已有分配方案
  1108. existing_solution = alloction_pre.objects.filter(batch_number=batch).first()
  1109. if existing_solution:
  1110. # 记录使用已有分配方案
  1111. try:
  1112. if request:
  1113. log_operation(
  1114. request=request,
  1115. operation_content=f"使用已有库位分配方案,托盘编码: {container_code}, 批次号: {batch}",
  1116. operation_level="view",
  1117. module_name="库位分配算法"
  1118. )
  1119. except Exception as log_error:
  1120. logger.error(f"记录使用已有方案日志失败: {str(log_error)}")
  1121. return existing_solution.layer_pre_type
  1122. # 获取关键参数
  1123. total_pallets = self.get_pallet_count_by_batch(container_code)
  1124. layer_capacity = self.get_left_locationGroup_number_by_type()
  1125. current_pressure = self.get_current_pressure()
  1126. # 测试参数
  1127. # total_pallets = 30
  1128. # 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}]
  1129. # current_pressure = [1,0,0]
  1130. print(f"[1]托盘数目: {total_pallets}")
  1131. print(f"[2]层容量: {layer_capacity}")
  1132. # print(f"[3]当前压力: {current_pressure}")
  1133. # 定义库位容量表
  1134. LOCATION_CAPACITY = {'T1':1, 'T2':2, 'T4':4, 'S4':4, 'T5':5}
  1135. def allocate(remain, path, pressure,real_pressure,layer_capacity_state, depth=0):
  1136. # 终止条件
  1137. if remain <= 0:
  1138. return [path,real_pressure]
  1139. # 深拷贝当前层容量状态
  1140. new_layer_capacity = copy.deepcopy(layer_capacity_state)
  1141. # print(f"[2]当前剩余: {new_layer_capacity}")
  1142. # 压力平衡系数
  1143. balance_factor = 1.0 - (0.1 * min(depth, 5))
  1144. # 层选择策略
  1145. print (f"[3]当前压力: {pressure}")
  1146. layer_priority = sorted(
  1147. [(0, pressure[0]), (1, pressure[1]), (2, pressure[2])],
  1148. key=lambda x: (x[1] * balance_factor, x[0])
  1149. )
  1150. for layer, _ in layer_priority:
  1151. # 生成候选库位类型(按效率和容量排序)
  1152. # 排序键函数 :
  1153. # min(x[1], remain) 计算当前库位类型的容量 c 和剩余数量 remain 中的较小值。
  1154. # -min(x[1], remain) 和 -x[1] 都使用了负号,这意味着排序是按降序进行的。
  1155. # 首先按 -min(x[1], remain) 排序,即优先选择容量与剩余数量更接近的库位类型。
  1156. # 如果有多个库位类型的容量与剩余数量相同,则按 -x[1] 排序,即优先选择容量更大的库位类型。
  1157. print(f"[4]当前层: {layer+1}, 剩余: {remain}, 容量状态: {new_layer_capacity[layer]}")
  1158. candidates = sorted(
  1159. [(t, c) for t, c in LOCATION_CAPACITY.items()
  1160. if new_layer_capacity[layer].get(t,0) > 0],
  1161. key=lambda x: (abs(x[1]-remain), -x[1])
  1162. )
  1163. print(f"[4]候选库位类型: {candidates}")
  1164. for loc_type, cap in candidates:
  1165. # 更新容量状态
  1166. updated_capacity = copy.deepcopy(new_layer_capacity)
  1167. updated_capacity[layer][loc_type] -= 1 # 占用一个库位组
  1168. # 允许适度空间浪费(当剩余<2时)
  1169. # effective_cap = min(cap, remain) if (cap - remain) < 2 else cap
  1170. effective_cap = min(cap, remain)
  1171. if effective_cap <= remain:
  1172. new_remain = remain - effective_cap
  1173. new_pressure = pressure.copy()
  1174. for i in range(0, 3):
  1175. new_pressure[i] -=1 if new_pressure[i] > 0 else 0
  1176. new_pressure[layer] += effective_cap # 按实际存放数计算压力,此时别的楼层压力可能降下来了
  1177. real_pressure[layer] += effective_cap # 实际压力
  1178. result = allocate(
  1179. new_remain,
  1180. path + [f"{layer+1}_{loc_type}"],
  1181. new_pressure,
  1182. real_pressure,
  1183. updated_capacity,
  1184. depth + 1
  1185. )
  1186. if result:
  1187. print (f"[5]分配方案: {result}")
  1188. return result
  1189. return None
  1190. # 执行分配
  1191. allocation = allocate(total_pallets, [], [current_pressure[0], current_pressure[1],current_pressure[2]],[current_pressure[0], current_pressure[1],current_pressure[2]], layer_capacity)
  1192. if not allocation:
  1193. logger.error("无法生成有效分配方案")
  1194. # 记录失败日志
  1195. try:
  1196. if request:
  1197. log_failure_operation(
  1198. request=request,
  1199. operation_content=f"库位分配算法执行失败,托盘编码: {container_code}, 批次号: {batch}, 托盘数: {total_pallets}, 错误: 无法生成有效分配方案",
  1200. operation_level="other",
  1201. module_name="库位分配算法"
  1202. )
  1203. else:
  1204. OperationLog.objects.create(
  1205. operator="系统自动",
  1206. operation_content=f"库位分配算法执行失败,托盘编码: {container_code}, 批次号: {batch}, 托盘数: {total_pallets}, 错误: 无法生成有效分配方案",
  1207. operation_level="other",
  1208. operation_result="failure",
  1209. module_name="库位分配算法"
  1210. )
  1211. except Exception as log_error:
  1212. logger.error(f"记录算法失败日志失败: {str(log_error)}")
  1213. return None
  1214. # 保存分配方案
  1215. allocation_json = self.divide_solution_by_layer(allocation[0])
  1216. print(f"[6]分配方案: {allocation_json}")
  1217. solution = alloction_pre(
  1218. batch_number=batch,
  1219. layer_pre_type =allocation_json
  1220. )
  1221. solution_pressure, created = base_location.objects.get_or_create(
  1222. id=1,
  1223. defaults={
  1224. 'layer1_pressure': 0,
  1225. 'layer2_pressure': 0,
  1226. 'layer3_pressure': 0
  1227. }
  1228. )
  1229. solution_pressure.layer1_pressure = allocation[1][0]
  1230. solution_pressure.layer2_pressure = allocation[1][1]
  1231. solution_pressure.layer3_pressure = allocation[1][2]
  1232. solution.save()
  1233. solution_pressure.save()
  1234. # 记录算法执行成功日志(关键操作)
  1235. try:
  1236. allocation_summary = f"批次号: {batch}, 托盘数: {total_pallets}, 分配方案: {len(allocation[0])}个库位组, 压力分布: L1={allocation[1][0]}, L2={allocation[1][1]}, L3={allocation[1][2]}"
  1237. if request:
  1238. log_success_operation(
  1239. request=request,
  1240. operation_content=f"库位分配算法执行成功,托盘编码: {container_code}, {allocation_summary}",
  1241. operation_level="other",
  1242. module_name="库位分配算法"
  1243. )
  1244. else:
  1245. OperationLog.objects.create(
  1246. operator="系统自动",
  1247. operation_content=f"库位分配算法执行成功,托盘编码: {container_code}, {allocation_summary}",
  1248. operation_level="other",
  1249. operation_result="success",
  1250. module_name="库位分配算法"
  1251. )
  1252. except Exception as log_error:
  1253. logger.error(f"记录算法成功日志失败: {str(log_error)}")
  1254. return [loc.split('_')[1] for loc in allocation[0]]
  1255. except Exception as e:
  1256. logger.error(f"分配算法异常:{str(e)}")
  1257. # 记录异常日志
  1258. try:
  1259. if request:
  1260. log_failure_operation(
  1261. request=request,
  1262. operation_content=f"库位分配算法执行异常,托盘编码: {container_code}, 错误: {str(e)}",
  1263. operation_level="other",
  1264. module_name="库位分配算法"
  1265. )
  1266. else:
  1267. OperationLog.objects.create(
  1268. operator="系统自动",
  1269. operation_content=f"库位分配算法执行异常,托盘编码: {container_code}, 错误: {str(e)}",
  1270. operation_level="other",
  1271. operation_result="failure",
  1272. module_name="库位分配算法"
  1273. )
  1274. except Exception as log_error:
  1275. logger.error(f"记录算法异常日志失败: {str(log_error)}")
  1276. return None
  1277. def divide_solution_by_layer(self, data):
  1278. # 统计所有存在的层级
  1279. layer_counts = defaultdict(lambda: defaultdict(int))
  1280. existing_layers = set()
  1281. for item in data:
  1282. # 分割层级和类型
  1283. try:
  1284. layer, loc_type = item.split('_')
  1285. layer_num = int(layer)
  1286. existing_layers.add(layer_num)
  1287. layer_counts[layer_num][loc_type] += 1
  1288. except (ValueError, IndexError):
  1289. continue # 跳过无效格式的数据
  1290. # 确定最大层级(至少包含1层)
  1291. max_layer = max(existing_layers) if existing_layers else 1
  1292. # 构建包含所有层级的最终结果
  1293. final_result = {}
  1294. for layer in range(1, max_layer + 1):
  1295. final_result[str(layer)] = dict(layer_counts.get(layer, {}))
  1296. return json.dumps(final_result, indent=2)
  1297. def get_current_pressure(self):
  1298. """获取实时工作压力"""
  1299. last_solution = base_location.objects.order_by('-id').first()
  1300. if not last_solution:
  1301. base_location.objects.create(
  1302. layer1_pressure=0,
  1303. layer2_pressure=0,
  1304. layer3_pressure=0,
  1305. ).save()
  1306. return [
  1307. last_solution.layer1_pressure if last_solution else 0,
  1308. last_solution.layer2_pressure if last_solution else 0,
  1309. last_solution.layer3_pressure if last_solution else 0,
  1310. ]
  1311. def get_current_finish_task(self,container):
  1312. batch = self.get_batch(container)
  1313. if not batch:
  1314. return None
  1315. solution = alloction_pre.objects.filter(batch_number=batch).first()
  1316. if not solution:
  1317. return None
  1318. return [solution.layer1_task_finish_number,solution.layer2_task_finish_number,solution.layer3_task_finish_number]
  1319. def update_current_finish_task(self,container,task_finish_number):
  1320. batch = self.get_batch(container)
  1321. if not batch:
  1322. return None
  1323. solution = alloction_pre.objects.filter(batch_number=batch).first()
  1324. if not solution:
  1325. return None
  1326. solution.layer1_task_finish_number = task_finish_number[0]
  1327. solution.layer2_task_finish_number = task_finish_number[1]
  1328. solution.layer3_task_finish_number = task_finish_number[2]
  1329. solution.save()
  1330. return True
  1331. def get_location_by_type(self, location_type_list, start_location, container_code):
  1332. """
  1333. 根据库位类型获取库位,先根据工作压力找出最空闲的层,看下这层有没有工作,如果有,就从里面找,如果没有,则跳过该层,继续找下一层
  1334. :param location_type_list: 库位分配方案
  1335. {
  1336. "1": {
  1337. "S4": 1
  1338. },
  1339. "2": {},
  1340. "3": {
  1341. "T5": 1
  1342. }
  1343. }
  1344. :param start_location: 起始位置(决定优先级排序方式)
  1345. :return: 符合条件的库位列表
  1346. """
  1347. locations = []
  1348. # 检查已有分配方案
  1349. existing_solution = alloction_pre.objects.filter(batch_number=self.get_batch(container_code)).first()
  1350. if existing_solution.layer_solution_type:
  1351. print(f"[0]已有库位分配方案:{existing_solution.layer_solution_type}")
  1352. return existing_solution.layer_solution_type
  1353. for layer, location_type_dict in location_type_list.items():
  1354. if not location_type_dict:
  1355. continue
  1356. # 获取库位类型列表
  1357. location_type = list(location_type_dict.keys())
  1358. demand_number = sum(location_type_dict.values())
  1359. print (f"[1]层{layer} 需求数量: {demand_number}, 库位: {location_type}")
  1360. location_groups = LocationGroupModel.objects.filter(
  1361. group_type__in=location_type,
  1362. layer=layer,
  1363. status='available'
  1364. )
  1365. if not location_groups:
  1366. print(f"层{layer} 无库位")
  1367. # 根据起始位置选择排序字段
  1368. if start_location == '203':
  1369. ordered_groups = location_groups.order_by('left_priority')
  1370. elif start_location == '103':
  1371. ordered_groups = location_groups.order_by('right_priority')
  1372. else:
  1373. ordered_groups = location_groups.none()
  1374. number = 0
  1375. for location_group in ordered_groups:
  1376. if number >= demand_number:
  1377. break
  1378. locations.append(f"{layer}_{location_group.id}")
  1379. number += 1
  1380. existing_solution.layer_solution_type = locations
  1381. existing_solution.save()
  1382. print(f"[2]分配方案: {locations}")
  1383. return locations if locations else None
  1384. def get_location_by_status(self,container_code,start_location, request=None):
  1385. """
  1386. 根据库位状态获取库位
  1387. :param container_code: 托盘编码
  1388. :param start_location: 起始库位 if in1 优先考虑left_priority, if in2 优先考虑right_priority 就是获取库位组列表之后进行排序
  1389. :param request: 请求对象(可选)
  1390. :return: 库位列表
  1391. """
  1392. # 1. 获取批次状态 1 为已组盘 2 为部分入库 3 为全部入库
  1393. status = self.get_batch_status(container_code)
  1394. #
  1395. if status == 1:
  1396. # 2. 获取库位组
  1397. print(f"[1]第一次入库")
  1398. # 重新获取最新数据
  1399. self.get_location_type(container_code, request)
  1400. location_type_list = json.loads(alloction_pre.objects.filter(batch_number=self.get_batch(container_code)).first().layer_pre_type)
  1401. location_list = self.get_location_by_type(location_type_list,start_location,container_code)
  1402. # 预定这些库组
  1403. self.update_group_status_reserved(location_list, request)
  1404. location_min_value = self.get_location_list_remainder(location_list,container_code)
  1405. print(f"库位安排到第{location_min_value.c_number}个库位:{location_min_value}")
  1406. # 记录库位分配成功日志
  1407. try:
  1408. if request:
  1409. log_success_operation(
  1410. request=request,
  1411. operation_content=f"库位分配成功(第一次入库),托盘编码: {container_code}, 起始位置: {start_location}, 分配库位: {location_min_value.location_code}, 库位组: {location_min_value.location_group}",
  1412. operation_level="other",
  1413. object_id=str(location_min_value.id),
  1414. module_name="库位分配算法"
  1415. )
  1416. else:
  1417. OperationLog.objects.create(
  1418. operator="系统自动",
  1419. operation_content=f"库位分配成功(第一次入库),托盘编码: {container_code}, 起始位置: {start_location}, 分配库位: {location_min_value.location_code}, 库位组: {location_min_value.location_group}",
  1420. operation_level="other",
  1421. operation_result="success",
  1422. object_id=str(location_min_value.id),
  1423. module_name="库位分配算法"
  1424. )
  1425. except Exception as log_error:
  1426. logger.error(f"记录库位分配日志失败: {str(log_error)}")
  1427. return location_min_value
  1428. elif status == 2:
  1429. # 3. 获取部分入库库位
  1430. print (f"部分入库")
  1431. location_list = alloction_pre.objects.filter(batch_number=self.get_batch(container_code)).first().layer_solution_type
  1432. location_min_value = self.get_location_list_remainder(location_list,container_code)
  1433. print(f"库位安排到第{location_min_value.c_number}个库位:{location_min_value}")
  1434. # 记录库位分配成功日志
  1435. try:
  1436. if request:
  1437. log_success_operation(
  1438. request=request,
  1439. operation_content=f"库位分配成功(部分入库),托盘编码: {container_code}, 分配库位: {location_min_value.location_code}, 库位组: {location_min_value.location_group}",
  1440. operation_level="other",
  1441. object_id=str(location_min_value.id),
  1442. module_name="库位分配算法"
  1443. )
  1444. else:
  1445. OperationLog.objects.create(
  1446. operator="系统自动",
  1447. operation_content=f"库位分配成功(部分入库),托盘编码: {container_code}, 分配库位: {location_min_value.location_code}, 库位组: {location_min_value.location_group}",
  1448. operation_level="other",
  1449. operation_result="success",
  1450. object_id=str(location_min_value.id),
  1451. module_name="库位分配算法"
  1452. )
  1453. except Exception as log_error:
  1454. logger.error(f"记录库位分配日志失败: {str(log_error)}")
  1455. return location_min_value
  1456. def release_location(self, location_code, request=None):
  1457. """释放库位并更新关联数据"""
  1458. try:
  1459. location = LocationModel.objects.get(location_code=location_code)
  1460. links = LocationContainerLink.objects.filter(location=location, is_active=True).first()
  1461. if not links:
  1462. logger.error(f"库位{location_code}未关联托盘")
  1463. return True
  1464. container_id = links.container_id
  1465. print(f"释放库位: {location_code}, 关联托盘: {container_id}")
  1466. # 解除关联并标记为非活跃
  1467. links.is_active = False
  1468. links.save()
  1469. # 更新库位状态为可用
  1470. location.status = 'available'
  1471. location.save()
  1472. # 记录释放库位操作日志(敏感操作)
  1473. try:
  1474. if request:
  1475. log_success_operation(
  1476. request=request,
  1477. operation_content=f"释放库位,库位编码: {location_code}, 托盘ID: {container_id}",
  1478. operation_level="update",
  1479. object_id=str(location.id),
  1480. module_name="库位分配"
  1481. )
  1482. else:
  1483. OperationLog.objects.create(
  1484. operator="系统自动",
  1485. operation_content=f"释放库位,库位编码: {location_code}, 托盘ID: {container_id}",
  1486. operation_level="update",
  1487. operation_result="success",
  1488. object_id=str(location.id),
  1489. module_name="库位分配"
  1490. )
  1491. except Exception as log_error:
  1492. logger.error(f"记录释放库位日志失败: {str(log_error)}")
  1493. return True
  1494. except Exception as e:
  1495. logger.error(f"释放库位失败: {str(e)}")
  1496. # 记录失败日志
  1497. try:
  1498. if request:
  1499. log_failure_operation(
  1500. request=request,
  1501. operation_content=f"释放库位失败,库位编码: {location_code}, 错误: {str(e)}",
  1502. operation_level="update",
  1503. module_name="库位分配"
  1504. )
  1505. else:
  1506. OperationLog.objects.create(
  1507. operator="系统自动",
  1508. operation_content=f"释放库位失败,库位编码: {location_code}, 错误: {str(e)}",
  1509. operation_level="update",
  1510. operation_result="failure",
  1511. module_name="库位分配"
  1512. )
  1513. except Exception as log_error:
  1514. logger.error(f"记录失败日志失败: {str(log_error)}")
  1515. return False