views.py 63 KB

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