views.py 64 KB

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