views.py 63 KB

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