views.py 56 KB

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