views.py 55 KB

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