views.py 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483
  1. from wsgiref import headers
  2. from rest_framework.views import APIView
  3. from rest_framework import viewsets
  4. from utils.page import MyPageNumberPagination
  5. from rest_framework.filters import OrderingFilter
  6. from django_filters.rest_framework import DjangoFilterBackend
  7. from rest_framework.response import Response
  8. from django.utils import timezone
  9. import requests
  10. from django.db import transaction
  11. import logging
  12. from rest_framework import status
  13. from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,ContainerWCSModel,TaskModel
  14. from bound.models import BoundBatchModel,BoundDetailModel,BoundListModel,OutBoundDetailModel
  15. from bin.views import LocationAllocation,base_location
  16. from bin.models import LocationModel,LocationContainerLink,LocationGroupModel
  17. from bound.models import BoundBatchModel
  18. from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSerializer
  19. from .serializers import ContainerListGetSerializer,ContainerListPostSerializer
  20. from .serializers import ContainerOperationGetSerializer,ContainerOperationPostSerializer
  21. from .serializers import TaskGetSerializer,TaskPostSerializer
  22. from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter
  23. from rest_framework.permissions import AllowAny
  24. import threading
  25. from django.db import close_old_connections
  26. logger = logging.getLogger(__name__)
  27. class ContainerListViewSet(viewsets.ModelViewSet):
  28. """
  29. retrieve:
  30. Response a data list(get)
  31. list:
  32. Response a data list(all)
  33. create:
  34. Create a data line(post)
  35. delete:
  36. Delete a data line(delete)
  37. """
  38. # authentication_classes = [] # 禁用所有认证类
  39. # permission_classes = [AllowAny] # 允许任意访问
  40. pagination_class = MyPageNumberPagination
  41. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  42. ordering_fields = ['id', "create_time", "update_time", ]
  43. filter_class = ContainerListFilter
  44. def get_project(self):
  45. try:
  46. id = self.kwargs.get('pk')
  47. return id
  48. except:
  49. return None
  50. def get_queryset(self):
  51. id = self.get_project()
  52. if self.request.user:
  53. if id is None:
  54. return ContainerListModel.objects.filter()
  55. else:
  56. return ContainerListModel.objects.filter(id=id)
  57. else:
  58. return ContainerListModel.objects.none()
  59. def get_serializer_class(self):
  60. if self.action in ['list', 'destroy','retrieve']:
  61. return ContainerListGetSerializer
  62. elif self.action in ['create', 'update']:
  63. return ContainerListPostSerializer
  64. else:
  65. return self.http_method_not_allowed(request=self.request)
  66. def create(self, request, *args, **kwargs):
  67. # 创建托盘:托盘码五位数字(唯一),当前库位,目标库位,状态,最后操作时间
  68. container_all = ContainerListModel.objects.all().order_by('container_code')
  69. if container_all.count() == 0:
  70. container_code = 12345
  71. else:
  72. container_code = container_all.last().container_code + 1
  73. container_obj = ContainerListModel.objects.create(
  74. container_code=container_code,
  75. current_location='N/A',
  76. target_location='N/A',
  77. status=0,
  78. last_operation=timezone.now()
  79. )
  80. serializer = ContainerListGetSerializer(container_obj)
  81. headers = self.get_success_headers(serializer.data)
  82. return Response(serializer.data, status=201, headers=headers)
  83. def update(self, request, pk):
  84. qs = self.get_object()
  85. data = self.request.data
  86. serializer = self.get_serializer(qs, data=data)
  87. serializer.is_valid(raise_exception=True)
  88. serializer.save()
  89. headers = self.get_success_headers(serializer.data)
  90. return Response(serializer.data, status=200, headers=headers)
  91. class TaskViewSet(viewsets.ModelViewSet):
  92. """
  93. retrieve:
  94. Response a data list(get)
  95. list:
  96. Response a data list(all)
  97. create:
  98. Create a data line(post)
  99. delete:
  100. Delete a data line(delete)
  101. """
  102. pagination_class = MyPageNumberPagination
  103. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  104. ordering_fields = ['id', "create_time", "update_time", ]
  105. filter_class = TaskFilter
  106. def get_project(self):
  107. try:
  108. id = self.kwargs.get('pk')
  109. return id
  110. except:
  111. return None
  112. def get_queryset(self):
  113. id = self.get_project()
  114. if self.request.user:
  115. if id is None:
  116. return TaskModel.objects.filter()
  117. else:
  118. return TaskModel.objects.filter(id=id)
  119. else:
  120. return TaskModel.objects.none()
  121. def get_serializer_class(self):
  122. if self.action in ['list', 'destroy','retrieve']:
  123. return TaskGetSerializer
  124. elif self.action in ['create', 'update']:
  125. return TaskPostSerializer
  126. else:
  127. return self.http_method_not_allowed(request=self.request)
  128. def create(self, request, *args, **kwargs):
  129. data = self.request.data
  130. return Response(data, status=200, headers=headers)
  131. def update(self, request, pk):
  132. qs = self.get_object()
  133. data = self.request.data
  134. serializer = self.get_serializer(qs, data=data)
  135. serializer.is_valid(raise_exception=True)
  136. serializer.save()
  137. headers = self.get_success_headers(serializer.data)
  138. return Response(serializer.data, status=200, headers=headers)
  139. class TaskRollbackMixin:
  140. @transaction.atomic
  141. def rollback_task(self, request, task_id, *args, **kwargs):
  142. """
  143. 撤销入库任务并回滚相关状态
  144. """
  145. try:
  146. # 获取任务实例并锁定数据库记录
  147. task = ContainerWCSModel.objects.select_for_update().get(taskid=task_id)
  148. container_code = task.container
  149. target_location = task.target_location
  150. batch = task.batch
  151. # 初始化库位分配器
  152. allocator = LocationAllocation()
  153. # ==================== 库位状态回滚 ====================
  154. # 解析目标库位信息(格式:仓库代码-行-列-层)
  155. try:
  156. warehouse_code, row, col, layer = target_location.split('-')
  157. location = LocationModel.objects.get(
  158. warehouse_code=warehouse_code,
  159. row=int(row),
  160. col=int(col),
  161. layer=int(layer)
  162. )
  163. # 回滚库位状态到可用状态
  164. allocator.update_location_status(location.location_code, 'available')
  165. # 更新库位组状态(需要根据实际逻辑实现)
  166. allocator.update_location_group_status(location.location_code)
  167. # 解除库位与托盘的关联
  168. allocator.update_location_container_link(location.location_code, None)
  169. # 清除库位组的批次关联
  170. allocator.update_location_group_batch(location, None)
  171. except (ValueError, LocationModel.DoesNotExist) as e:
  172. logger.error(f"库位解析失败: {str(e)}")
  173. raise Exception("关联库位信息无效")
  174. # ==================== 批次状态回滚 ====================
  175. if batch:
  176. # 将批次状态恢复为未处理状态(假设原状态为1)
  177. allocator.update_batch_status(batch.bound_number, '1')
  178. # ==================== 容器状态回滚 ====================
  179. container_obj = ContainerListModel.objects.get(container_code=container_code)
  180. # 恢复容器详细状态为初始状态(假设原状态为1)
  181. allocator.update_container_detail_status(container_code, 1)
  182. # 恢复容器的目标位置为当前所在位置
  183. container_obj.target_location = task.current_location
  184. container_obj.save()
  185. # ==================== 删除任务记录 ====================
  186. task.delete()
  187. # ==================== 其他关联清理 ====================
  188. # 如果有其他关联数据(如inport_update_task的操作),在此处添加清理逻辑
  189. return Response(
  190. {'code': '200', 'message': '任务回滚成功', 'data': None},
  191. status=status.HTTP_200_OK
  192. )
  193. except ContainerWCSModel.DoesNotExist:
  194. logger.warning(f"任务不存在: {task_id}")
  195. return Response(
  196. {'code': '404', 'message': '任务不存在', 'data': None},
  197. status=status.HTTP_404_NOT_FOUND
  198. )
  199. except Exception as e:
  200. logger.error(f"任务回滚失败: {str(e)}", exc_info=True)
  201. return Response(
  202. {'code': '500', 'message': '服务器内部错误', 'data': None},
  203. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  204. )
  205. class ContainerWCSViewSet(viewsets.ModelViewSet):
  206. """
  207. retrieve:
  208. Response a data list(get)
  209. list:
  210. Response a data list(all)
  211. create:
  212. Create a data line(post)
  213. delete:
  214. Delete a data line(delete)
  215. """
  216. authentication_classes = [] # 禁用所有认证类
  217. permission_classes = [AllowAny] # 允许任意访问
  218. def get_container_wcs(self, request, *args, **kwargs):
  219. data = self.request.data
  220. container = data.get('container_number')
  221. current_location = data.get('current_location')
  222. logger.info(f"请求托盘:{container},请求位置:{current_location}")
  223. data_return = {}
  224. try:
  225. container_obj = ContainerListModel.objects.filter(container_code=container).first()
  226. if not container_obj:
  227. data_return = {
  228. 'code': '400',
  229. 'message': '托盘编码不存在',
  230. 'data': data
  231. }
  232. return Response(data_return, status=status.HTTP_400_BAD_REQUEST)
  233. # 更新容器数据(部分更新)
  234. serializer = ContainerListPostSerializer(
  235. container_obj,
  236. data=data,
  237. partial=True # 允许部分字段更新
  238. )
  239. serializer.is_valid(raise_exception=True)
  240. serializer.save()
  241. # 检查是否已在目标位置
  242. if current_location == str(container_obj.target_location) and current_location!= '203' and current_location!= '103':
  243. logger.info(f"托盘 {container} 已在目标位置")
  244. data_return = {
  245. 'code': '200',
  246. 'message': '当前位置已是目标位置',
  247. 'data': data
  248. }
  249. else:
  250. current_task = ContainerWCSModel.objects.filter(
  251. container=container,
  252. tasktype='inbound',
  253. working = 1,
  254. ).exclude(status=300).first()
  255. if current_task:
  256. data_return = {
  257. 'code': '200',
  258. 'message': '任务已存在,重新下发',
  259. 'data': current_task.to_dict()
  260. }
  261. else:
  262. # 库位分配
  263. container_code = container
  264. print(f"开始生成库位,托盘编码:{container_code}")
  265. allocator = LocationAllocation() # 创建实例
  266. location_list_cnumber = allocator.get_location_by_status(container_code, current_location) # 获取库位列表
  267. if not location_list_cnumber:
  268. print("❌ 通用库位获取失败,请检查托盘编码")
  269. return
  270. print(f"[1]库位:{location_list_cnumber}")
  271. update_location_status = allocator.update_location_status(location_list_cnumber.location_code, 'reserved') # 更新库位状态
  272. if not update_location_status:
  273. print("❌ 库位状态更新失败,请检查托盘编码")
  274. return
  275. print(f"[2]发送任务,库位状态更新成功!")
  276. update_location_group_status = allocator.update_location_group_status(location_list_cnumber.location_code) # 更新库位组状态
  277. if not update_location_group_status:
  278. print("❌ 库位组状态更新失败,请检查托盘编码")
  279. return
  280. print(f"[3]库位组状态更新成功!")
  281. update_batch_status = allocator.update_batch_status(container_code, '2') # 更新批次状态
  282. if not update_batch_status:
  283. print("❌ 批次状态更新失败,请检查批次号")
  284. return
  285. print(f"[4]批次状态更新成功!")
  286. update_location_group_batch = allocator.update_location_group_batch(location_list_cnumber, container_code) # 更新库位组的批次
  287. if not update_location_group_batch:
  288. print("❌ 库位组批次更新失败,请检查托盘编码")
  289. return
  290. print(f"[5]库位组批次更新成功!")
  291. update_location_container_link = allocator.update_location_container_link(location_list_cnumber.location_code, container_code) # 更新库位和托盘的关联关系
  292. if not update_location_container_link:
  293. print("❌ 库位和托盘的关联关系更新失败,请检查托盘编码")
  294. return
  295. print(f"[7]库位和托盘的关联关系更新成功!")
  296. update_location_container_detail = allocator.update_container_detail_status(container_code,2) # 更新库位和托盘的关联关系
  297. if not update_location_container_detail:
  298. print("❌ 库位和托盘的关联关系更新失败,请检查托盘编码")
  299. return
  300. print(f"[8]托盘的关联关系更新成功!")
  301. allocation_target_location = (
  302. location_list_cnumber.warehouse_code + '-'
  303. + f"{int(location_list_cnumber.row):02d}" + '-'
  304. + f"{int(location_list_cnumber.col):02d}" + '-'
  305. + f"{int(location_list_cnumber.layer):02d}"
  306. )
  307. batch_id = allocator.get_batch(container_code)
  308. self.generate_task(container, current_location, allocation_target_location,batch_id,location_list_cnumber.c_number) # 生成任务
  309. current_task = ContainerWCSModel.objects.get(
  310. container=container,
  311. tasktype='inbound',
  312. working=1,
  313. )
  314. batch_obj = BoundBatchModel.objects.filter(bound_number=batch_id).first()
  315. ContainerOperationModel.objects.create(
  316. month = int(timezone.now().strftime("%Y%m")),
  317. container = container_obj,
  318. goods_code = batch_obj.goods_code,
  319. goods_desc = batch_obj.goods_desc,
  320. operation_type ="inbound",
  321. batch_id = batch_obj.id,
  322. goods_qty = batch_obj.goods_qty,
  323. goods_weight = batch_obj.goods_qty,
  324. from_location = container_obj.current_location,
  325. to_location= allocation_target_location,
  326. timestamp=timezone.now(),
  327. operator="WMS",
  328. memo=f"WCS入库: 批次: {batch_id}, 数量: {batch_obj.goods_qty}"
  329. )
  330. data_return = {
  331. 'code': '200',
  332. 'message': '任务下发成功',
  333. 'data': current_task.to_dict()
  334. }
  335. container_obj.target_location = allocation_target_location
  336. container_obj.save()
  337. self.inport_update_task(current_task.id, container_obj.id)
  338. http_status = status.HTTP_200_OK if data_return['code'] == '200' else status.HTTP_400_BAD_REQUEST
  339. return Response(data_return, status=http_status)
  340. except Exception as e:
  341. logger.error(f"处理请求时发生错误: {str(e)}", exc_info=True)
  342. return Response(
  343. {'code': '500', 'message': '服务器内部错误', 'data': None},
  344. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  345. )
  346. @transaction.atomic
  347. def generate_task(self, container, current_location, target_location,batch_id,location_c_number):
  348. batch = BoundBatchModel.objects.filter(bound_number=batch_id).first()
  349. batch_detail = BoundDetailModel.objects.filter(bound_batch=batch).first()
  350. if not batch:
  351. logger.error(f"批次号 {batch_id} 不存在")
  352. return False
  353. data_tosave = {
  354. 'container': container,
  355. 'batch': batch,
  356. 'batch_out': None,
  357. 'bound_list': batch_detail.bound_list,
  358. 'sequence': 1,
  359. 'order_number' :location_c_number,
  360. 'priority': 1,
  361. 'current_location': current_location,
  362. 'month': timezone.now().strftime('%Y%m'),
  363. 'target_location': target_location,
  364. 'tasktype': 'inbound',
  365. 'status': 103,
  366. 'is_delete': False
  367. }
  368. # 生成唯一递增的 taskid
  369. last_task = ContainerWCSModel.objects.filter(
  370. month=data_tosave['month'],
  371. ).order_by('-tasknumber').first()
  372. if last_task:
  373. number_id = last_task.tasknumber + 1
  374. new_id = f"{number_id:05d}"
  375. else:
  376. new_id = "00001"
  377. number_id = f"{data_tosave['month']}{new_id}"
  378. data_tosave['taskid'] = f"inbound-{data_tosave['month']}-{new_id}"
  379. logger.info(f"生成入库任务: {data_tosave['taskid']}")
  380. # 每月生成唯一递增的 taskNumber
  381. data_tosave['tasknumber'] = number_id
  382. ContainerWCSModel.objects.create(**data_tosave)
  383. def update_container_wcs(self, request, *args, **kwargs):
  384. data = self.request.data
  385. logger.info(f"请求托盘:{data.get('container_number')}, 请求位置:{data.get('current_location')}, 任务号:{data.get('taskNumber')}")
  386. try:
  387. # 前置校验
  388. container_obj, error_response = self.validate_container(data)
  389. if error_response:
  390. return error_response
  391. # 更新容器数据
  392. if not self.update_container_data(container_obj, data):
  393. return Response(
  394. {'code': '400', 'message': '数据更新失败', 'data': data},
  395. status=status.HTTP_400_BAD_REQUEST
  396. )
  397. # 处理位置逻辑
  398. task = ContainerWCSModel.objects.filter(
  399. container=container_obj.container_code,
  400. tasktype='inbound'
  401. ).first()
  402. if self.is_already_at_target(container_obj, data.get('current_location')):
  403. return self.handle_target_reached(container_obj, data)
  404. elif task:
  405. data_return = {
  406. 'code': '200',
  407. 'message': '任务已存在,重新下发',
  408. 'data': task.to_dict()
  409. }
  410. return Response(data_return, status=status.HTTP_200_OK)
  411. else:
  412. return self.handle_new_allocation(container_obj, data)
  413. except Exception as e:
  414. logger.error(f"处理请求时发生错误: {str(e)}", exc_info=True)
  415. return Response({'code': '500', 'message': '服务器内部错误', 'data': None},
  416. status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  417. # ---------- 辅助函数 ----------
  418. def validate_container(self, data):
  419. """验证容器是否存在"""
  420. container = data.get('container_number')
  421. container_obj = ContainerListModel.objects.filter(container_code=container).first()
  422. if not container_obj:
  423. return None, Response({
  424. 'code': '400',
  425. 'message': '托盘编码不存在',
  426. 'data': data
  427. }, status=status.HTTP_400_BAD_REQUEST)
  428. return container_obj, None
  429. def update_container_data(self, container_obj, data):
  430. """更新容器数据"""
  431. serializer = ContainerListPostSerializer(
  432. container_obj,
  433. data=data,
  434. partial=True
  435. )
  436. if serializer.is_valid():
  437. serializer.save()
  438. return True
  439. return False
  440. def is_already_at_target(self, container_obj, current_location):
  441. """检查是否已在目标位置"""
  442. return current_location == str(container_obj.target_location)
  443. def handle_target_reached(self, container_obj, data):
  444. """处理已到达目标位置的逻辑"""
  445. logger.info(f"托盘 {container_obj.container_code} 已在目标位置")
  446. task = self.get_task_by_tasknumber(data)
  447. self.update_pressure_values(task, container_obj)
  448. task = self.process_task_completion(data)
  449. if not task:
  450. return Response({'code': '400', 'message': '任务不存在', 'data': data},
  451. status=status.HTTP_400_BAD_REQUEST)
  452. if task and task.tasktype == 'inbound':
  453. self.update_storage_system(container_obj)
  454. if task and task.tasktype == 'outbound' and task.status == 300:
  455. success = self.handle_outbound_completion(container_obj, task)
  456. if not success:
  457. return Response({'code': '500', 'message': '出库状态更新失败', 'data': None},
  458. status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  459. OutboundService.process_next_task()
  460. return Response({
  461. 'code': '200',
  462. 'message': '当前位置已是目标位置',
  463. 'data': data
  464. }, status=status.HTTP_200_OK)
  465. def get_task_by_tasknumber(self, data):
  466. taskNumber = data.get('taskNumber') + 20000000000
  467. task = ContainerWCSModel.objects.filter(tasknumber=taskNumber).first()
  468. if task:
  469. return task
  470. else:
  471. return None
  472. def process_task_completion(self, data):
  473. """处理任务完成状态"""
  474. taskNumber = data.get('taskNumber') + 20000000000
  475. task = ContainerWCSModel.objects.filter(tasknumber=taskNumber).first()
  476. if task:
  477. task.status = 300
  478. task.message = '任务已完成'
  479. task.working = 0
  480. task.save()
  481. return task
  482. def update_pressure_values(self, task, container_obj):
  483. """更新压力值计算"""
  484. if task and task.tasktype in ['inbound']:
  485. base_location_obj = base_location.objects.get(id=1)
  486. layer = int(container_obj.target_location.split('-')[-1])
  487. pressure_field = f"layer{layer}_pressure"
  488. logger.info(f"更新压力值,压力字段:{pressure_field}")
  489. current_pressure = getattr(base_location_obj, pressure_field, 0)
  490. updated_pressure = max(current_pressure - task.working, 0)
  491. setattr(base_location_obj, pressure_field, updated_pressure)
  492. base_location_obj.save()
  493. def update_storage_system(self, container_obj):
  494. """更新仓储系统状态"""
  495. allocator = LocationAllocation()
  496. location_code = self.get_location_code(container_obj.target_location)
  497. # 链式更新操作
  498. update_operations = [
  499. (allocator.update_location_status, location_code, 'occupied'),
  500. (allocator.update_location_container_link, location_code, container_obj.container_code),
  501. (allocator.update_container_detail_status, container_obj.container_code, 2)
  502. ]
  503. for func, *args in update_operations:
  504. if not func(*args):
  505. logger.error(f"操作失败: {func.__name__}")
  506. return False
  507. return True
  508. def get_location_code(self, target_location):
  509. """从目标位置解析获取位置编码"""
  510. parts = target_location.split('-')
  511. coordinate = f"{int(parts[1])}-{int(parts[2])}-{int(parts[3])}"
  512. return LocationModel.objects.filter(coordinate=coordinate).first().location_code
  513. def handle_new_allocation(self, container_obj, data):
  514. """处理新库位分配逻辑"""
  515. allocator = LocationAllocation()
  516. container_code = container_obj.container_code
  517. # 获取并验证库位分配
  518. location = allocator.get_location_by_status(container_code, data.get('current_location'))
  519. if not location or not self.perform_initial_allocation(allocator, location, container_code):
  520. return Response({'code': '400', 'message': '库位分配失败', 'data': data},
  521. status=status.HTTP_400_BAD_REQUEST)
  522. # 生成目标位置并更新容器
  523. target_location = self.generate_target_location(location)
  524. container_obj.target_location = target_location
  525. container_obj.save()
  526. # 创建任务并返回响应
  527. task = self.create_inbound_task(container_code, data, target_location, location)
  528. return Response({
  529. 'code': '200',
  530. 'message': '任务下发成功',
  531. 'data': task.to_dict()
  532. }, status=status.HTTP_200_OK)
  533. def perform_initial_allocation(self, allocator, location, container_code):
  534. """执行初始库位分配操作"""
  535. operations = [
  536. (allocator.update_location_status, location.location_code, 'reserved'),
  537. (allocator.update_location_group_status, location.location_code),
  538. (allocator.update_batch_status, container_code, '2'),
  539. (allocator.update_location_group_batch, location, container_code),
  540. (allocator.update_location_container_link, location.location_code, container_code),
  541. (allocator.update_container_detail_status, container_code, 2)
  542. ]
  543. for func, *args in operations:
  544. if not func(*args):
  545. logger.error(f"分配操作失败: {func.__name__}")
  546. return False
  547. return True
  548. def generate_target_location(self, location):
  549. """生成目标位置字符串"""
  550. return (
  551. f"{location.warehouse_code}-"
  552. f"{int(location.row):02d}-"
  553. f"{int(location.col):02d}-"
  554. f"{int(location.layer):02d}"
  555. )
  556. def create_inbound_task(self, container_code, data, target_location, location):
  557. """创建入库任务"""
  558. batch_id = LocationAllocation().get_batch(container_code)
  559. self.generate_task(
  560. container_code,
  561. data.get('current_location'),
  562. target_location,
  563. batch_id,
  564. location.c_number
  565. )
  566. task = ContainerWCSModel.objects.get(container=container_code, tasktype='inbound')
  567. self.inport_update_task(task.id, container_code)
  568. return task
  569. @transaction.atomic
  570. def inport_update_task(self, wcs_id,container_id):
  571. try:
  572. task_obj = ContainerWCSModel.objects.filter(id=wcs_id).first()
  573. if task_obj:
  574. container_detail_obj = ContainerDetailModel.objects.filter(container=container_id).all()
  575. if container_detail_obj:
  576. for detail in container_detail_obj:
  577. # 保存到数据库
  578. batch = BoundDetailModel.objects.filter(bound_batch_id=detail.batch.id).first()
  579. TaskModel.objects.create(
  580. task_wcs = task_obj,
  581. container_detail = detail,
  582. batch_detail = batch
  583. )
  584. logger.info(f"入库任务 {wcs_id} 已更新")
  585. else:
  586. logger.info(f"入库任务 {container_id} 批次不存在")
  587. else:
  588. logger.info(f"入库任务 {wcs_id} 不存在")
  589. except Exception as e:
  590. logger.error(f"处理入库任务时发生错误: {str(e)}", exc_info=True)
  591. return Response(
  592. {'code': '500', 'message': '服务器内部错误', 'data': None},
  593. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  594. )
  595. def handle_outbound_completion(self, container_obj, task):
  596. """处理出库完成后的库位释放和状态更新"""
  597. try:
  598. allocator = LocationAllocation()
  599. location_task = task.current_location
  600. location_row = location_task.split('-')[1]
  601. location_col = location_task.split('-')[2]
  602. location_layer = location_task.split('-')[3]
  603. location= LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first()
  604. location_code = location.location_code
  605. # 事务确保原子性
  606. with transaction.atomic():
  607. # 解除库位与托盘的关联
  608. if not allocator.release_location(location_code):
  609. raise Exception("解除库位关联失败")
  610. # 更新库位状态为可用
  611. if not allocator.update_location_status(location_code, 'available'):
  612. raise Exception("库位状态更新失败")
  613. # 更新库位组的统计信息
  614. self.handle_group_location_status(location_code, location.location_group)
  615. # 更新容器状态为已出库(假设状态3表示已出库)
  616. container_obj.status = 3
  617. container_obj.save()
  618. return True
  619. except Exception as e:
  620. logger.error(f"出库完成处理失败: {str(e)}")
  621. return False
  622. def handle_group_location_status(self,location_code,location_group):
  623. """
  624. 处理库位组和库位的关联关系
  625. :param location_code: 库位编码
  626. :param location_group: 库位组编码
  627. :return:
  628. """
  629. # 1. 获取库位空闲状态的库位数目
  630. location_obj_number = LocationModel.objects.filter(
  631. location_group=location_group,
  632. status='available'
  633. ).all().count()
  634. # 2. 获取库位组对象
  635. logger.info(f"库位组 {location_group} 下的库位数目:{location_obj_number}")
  636. # 1. 获取库位和库位组的关联关系
  637. location_group_obj = LocationGroupModel.objects.filter(
  638. group_code=location_group
  639. ).first()
  640. if not location_group_obj:
  641. logger.info(f"库位组 {location_group} 不存在")
  642. return None
  643. else:
  644. if location_obj_number == 0:
  645. # 库位组库位已满,更新库位组状态为full
  646. location_group_obj.status = 'full'
  647. location_group_obj.save()
  648. elif location_obj_number < location_group_obj.max_capacity:
  649. location_group_obj.status = 'occupied'
  650. location_group_obj.save()
  651. else:
  652. location_group_obj.status = 'available'
  653. location_group_obj.current_batch = ''
  654. location_group_obj.current_goods_code = ''
  655. location_group_obj.save()
  656. # PDA组盘入库 将扫描到的托盘编码和批次信息保存到数据库
  657. # 1. 先查询托盘对象,如果不存在,则创建托盘对象
  658. # 2. 循环处理每个批次,查询批次对象,
  659. # 3. 更新批次数据(根据业务规则)
  660. # 4. 保存到数据库
  661. # 5. 保存操作记录到数据库
  662. class ContainerDetailViewSet(viewsets.ModelViewSet):
  663. """
  664. retrieve:
  665. Response a data list(get)
  666. list:
  667. Response a data list(all)
  668. create:
  669. Create a data line(post)
  670. delete:
  671. Delete a data line(delete)
  672. """
  673. # authentication_classes = [] # 禁用所有认证类
  674. # permission_classes = [AllowAny] # 允许任意访问
  675. pagination_class = MyPageNumberPagination
  676. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  677. ordering_fields = ['id', "create_time", "update_time", ]
  678. filter_class = ContainerDetailFilter
  679. def get_project(self):
  680. try:
  681. id = self.kwargs.get('pk')
  682. return id
  683. except:
  684. return None
  685. def get_queryset(self):
  686. id = self.get_project()
  687. if self.request.user:
  688. if id is None:
  689. return ContainerDetailModel.objects.filter( is_delete=False)
  690. else:
  691. return ContainerDetailModel.objects.filter( id=id, is_delete=False)
  692. else:
  693. return ContainerDetailModel.objects.none()
  694. def get_serializer_class(self):
  695. if self.action in ['list', 'destroy','retrieve']:
  696. return ContainerDetailGetSerializer
  697. elif self.action in ['create', 'update']:
  698. return ContainerDetailPostSerializer
  699. else:
  700. return self.http_method_not_allowed(request=self.request)
  701. def create(self, request, *args, **kwargs):
  702. data = self.request.data
  703. order_month = str(timezone.now().strftime('%Y%m'))
  704. data['month'] = order_month
  705. container_code = data.get('container')
  706. batches = data.get('batches', []) # 确保有默认空列表
  707. print('扫描到的托盘编码', container_code)
  708. # 处理托盘对象
  709. container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
  710. if container_obj:
  711. data['container'] = container_obj.id
  712. logger.info(f"托盘 {container_code} 已存在")
  713. else:
  714. logger.info(f"托盘 {container_code} 不存在,创建托盘对象")
  715. serializer_list = ContainerListPostSerializer(data={'container_code': container_code})
  716. serializer_list.is_valid(raise_exception=True)
  717. serializer_list.save()
  718. data['container'] = serializer_list.data.get('id')
  719. # 循环处理每个批次
  720. for batch in batches:
  721. bound_number = batch.get('goods_code')
  722. goods_group_qty = batch.get('goods_qty',0)
  723. goods_group_number = batch.get('goods_group',0)
  724. goods_qty = goods_group_number * goods_group_qty
  725. # 查询商品对象
  726. bound_obj = BoundBatchModel.objects.filter(bound_number=bound_number).first()
  727. if not bound_obj:
  728. # 如果商品不存在,返回错误,这里暂时在程序中进行提醒,后续需要改为前端弹窗提醒
  729. logger.error(f"批次 {bound_number} 不存在")
  730. # 跳出此次循环
  731. continue
  732. # return Response({"error": f"商品编码 {bound_number} 不存在"}, status=400)
  733. # 3. 更新批次数据(根据业务规则)
  734. try:
  735. last_qty = bound_obj.goods_in_qty
  736. bound_obj.goods_in_qty += goods_qty
  737. if bound_obj.goods_in_qty >= bound_obj.goods_qty:
  738. bound_obj.goods_in_qty = bound_obj.goods_qty
  739. bound_obj.status = 1 # 批次状态为组盘完成
  740. print('批次id',bound_obj.id)
  741. bound_detail_obj = BoundDetailModel.objects.filter(bound_batch=bound_obj.id).first()
  742. if bound_detail_obj:
  743. bound_detail_obj.status = 1
  744. bound_detail_obj.save()
  745. print('入库申请id',bound_detail_obj.bound_list_id)
  746. # 入库申请全部批次入库完成
  747. bound_batch_all = BoundDetailModel.objects.filter(bound_list=bound_detail_obj.bound_list_id).all()
  748. if bound_batch_all.count() == bound_batch_all.filter(status=1).count():
  749. bound_list_obj = BoundListModel.objects.filter(id=bound_detail_obj.bound_list_id).first()
  750. print('当前状态',bound_list_obj.bound_status)
  751. bound_list_obj.bound_status = 102
  752. print('更新状态',bound_list_obj.bound_status)
  753. bound_list_obj.save()
  754. print('入库申请全部批次组盘完成')
  755. else:
  756. print('入库申请部分批次组盘完成')
  757. else:
  758. bound_obj.status = 0
  759. bound_obj.save() # 保存到数据库
  760. # 创建托盘详情记录(每个批次独立)
  761. print('新增个数',bound_obj.goods_in_qty-last_qty)
  762. if bound_obj.goods_in_qty-last_qty == goods_qty:
  763. detail_data = {
  764. "container": data['container'], # 托盘ID
  765. "batch": bound_obj.id, # 外键关联批次
  766. "goods_code": bound_obj.goods_code,
  767. "goods_desc": bound_obj.goods_desc,
  768. "goods_qty": goods_qty,
  769. "goods_weight": bound_obj.goods_weight,
  770. "status": 1,
  771. "month": data['month'],
  772. "creater": data.get('creater', 'zl') # 默认值兜底
  773. }
  774. serializer = self.get_serializer(data=detail_data)
  775. serializer.is_valid(raise_exception=True)
  776. serializer.save() # 必须保存到数据库
  777. operate_data = {
  778. "month" : data['month'],
  779. "container": data['container'], # 托盘ID
  780. "operation_type" : 'container',
  781. "batch" : bound_obj.id, # 外键关联批次
  782. "goods_code": bound_obj.goods_code,
  783. "goods_desc": bound_obj.goods_desc,
  784. "goods_qty": goods_qty,
  785. "goods_weight": bound_obj.goods_weight,
  786. "operator": data.get('creater', 'zl'), # 默认值兜底
  787. "timestamp": timezone.now(),
  788. "from_location": "container",
  789. "to_location": "container",
  790. "memo": "每组数目"+str(goods_group_qty)+"入库PDA组盘"+str(bound_obj.goods_code)+"数量"+str(goods_qty)
  791. }
  792. serializer_operate = ContainerOperationPostSerializer(data=operate_data)
  793. serializer_operate.is_valid(raise_exception=True)
  794. serializer_operate.save() # 必须保存到数据库
  795. elif bound_obj.goods_in_qty-last_qty > 0:
  796. print('批次数量不一致')
  797. detail_data = {
  798. "container": data['container'], # 托盘ID
  799. "batch": bound_obj.id, # 外键关联批次
  800. "goods_code": bound_obj.goods_code,
  801. "goods_desc": bound_obj.goods_desc,
  802. "goods_qty": bound_obj.goods_in_qty-last_qty,
  803. "goods_weight": bound_obj.goods_weight,
  804. "status": 1,
  805. "month": data['month'],
  806. "creater": data.get('creater', 'zl') # 默认值兜底
  807. }
  808. serializer = self.get_serializer(data=detail_data)
  809. serializer.is_valid(raise_exception=True)
  810. serializer.save() # 必须保存到数据库
  811. operate_data = {
  812. "month" : data['month'],
  813. "container": data['container'], # 托盘ID
  814. "operation_type" : 'container',
  815. "batch" : bound_obj.id, # 外键关联批次
  816. "goods_code": bound_obj.goods_code,
  817. "goods_desc": bound_obj.goods_desc,
  818. "goods_qty": bound_obj.goods_in_qty-last_qty,
  819. "goods_weight": bound_obj.goods_weight,
  820. "operator": data.get('creater', 'zl'), # 默认值兜底
  821. "timestamp": timezone.now(),
  822. "from_location": "container",
  823. "to_location": "container",
  824. "memo": "每组数目"+str(goods_group_qty)+"入库PDA组盘(数量不一致)"+str(bound_obj.goods_code)+"数量"+str(goods_qty)
  825. }
  826. serializer_operate = ContainerOperationPostSerializer(data=operate_data)
  827. serializer_operate.is_valid(raise_exception=True)
  828. serializer_operate.save() # 必须保存到数据库
  829. else :
  830. print('重复组盘')
  831. except Exception as e:
  832. print(f"更新批次 {bound_number} 失败: {str(e)}")
  833. continue
  834. # 将处理后的数据返回(或根据业务需求保存到数据库)
  835. res_data={
  836. "code": "200",
  837. "msg": "Success Create",
  838. "data": data
  839. }
  840. return Response(res_data, status=200)
  841. def update(self, request, pk):
  842. qs = self.get_object()
  843. data = self.request.data
  844. serializer = self.get_serializer(qs, data=data)
  845. serializer.is_valid(raise_exception=True)
  846. serializer.save()
  847. headers = self.get_success_headers(serializer.data)
  848. return Response(serializer.data, status=200, headers=headers)
  849. class ContainerOperateViewSet(viewsets.ModelViewSet):
  850. """
  851. retrieve:
  852. Response a data list(get)
  853. list:
  854. Response a data list(all)
  855. create:
  856. Create a data line(post)
  857. delete:
  858. Delete a data line(delete)
  859. """
  860. # authentication_classes = [] # 禁用所有认证类
  861. # permission_classes = [AllowAny] # 允许任意访问
  862. pagination_class = MyPageNumberPagination
  863. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  864. ordering_fields = ['id', "timestamp" ]
  865. filter_class = ContainerOperationFilter
  866. def get_project(self):
  867. try:
  868. id = self.kwargs.get('pk')
  869. return id
  870. except:
  871. return None
  872. def get_queryset(self):
  873. id = self.get_project()
  874. if self.request.user:
  875. if id is None:
  876. return ContainerOperationModel.objects.filter( is_delete=False)
  877. else:
  878. return ContainerOperationModel.objects.filter( id=id, is_delete=False)
  879. else:
  880. return ContainerOperationModel.objects.none()
  881. def get_serializer_class(self):
  882. if self.action in ['list', 'destroy','retrieve']:
  883. return ContainerOperationGetSerializer
  884. elif self.action in ['create', 'update']:
  885. return ContainerOperationPostSerializer
  886. else:
  887. return self.http_method_not_allowed(request=self.request)
  888. def create(self, request, *args, **kwargs):
  889. data = self.request.data
  890. serializer = self.get_serializer(data=data)
  891. serializer.is_valid(raise_exception=True)
  892. serializer.save()
  893. headers = self.get_success_headers(serializer.data)
  894. return Response(serializer.data, status=200, headers=headers)
  895. def update(self, request, pk):
  896. qs = self.get_object()
  897. data = self.request.data
  898. serializer = self.get_serializer(qs, data=data)
  899. serializer.is_valid(raise_exception=True)
  900. serializer.save()
  901. headers = self.get_success_headers(serializer.data)
  902. return Response(serializer.data, status=200, headers=headers)
  903. class OutboundService:
  904. @staticmethod
  905. def generate_task_id():
  906. """生成唯一任务ID(格式: outbound-年月-顺序号)"""
  907. month = timezone.now().strftime("%Y%m")
  908. last_task = ContainerWCSModel.objects.filter(
  909. tasktype='outbound',
  910. month=int(month)
  911. ).order_by('-sequence').first()
  912. sequence = last_task.sequence + 1 if last_task else 1
  913. return f"outbound-{month}-{sequence:05d}"
  914. @staticmethod
  915. def send_task_to_wcs(task):
  916. """异步发送任务到WCS(非阻塞版本)"""
  917. # 提取任务关键数据用于线程(避免直接传递ORM对象)
  918. task_data = {
  919. 'task_id': task.pk, # 使用主键而不是对象
  920. 'send_data': {
  921. "code":'200',
  922. "message": task.message,
  923. "data":{
  924. "taskid": task.taskid,
  925. "container": task.container,
  926. "current_location": task.current_location,
  927. "target_location": task.target_location,
  928. "tasktype": task.tasktype,
  929. "month": task.month,
  930. "message": task.message,
  931. "status": task.status,
  932. "taskNumber": task.tasknumber-20000000000,
  933. "order_number":task.order_number,
  934. "sequence":task.sequence
  935. }
  936. }
  937. }
  938. # 创建并启动线程
  939. thread = threading.Thread(
  940. target=OutboundService._async_send_handler,
  941. kwargs=task_data,
  942. daemon=True # 守护线程(主程序退出时自动终止)
  943. )
  944. thread.start()
  945. return True # 立即返回表示已开始处理
  946. @staticmethod
  947. def _async_send_handler(task_id, send_data):
  948. """异步处理的实际工作函数"""
  949. try:
  950. # 每个线程需要独立的数据库连接
  951. close_old_connections()
  952. # 重新获取任务对象(确保使用最新数据)
  953. task = ContainerWCSModel.objects.get(pk=task_id)
  954. # 发送第一个请求(不处理结果)
  955. requests.post(
  956. "http://127.0.0.1:8008/container/batch/",
  957. json=send_data,
  958. timeout=10
  959. )
  960. # 发送关键请求
  961. response = requests.post(
  962. "http://192.168.18.67:1616/wcs/WebApi/getOutTask",
  963. json=send_data,
  964. timeout=10
  965. )
  966. # 处理响应
  967. if response.status_code == 200:
  968. task.status = 200
  969. task.save()
  970. logger.info(f"任务 {task.taskid} 已发送")
  971. else:
  972. logger.error(f"WCS返回错误: {response.text}")
  973. except Exception as e:
  974. logger.error(f"发送失败: {str(e)}")
  975. finally:
  976. close_old_connections() # 清理数据库连接
  977. @staticmethod
  978. def create_initial_tasks(container_list,bound_list_id):
  979. """生成初始任务队列"""
  980. with transaction.atomic():
  981. current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id).first()
  982. if current_WCS:
  983. logger.error(f"当前{bound_list_id}已有出库任务")
  984. return False
  985. tasks = []
  986. start_sequence = ContainerWCSModel.objects.filter(tasktype='outbound').count() + 1
  987. tasknumber = ContainerWCSModel.objects.filter().count()
  988. tasknumber_index = 1
  989. for index, container in enumerate(container_list, start=start_sequence):
  990. container_obj = ContainerListModel.objects.filter(id =container['container_number']).first()
  991. if container_obj.current_location != container_obj.target_location:
  992. logger.error(f"托盘 {container_obj.container_code} 未到达目的地,不生成任务")
  993. return False
  994. OutBoundDetail_obj = OutBoundDetailModel.objects.filter(bound_list=bound_list_id,bound_batch_number_id=container['batch_id']).first()
  995. if not OutBoundDetail_obj:
  996. logger.error(f"批次 {container['batch_id']} 不存在")
  997. return False
  998. month = int(timezone.now().strftime("%Y%m"))
  999. task = ContainerWCSModel(
  1000. taskid=OutboundService.generate_task_id(),
  1001. batch = OutBoundDetail_obj.bound_batch_number,
  1002. batch_out = OutBoundDetail_obj.bound_batch,
  1003. bound_list = OutBoundDetail_obj.bound_list,
  1004. sequence=index,
  1005. order_number = container['location_c_number'],
  1006. priority=100,
  1007. tasknumber = month*100000+tasknumber_index+tasknumber,
  1008. container=container_obj.container_code,
  1009. current_location=container_obj.current_location,
  1010. target_location="203",
  1011. tasktype="outbound",
  1012. month=int(timezone.now().strftime("%Y%m")),
  1013. message="等待出库",
  1014. status=100,
  1015. )
  1016. tasknumber_index += 1
  1017. tasks.append(task)
  1018. container_obj = ContainerListModel.objects.filter(container_code=task.container).first()
  1019. container_obj.target_location = task.target_location
  1020. container_obj.save()
  1021. ContainerWCSModel.objects.bulk_create(tasks)
  1022. logger.info(f"已创建 {len(tasks)} 个初始任务")
  1023. @staticmethod
  1024. def insert_new_tasks(new_tasks):
  1025. """动态插入新任务并重新排序"""
  1026. with transaction.atomic():
  1027. pending_tasks = list(ContainerWCSModel.objects.select_for_update().filter(status=100))
  1028. # 插入新任务
  1029. for new_task_data in new_tasks:
  1030. new_task = ContainerWCSModel(
  1031. taskid=OutboundService.generate_task_id(),
  1032. priority=new_task_data.get('priority', 100),
  1033. container=new_task_data['container'],
  1034. current_location=new_task_data['current_location'],
  1035. target_location=new_task_data.get('target_location', 'OUT01'),
  1036. tasktype="outbound",
  1037. month=int(timezone.now().strftime("%Y%m")),
  1038. message="等待出库",
  1039. status=100,
  1040. )
  1041. # 找到插入位置
  1042. insert_pos = 0
  1043. for i, task in enumerate(pending_tasks):
  1044. if new_task.priority < task.priority:
  1045. insert_pos = i
  1046. break
  1047. else:
  1048. insert_pos = len(pending_tasks)
  1049. pending_tasks.insert(insert_pos, new_task)
  1050. # 重新分配顺序号
  1051. for i, task in enumerate(pending_tasks, start=1):
  1052. task.sequence = i
  1053. if task.pk is None:
  1054. task.save()
  1055. else:
  1056. task.save(update_fields=['sequence'])
  1057. logger.info(f"已插入 {len(new_tasks)} 个新任务")
  1058. @staticmethod
  1059. def process_next_task():
  1060. """处理下一个任务"""
  1061. next_task = ContainerWCSModel.objects.filter(status=100).order_by('sequence').first()
  1062. if not next_task:
  1063. logger.info("没有待处理任务")
  1064. return
  1065. allocator = LocationAllocation()
  1066. OutboundService.perform_initial_allocation(allocator, next_task.current_location)
  1067. OutboundService.send_task_to_wcs(next_task)
  1068. def perform_initial_allocation(allocator, location):
  1069. """执行初始库位分配操作"""
  1070. location_row = location.split('-')[1]
  1071. location_col = location.split('-')[2]
  1072. location_layer = location.split('-')[3]
  1073. location_code = LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first().location_code
  1074. if not location_code:
  1075. logger.error(f"未找到库位: {location}")
  1076. operations = [
  1077. (allocator.update_location_status,location_code, 'reserved'),
  1078. (allocator.update_location_group_status,location_code)
  1079. ]
  1080. for func, *args in operations:
  1081. if not func(*args):
  1082. logger.error(f"分配操作失败: {func.__name__}")
  1083. return False
  1084. return True
  1085. class OutTaskViewSet(APIView):
  1086. """
  1087. # fun:get_out_task:下发出库任务
  1088. # fun:get_batch_count_by_boundlist:获取出库申请下的批次数量
  1089. # fun:generate_location_by_demand:根据出库需求生成出库任务
  1090. """
  1091. # authentication_classes = [] # 禁用所有认证类
  1092. # permission_classes = [AllowAny] # 允许任意访问
  1093. def post(self, request):
  1094. try:
  1095. data = self.request.data
  1096. logger.info(f"收到 WMS 推送数据: {data}")
  1097. # 假设从请求中获取 bound_list_id
  1098. bound_list_id = data.get('bound_list_id')
  1099. batch_count = self.get_batch_count_by_boundlist(bound_list_id)
  1100. logger.info(f"出库批次数量: {batch_count}")
  1101. # 获取需要出库的托盘列表
  1102. generate_result = self.generate_location_by_demand(batch_count,bound_list_id)
  1103. if generate_result['code'] != '200':
  1104. current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id).first()
  1105. if current_WCS:
  1106. OutboundService.process_next_task()
  1107. return Response({"code": "200", "msg": "Success 再次发送任务"}, status=200)
  1108. return Response(generate_result, status=500)
  1109. container_list = generate_result['data']
  1110. logger.info(f"生成出库任务: {container_list}")
  1111. # 2. 生成初始任务
  1112. OutboundService.create_initial_tasks(container_list,bound_list_id)
  1113. # 3. 立即发送第一个任务
  1114. OutboundService.process_next_task()
  1115. return Response({"code": "200", "msg": "Success"}, status=200)
  1116. except Exception as e:
  1117. logger.error(f"任务生成失败: {str(e)}")
  1118. return Response({"code": "500", "msg": str(e)}, status=500)
  1119. # 获取出库需求
  1120. def get_batch_count_by_boundlist(self,bound_list_id):
  1121. try:
  1122. bound_list_obj_all = OutBoundDetailModel.objects.filter(bound_list=bound_list_id).all()
  1123. if bound_list_obj_all:
  1124. batch_count_dict = {}
  1125. # 统计批次数量(创建哈希表,去重)
  1126. for batch in bound_list_obj_all:
  1127. if batch.bound_batch_number_id not in batch_count_dict:
  1128. batch_count_dict[batch.bound_batch_number_id] = batch.bound_batch.goods_out_qty
  1129. else:
  1130. batch_count_dict[batch.bound_batch_number_id] += batch.bound_batch.goods_out_qty
  1131. return batch_count_dict
  1132. else:
  1133. logger.error(f"查询批次数量失败: {bound_list_id} 不存在")
  1134. return {}
  1135. except Exception as e:
  1136. logger.error(f"查询批次数量失败: {str(e)}")
  1137. return {}
  1138. def get_location_by_status_and_batch(self,status,bound_id):
  1139. try:
  1140. container_obj = ContainerDetailModel.objects.filter(batch=bound_id,status=status).all()
  1141. if container_obj:
  1142. container_dict = {}
  1143. # 统计托盘数量(创建哈希表,去重)
  1144. for obj in container_obj:
  1145. if obj.container_id not in container_dict:
  1146. container_dict[obj.container_id] = obj.goods_qty
  1147. else:
  1148. container_dict[obj.container_id] += obj.goods_qty
  1149. return container_dict
  1150. else:
  1151. logger.error(f"查询{status}状态的批次数量失败: {bound_id} 不存在")
  1152. return {}
  1153. except Exception as e:
  1154. logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
  1155. return {}
  1156. def get_order_by_batch(self,container_list,bound_id):
  1157. try:
  1158. container_dict = {}
  1159. for container in container_list:
  1160. location_container = LocationContainerLink.objects.filter(container_id=container,is_active=True).first()
  1161. if location_container:
  1162. location_c_number = location_container.location.c_number
  1163. if container not in container_dict:
  1164. container_dict[container] = {
  1165. "container_number":container,
  1166. "location_c_number":location_c_number,
  1167. "location_id ":location_container.location.id,
  1168. "location_type":location_container.location.location_type,
  1169. "batch_id":bound_id,
  1170. }
  1171. if len(container_dict.keys()) == len(container_list):
  1172. return container_dict
  1173. else:
  1174. logger.error(f"查询批次数量失败: {container_list} 不存在")
  1175. return {}
  1176. except Exception as e:
  1177. logger.error(f"查询批次数量失败: {str(e)}")
  1178. return {}
  1179. except Exception as e:
  1180. logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
  1181. return {}
  1182. def generate_location_by_demand(self,demand_list,bound_list_id):
  1183. # demand_list {1: 25, 2: 17}
  1184. try:
  1185. return_location =[]
  1186. for demand_id, demand_qty in demand_list.items():
  1187. container_list = self.get_location_by_status_and_batch(2, demand_id)
  1188. if not container_list:
  1189. return {"code": "500", "msg": f"批次 {demand_id} 不存在"}
  1190. container_id_list = container_list.keys()
  1191. container_order = self.get_order_by_batch(container_id_list,demand_id)
  1192. if not container_order:
  1193. return {"code": "500", "msg": f"托盘 {container_id_list} 不存在"}
  1194. order = sorted(
  1195. container_order.values(),
  1196. key=lambda x: (
  1197. int(x['location_type'][-1]), # 提取最后一位数字并转为整数
  1198. -x['location_c_number'] # 按location_c_number降序
  1199. )
  1200. )
  1201. current_qty = 0
  1202. for container in order:
  1203. container_detail_obj = ContainerDetailModel.objects.filter(container_id=container['container_number'],batch_id=demand_id,status=2).all()
  1204. container_obj = ContainerListModel.objects.filter(id=container['container_number']).first()
  1205. if not container_obj:
  1206. return {"code": "500", "msg": f"托盘 {container['container_number']} 不存在"}
  1207. if not container_detail_obj:
  1208. return {"code": "500", "msg": f"托盘上无该批次,请检查{container['container_number']} 不存在"}
  1209. goods_qty = 0
  1210. for obj in container_detail_obj:
  1211. goods_qty += obj.goods_qty
  1212. if current_qty < demand_qty:
  1213. now_qty = current_qty
  1214. current_qty += goods_qty
  1215. return_location.append(container)
  1216. logger.info(f"批次 {demand_id} 托盘 {container['container_number']} 当前数量 {current_qty}")
  1217. self.create_or_update_container_operation(container_obj,demand_id,bound_list_id,203,min(demand_qty-now_qty,goods_qty),min(demand_qty-now_qty,goods_qty))
  1218. self.update_container_detail_out_qty(container_obj,demand_id)
  1219. else:
  1220. break
  1221. return {"code": "200", "msg": "Success", "data": return_location}
  1222. except Exception as e:
  1223. return {"code": "500", "msg": str(e)}
  1224. def create_or_update_container_operation(self,container_obj,batch_id,bound_id,to_location,goods_qty,goods_weight):
  1225. try:
  1226. container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,bound_id=bound_id,operation_type="outbound").first()
  1227. if container_operation_obj:
  1228. logger.info(f"[0]查询出库任务: {container_operation_obj.operation_type} ")
  1229. logger.info(f"更新出库任务: {container_obj.container_code} 批次 {batch_id} 出库需求: {bound_id} 数量: {goods_qty} 重量: {goods_weight}")
  1230. container_operation_obj.to_location = to_location
  1231. container_operation_obj.goods_qty = goods_qty
  1232. container_operation_obj.goods_weight = goods_weight
  1233. container_operation_obj.save()
  1234. else:
  1235. logger.info(f"创建出库任务: {container_obj.container_code} 批次 {batch_id} 出库需求: {bound_id} 数量: {goods_qty} 重量: {goods_weight}")
  1236. batch = BoundBatchModel.objects.filter(id=batch_id).first()
  1237. if not batch:
  1238. return {"code": "500", "msg": f"批次 {batch_id} 不存在"}
  1239. ContainerOperationModel.objects.create(
  1240. month = int(timezone.now().strftime("%Y%m")),
  1241. container = container_obj,
  1242. goods_code = batch.goods_code,
  1243. goods_desc = batch.goods_desc,
  1244. operation_type ="outbound",
  1245. batch_id = batch_id,
  1246. bound_id = bound_id,
  1247. goods_qty = goods_qty,
  1248. goods_weight = goods_weight,
  1249. from_location = container_obj.current_location,
  1250. to_location= to_location,
  1251. timestamp=timezone.now(),
  1252. operator="WMS",
  1253. memo=f"出库需求: {bound_id}, 批次: {batch_id}, 数量: {goods_qty}"
  1254. )
  1255. return {"code": "200", "msg": "Success"}
  1256. except Exception as e:
  1257. return {"code": "500", "msg": str(e)}
  1258. def update_container_detail_out_qty(self,container_obj,batch_id):
  1259. try:
  1260. logger.info(f"[1]更新托盘出库数量: {container_obj.container_code} 批次 {batch_id} ")
  1261. container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,operation_type="outbound").all()
  1262. if not container_operation_obj:
  1263. logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无出库任务")
  1264. return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无出库任务"}
  1265. container_detail_obj = ContainerDetailModel.objects.filter(container=container_obj,batch_id=batch_id,status=2).first()
  1266. if not container_detail_obj:
  1267. logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无批次信息")
  1268. return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无批次信息"}
  1269. out_qty = 0
  1270. for obj in container_operation_obj:
  1271. out_qty += obj.goods_qty
  1272. if out_qty >= container_detail_obj.goods_qty:
  1273. out_qty = container_detail_obj.goods_qty
  1274. container_detail_obj.status = 3
  1275. break
  1276. if out_qty == 0:
  1277. logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无出库数量")
  1278. return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无出库数量"}
  1279. container_detail_obj.goods_out_qty = out_qty
  1280. container_detail_obj.save()
  1281. return {"code": "200", "msg": "Success"}
  1282. except Exception as e:
  1283. return {"code": "500", "msg": str(e)}
  1284. class BatchViewSet(viewsets.ModelViewSet):
  1285. authentication_classes = [] # 禁用所有认证类
  1286. permission_classes = [AllowAny] # 允许任意访问
  1287. def wcs_post(self, request, *args, **kwargs):
  1288. data = self.request.data
  1289. logger.info(f"收到 WMS 推送数据: {data}")
  1290. return Response({"code": "200", "msg": "Success"}, status=200)