views.py 58 KB

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