views.py 51 KB

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