views.py 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  1. from rest_framework import viewsets
  2. from utils.page import MyPageNumberPagination
  3. from utils.datasolve import sumOfList, transportation_calculate
  4. from utils.md5 import Md5
  5. from rest_framework.filters import OrderingFilter
  6. from django_filters.rest_framework import DjangoFilterBackend
  7. from rest_framework.response import Response
  8. from rest_framework.exceptions import APIException
  9. from django.utils import timezone
  10. import requests
  11. import json
  12. from django.conf import settings
  13. from django.db import transaction
  14. import logging
  15. from rest_framework import status
  16. from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,ContainerWCSModel,TaskModel
  17. from bound.models import BoundBatchModel,BoundDetailModel,BoundListModel,OutBoundDetailModel
  18. from bin.views import LocationAllocation
  19. from bin.models import LocationModel,LocationContainerLink
  20. # from .files import FileListRenderCN, FileDetailRenderCN
  21. from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSerializer
  22. from .serializers import ContainerListGetSerializer,ContainerListPostSerializer
  23. from .serializers import ContainerOperationGetSerializer,ContainerOperationPostSerializer
  24. from .serializers import TaskGetSerializer,TaskPostSerializer
  25. from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter
  26. # 以后添加模
  27. from warehouse.models import ListModel as warehouse
  28. from staff.models import ListModel as staff
  29. from rest_framework.permissions import AllowAny
  30. logger = logging.getLogger(__name__)
  31. class ContainerListViewSet(viewsets.ModelViewSet):
  32. """
  33. retrieve:
  34. Response a data list(get)
  35. list:
  36. Response a data list(all)
  37. create:
  38. Create a data line(post)
  39. delete:
  40. Delete a data line(delete)
  41. """
  42. # authentication_classes = [] # 禁用所有认证类
  43. # permission_classes = [AllowAny] # 允许任意访问
  44. pagination_class = MyPageNumberPagination
  45. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  46. ordering_fields = ['id', "create_time", "update_time", ]
  47. filter_class = ContainerListFilter
  48. def get_project(self):
  49. try:
  50. id = self.kwargs.get('pk')
  51. return id
  52. except:
  53. return None
  54. def get_queryset(self):
  55. id = self.get_project()
  56. if self.request.user:
  57. if id is None:
  58. return ContainerListModel.objects.filter()
  59. else:
  60. return ContainerListModel.objects.filter( id=id)
  61. else:
  62. return ContainerListModel.objects.none()
  63. def get_serializer_class(self):
  64. if self.action in ['list', 'destroy','retrieve']:
  65. return ContainerListGetSerializer
  66. elif self.action in ['create', 'update']:
  67. return ContainerListPostSerializer
  68. else:
  69. return self.http_method_not_allowed(request=self.request)
  70. def create(self, request, *args, **kwargs):
  71. data = self.request.data
  72. order_month = str(timezone.now().strftime('%Y%m'))
  73. data['month'] = order_month
  74. data['last_operate'] = str(timezone.now())
  75. serializer = self.get_serializer(data=data)
  76. serializer.is_valid(raise_exception=True)
  77. serializer.save()
  78. headers = self.get_success_headers(serializer.data)
  79. return Response(serializer.data, status=200, headers=headers)
  80. def update(self, request, pk):
  81. qs = self.get_object()
  82. data = self.request.data
  83. serializer = self.get_serializer(qs, data=data)
  84. serializer.is_valid(raise_exception=True)
  85. serializer.save()
  86. headers = self.get_success_headers(serializer.data)
  87. return Response(serializer.data, status=200, headers=headers)
  88. class TaskViewSet(viewsets.ModelViewSet):
  89. """
  90. retrieve:
  91. Response a data list(get)
  92. list:
  93. Response a data list(all)
  94. create:
  95. Create a data line(post)
  96. delete:
  97. Delete a data line(delete)
  98. """
  99. pagination_class = MyPageNumberPagination
  100. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  101. ordering_fields = ['id', "create_time", "update_time", ]
  102. filter_class = TaskFilter
  103. def get_project(self):
  104. try:
  105. id = self.kwargs.get('pk')
  106. return id
  107. except:
  108. return None
  109. def get_queryset(self):
  110. id = self.get_project()
  111. if self.request.user:
  112. if id is None:
  113. return TaskModel.objects.filter()
  114. else:
  115. return TaskModel.objects.filter( id=id)
  116. else:
  117. return TaskModel.objects.none()
  118. def get_serializer_class(self):
  119. if self.action in ['list', 'destroy','retrieve']:
  120. return TaskGetSerializer
  121. elif self.action in ['create', 'update']:
  122. return TaskPostSerializer
  123. else:
  124. return self.http_method_not_allowed(request=self.request)
  125. def create(self, request, *args, **kwargs):
  126. data = self.request.data
  127. return Response(data, status=200, headers=headers)
  128. def update(self, request, pk):
  129. qs = self.get_object()
  130. data = self.request.data
  131. serializer = self.get_serializer(qs, data=data)
  132. serializer.is_valid(raise_exception=True)
  133. serializer.save()
  134. headers = self.get_success_headers(serializer.data)
  135. return Response(serializer.data, status=200, headers=headers)
  136. class ContainerWCSViewSet(viewsets.ModelViewSet):
  137. """
  138. retrieve:
  139. Response a data list(get)
  140. list:
  141. Response a data list(all)
  142. create:
  143. Create a data line(post)
  144. delete:
  145. Delete a data line(delete)
  146. """
  147. authentication_classes = [] # 禁用所有认证类
  148. permission_classes = [AllowAny] # 允许任意访问
  149. def get_container_wcs(self, request, *args, **kwargs):
  150. data = self.request.data
  151. container = data.get('container_number')
  152. current_location = data.get('current_location')
  153. logger.info(f"请求托盘:{container},请求位置:{current_location}")
  154. if current_location =="203":
  155. current_location ="in1"
  156. elif current_location=="103":
  157. current_location="in2"
  158. data_return = {}
  159. try:
  160. container_obj = ContainerListModel.objects.filter(container_code=container).first()
  161. if not container_obj:
  162. data_return = {
  163. 'code': '400',
  164. 'message': '托盘编码不存在',
  165. 'data': data
  166. }
  167. return Response(data_return, status=status.HTTP_400_BAD_REQUEST)
  168. # 更新容器数据(部分更新)
  169. serializer = ContainerListPostSerializer(
  170. container_obj,
  171. data=data,
  172. partial=True # 允许部分字段更新
  173. )
  174. serializer.is_valid(raise_exception=True)
  175. serializer.save()
  176. # 检查是否已在目标位置
  177. if current_location == str(container_obj.target_location):
  178. logger.info(f"托盘 {container} 已在目标位置")
  179. data_return = {
  180. 'code': '200',
  181. 'message': '当前位置已是目标位置',
  182. 'data': data
  183. }
  184. else:
  185. current_task = ContainerWCSModel.objects.filter(
  186. container=container,
  187. tasktype='inbound'
  188. ).first()
  189. if current_task:
  190. data_return = {
  191. 'code': '200',
  192. 'message': '任务已存在,重新下发',
  193. 'data': current_task.to_dict()
  194. }
  195. else:
  196. # 库位分配
  197. container_code = container
  198. print(f"开始生成库位,托盘编码:{container_code}")
  199. allocator = LocationAllocation() # 创建实例
  200. location_list_cnumber = allocator.get_location_by_status(container_code, current_location) # 获取库位列表
  201. if not location_list_cnumber:
  202. print("❌ 通用库位获取失败,请检查托盘编码")
  203. return
  204. print(f"[1]库位:{location_list_cnumber}")
  205. update_location_status = allocator.update_location_status(location_list_cnumber.location_code, 'reserved') # 更新库位状态
  206. if not update_location_status:
  207. print("❌ 库位状态更新失败,请检查托盘编码")
  208. return
  209. print(f"[2]发送任务,库位状态更新成功!")
  210. update_location_group_status = allocator.update_location_group_status(location_list_cnumber.location_code) # 更新库位组状态
  211. if not update_location_group_status:
  212. print("❌ 库位组状态更新失败,请检查托盘编码")
  213. return
  214. print(f"[3]库位组状态更新成功!")
  215. update_batch_status = allocator.update_batch_status(container_code, '2') # 更新批次状态
  216. if not update_batch_status:
  217. print("❌ 批次状态更新失败,请检查批次号")
  218. return
  219. print(f"[4]批次状态更新成功!")
  220. update_location_group_batch = allocator.update_location_group_batch(location_list_cnumber, container_code) # 更新库位组的批次
  221. if not update_location_group_batch:
  222. print("❌ 库位组批次更新失败,请检查托盘编码")
  223. return
  224. print(f"[5]库位组批次更新成功!")
  225. update_location_container_link = allocator.update_location_container_link(location_list_cnumber.location_code, container_code) # 更新库位和托盘的关联关系
  226. if not update_location_container_link:
  227. print("❌ 库位和托盘的关联关系更新失败,请检查托盘编码")
  228. return
  229. print(f"[7]库位和托盘的关联关系更新成功!")
  230. allocation_target_location = (
  231. location_list_cnumber.warehouse_code + '-'
  232. + f"{int(location_list_cnumber.row):02d}" + '-'
  233. + f"{int(location_list_cnumber.col):02d}" + '-'
  234. + f"{int(location_list_cnumber.layer):02d}"
  235. )
  236. self.generate_task(container, current_location, allocation_target_location) # 生成任务
  237. current_task = ContainerWCSModel.objects.get(
  238. container=container,
  239. tasktype='inbound'
  240. )
  241. data_return = {
  242. 'code': '200',
  243. 'message': '任务下发成功',
  244. 'data': current_task.to_dict()
  245. }
  246. container_obj.target_location = allocation_target_location
  247. container_obj.save()
  248. self.inport_update_task(current_task.id, container_obj.id)
  249. http_status = status.HTTP_200_OK if data_return['code'] == '200' else status.HTTP_400_BAD_REQUEST
  250. return Response(data_return, status=http_status)
  251. except Exception as e:
  252. logger.error(f"处理请求时发生错误: {str(e)}", exc_info=True)
  253. return Response(
  254. {'code': '500', 'message': '服务器内部错误', 'data': None},
  255. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  256. )
  257. @transaction.atomic
  258. def generate_task(self, container, current_location, target_location):
  259. data_tosave = {
  260. 'container': container,
  261. 'current_location': current_location,
  262. 'month': timezone.now().strftime('%Y%m'),
  263. 'target_location': target_location,
  264. 'tasktype': 'inbound',
  265. 'status': 103,
  266. 'is_delete': False
  267. }
  268. # 生成唯一递增的 taskid
  269. last_task = ContainerWCSModel.objects.filter(
  270. month=data_tosave['month'],
  271. tasktype='inbound'
  272. ).order_by('-taskid').first()
  273. if last_task:
  274. last_id = int(last_task.taskid.split('-')[-1])
  275. new_id = f"{last_id + 1:04}"
  276. else:
  277. new_id = "0001"
  278. data_tosave['taskid'] = f"inbound-{data_tosave['month']}-{new_id}"
  279. logger.info(f"生成入库任务: {data_tosave['taskid']}")
  280. # 每月生成唯一递增的 taskNumber
  281. data_tosave['tasknumber'] = f"{data_tosave['month']}{new_id}"
  282. ContainerWCSModel.objects.create(**data_tosave)
  283. def update_container_wcs(self, request, *args, **kwargs):
  284. data = self.request.data
  285. container = data.get('container_number')
  286. current_location = data.get('current_location')
  287. logger.info(f"请求托盘:{container},请求位置:{current_location}")
  288. if current_location =="203":
  289. current_location ="in1"
  290. elif current_location=="103":
  291. current_location="in2"
  292. data_return = {}
  293. try:
  294. container_obj = ContainerListModel.objects.filter(container_code=container).first()
  295. if not container_obj:
  296. data_return = {
  297. 'code': '400',
  298. 'message': '托盘编码不存在',
  299. 'data': data
  300. }
  301. return Response(data_return, status=status.HTTP_400_BAD_REQUEST)
  302. # 更新容器数据(部分更新)
  303. serializer = ContainerListPostSerializer(
  304. container_obj,
  305. data=data,
  306. partial=True # 允许部分字段更新
  307. )
  308. serializer.is_valid(raise_exception=True)
  309. serializer.save()
  310. # 检查是否已在目标位置
  311. if current_location == str(container_obj.target_location):
  312. logger.info(f"托盘 {container} 已在目标位置")
  313. data_return = {
  314. 'code': '200',
  315. 'message': '当前位置已是目标位置',
  316. 'data': data
  317. }
  318. allocator = LocationAllocation() # 创建实例
  319. location_row = int(container_obj.target_location.split('-')[1])
  320. location_col = int(container_obj.target_location.split('-')[2])
  321. location_layer = int(container_obj.target_location.split('-')[3])
  322. coordinate = f"{location_row}-{location_col}-{location_layer}"
  323. print(f"坐标:{coordinate}")
  324. location_code = LocationModel.objects.filter(coordinate=coordinate).first().location_code
  325. container_code = container
  326. update_location_status = allocator.update_location_status(location_code, 'occupied') # 更新库位状态
  327. if not update_location_status:
  328. print("❌ 库位状态更新失败,请检查托盘编码")
  329. return
  330. print(f"[6]WCS到位,库位状态更新成功!")
  331. update_location_container_link = allocator.update_location_container_link(location_code, container_code) # 更新库位和托盘的关联关系
  332. if not update_location_container_link:
  333. print("❌ 库位和托盘的关联关系更新失败,请检查托盘编码")
  334. return
  335. print(f"[7]库位和托盘的关联关系更新成功!")
  336. else:
  337. current_task = ContainerWCSModel.objects.filter(
  338. container=container,
  339. tasktype='inbound'
  340. ).first()
  341. if current_task:
  342. data_return = {
  343. 'code': '200',
  344. 'message': '任务已存在,重新下发',
  345. 'data': current_task.to_dict()
  346. }
  347. else:
  348. # 库位分配
  349. container_code = container
  350. print(f"开始生成库位,托盘编码:{container_code}")
  351. allocator = LocationAllocation() # 创建实例
  352. location_list_cnumber = allocator.get_location_by_status(container_code, current_location) # 获取库位列表
  353. if not location_list_cnumber:
  354. print("❌ 通用库位获取失败,请检查托盘编码")
  355. return
  356. print(f"[1]库位:{location_list_cnumber}")
  357. update_location_status = allocator.update_location_status(location_list_cnumber.location_code, 'reserved') # 更新库位状态
  358. if not update_location_status:
  359. print("❌ 库位状态更新失败,请检查托盘编码")
  360. return
  361. print(f"[2]发送任务,库位状态更新成功!")
  362. update_location_group_status = allocator.update_location_group_status(location_list_cnumber.location_code) # 更新库位组状态
  363. if not update_location_group_status:
  364. print("❌ 库位组状态更新失败,请检查托盘编码")
  365. return
  366. print(f"[3]库位组状态更新成功!")
  367. update_batch_status = allocator.update_batch_status(container_code, '2') # 更新批次状态
  368. if not update_batch_status:
  369. print("❌ 批次状态更新失败,请检查批次号")
  370. return
  371. print(f"[4]批次状态更新成功!")
  372. update_location_group_batch = allocator.update_location_group_batch(location_list_cnumber, container_code) # 更新库位组的批次
  373. if not update_location_group_batch:
  374. print("❌ 库位组批次更新失败,请检查托盘编码")
  375. return
  376. print(f"[5]库位组批次更新成功!")
  377. update_location_container_link = allocator.update_location_container_link(location_list_cnumber.location_code, container_code) # 更新库位和托盘的关联关系
  378. if not update_location_container_link:
  379. print("❌ 库位和托盘的关联关系更新失败,请检查托盘编码")
  380. return
  381. print(f"[7]库位和托盘的关联关系更新成功!")
  382. # update_location_status = allocator.update_location_status(location_list_cnumber.location_code, 'occupied') # 更新库位状态
  383. # if not update_location_status:
  384. # print("❌ 库位状态更新失败,请检查托盘编码")
  385. # return
  386. # print(f"[6]WCS到位,库位状态更新成功!")
  387. # update_location_container_link = allocator.update_location_container_link(location_list_cnumber.location_code, container_code) # 更新库位和托盘的关联关系
  388. # if not update_location_container_link:
  389. # print("❌ 库位和托盘的关联关系更新失败,请检查托盘编码")
  390. # return
  391. # print(f"[7]库位和托盘的关联关系更新成功!")
  392. allocation_target_location = (
  393. location_list_cnumber.warehouse_code + '-'
  394. + f"{int(location_list_cnumber.row):02d}" + '-' # 关键修改点
  395. + f"{int(location_list_cnumber.col):02d}" + '-'
  396. + f"{int(location_list_cnumber.layer):02d}"
  397. )
  398. self.generate_task(container, current_location, allocation_target_location) # 生成任务
  399. current_task = ContainerWCSModel.objects.get(
  400. container=container,
  401. tasktype='inbound'
  402. )
  403. data_return = {
  404. 'code': '200',
  405. 'message': '任务下发成功',
  406. 'data': current_task.to_dict()
  407. }
  408. container_obj.target_location = allocation_target_location
  409. container_obj.save()
  410. self.inport_update_task(current_task.id, container_obj.id)
  411. http_status = status.HTTP_200_OK if data_return['code'] == '200' else status.HTTP_400_BAD_REQUEST
  412. return Response(data_return, status=http_status)
  413. except Exception as e:
  414. logger.error(f"处理请求时发生错误: {str(e)}", exc_info=True)
  415. return Response(
  416. {'code': '500', 'message': '服务器内部错误', 'data': None},
  417. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  418. )
  419. @transaction.atomic
  420. def inport_update_task(self, wcs_id,container_id):
  421. try:
  422. task_obj = ContainerWCSModel.objects.filter(id=wcs_id).first()
  423. if task_obj:
  424. container_detail_obj = ContainerDetailModel.objects.filter(container=container_id).all()
  425. if container_detail_obj:
  426. for detail in container_detail_obj:
  427. # 保存到数据库
  428. batch = BoundDetailModel.objects.filter(bound_batch_id=detail.batch.id).first()
  429. TaskModel.objects.create(
  430. task_wcs = task_obj,
  431. container_detail = detail,
  432. batch_detail = batch
  433. )
  434. logger.info(f"入库任务 {wcs_id} 已更新")
  435. else:
  436. logger.info(f"入库任务 {container_id} 批次不存在")
  437. else:
  438. logger.info(f"入库任务 {wcs_id} 不存在")
  439. except Exception as e:
  440. logger.error(f"处理入库任务时发生错误: {str(e)}", exc_info=True)
  441. return Response(
  442. {'code': '500', 'message': '服务器内部错误', 'data': None},
  443. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  444. )
  445. # PDA组盘入库 将扫描到的托盘编码和批次信息保存到数据库
  446. # 1. 先查询托盘对象,如果不存在,则创建托盘对象
  447. # 2. 循环处理每个批次,查询批次对象,
  448. # 3. 更新批次数据(根据业务规则)
  449. # 4. 保存到数据库
  450. # 5. 保存操作记录到数据库
  451. class ContainerDetailViewSet(viewsets.ModelViewSet):
  452. """
  453. retrieve:
  454. Response a data list(get)
  455. list:
  456. Response a data list(all)
  457. create:
  458. Create a data line(post)
  459. delete:
  460. Delete a data line(delete)
  461. """
  462. # authentication_classes = [] # 禁用所有认证类
  463. # permission_classes = [AllowAny] # 允许任意访问
  464. pagination_class = MyPageNumberPagination
  465. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  466. ordering_fields = ['id', "create_time", "update_time", ]
  467. filter_class = ContainerDetailFilter
  468. def get_project(self):
  469. try:
  470. id = self.kwargs.get('pk')
  471. return id
  472. except:
  473. return None
  474. def get_queryset(self):
  475. id = self.get_project()
  476. if self.request.user:
  477. if id is None:
  478. return ContainerDetailModel.objects.filter( is_delete=False)
  479. else:
  480. return ContainerDetailModel.objects.filter( id=id, is_delete=False)
  481. else:
  482. return ContainerDetailModel.objects.none()
  483. def get_serializer_class(self):
  484. if self.action in ['list', 'destroy','retrieve']:
  485. return ContainerDetailGetSerializer
  486. elif self.action in ['create', 'update']:
  487. return ContainerDetailPostSerializer
  488. else:
  489. return self.http_method_not_allowed(request=self.request)
  490. def create(self, request, *args, **kwargs):
  491. data = self.request.data
  492. order_month = str(timezone.now().strftime('%Y%m'))
  493. data['month'] = order_month
  494. container_code = data.get('container')
  495. batches = data.get('batches', []) # 确保有默认空列表
  496. print('扫描到的托盘编码', container_code)
  497. # 处理托盘对象
  498. container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
  499. if container_obj:
  500. data['container'] = container_obj.id
  501. logger.info(f"托盘 {container_code} 已存在")
  502. else:
  503. logger.info(f"托盘 {container_code} 不存在,创建托盘对象")
  504. serializer_list = ContainerListPostSerializer(data={'container_code': container_code})
  505. serializer_list.is_valid(raise_exception=True)
  506. serializer_list.save()
  507. data['container'] = serializer_list.data.get('id')
  508. # 循环处理每个批次
  509. for batch in batches:
  510. bound_number = batch.get('goods_code')
  511. goods_qty = batch.get('goods_qty')
  512. # 查询商品对象
  513. bound_obj = BoundBatchModel.objects.filter(bound_number=bound_number).first()
  514. if not bound_obj:
  515. # 如果商品不存在,返回错误,这里暂时在程序中进行提醒,后续需要改为前端弹窗提醒
  516. logger.error(f"批次 {bound_number} 不存在")
  517. # 跳出此次循环
  518. continue
  519. # return Response({"error": f"商品编码 {bound_number} 不存在"}, status=400)
  520. # 3. 更新批次数据(根据业务规则)
  521. try:
  522. last_qty = bound_obj.goods_in_qty
  523. bound_obj.goods_in_qty += batch.get("goods_qty", 0)
  524. if bound_obj.goods_in_qty >= bound_obj.goods_qty:
  525. bound_obj.goods_in_qty = bound_obj.goods_qty
  526. bound_obj.status = 1 # 批次状态为组盘完成
  527. print('批次id',bound_obj.id)
  528. bound_detail_obj = BoundDetailModel.objects.filter(bound_batch=bound_obj.id).first()
  529. if bound_detail_obj:
  530. bound_detail_obj.status = 1
  531. bound_detail_obj.save()
  532. print('入库申请id',bound_detail_obj.bound_list_id)
  533. # 入库申请全部批次入库完成
  534. bound_batch_all = BoundDetailModel.objects.filter(bound_list=bound_detail_obj.bound_list_id).all()
  535. if bound_batch_all.count() == bound_batch_all.filter(status=1).count():
  536. bound_list_obj = BoundListModel.objects.filter(id=bound_detail_obj.bound_list_id).first()
  537. print('当前状态',bound_list_obj.bound_status)
  538. bound_list_obj.bound_status = 102
  539. print('更新状态',bound_list_obj.bound_status)
  540. bound_list_obj.save()
  541. print('入库申请全部批次组盘完成')
  542. else:
  543. print('入库申请部分批次组盘完成')
  544. else:
  545. bound_obj.status = 0
  546. bound_obj.save() # 保存到数据库
  547. # 创建托盘详情记录(每个批次独立)
  548. print('新增个数',bound_obj.goods_in_qty-last_qty)
  549. if bound_obj.goods_in_qty-last_qty == goods_qty:
  550. detail_data = {
  551. "container": data['container'], # 托盘ID
  552. "batch": bound_obj.id, # 外键关联批次
  553. "goods_code": bound_obj.goods_code,
  554. "goods_desc": bound_obj.goods_desc,
  555. "goods_qty": goods_qty,
  556. "goods_weight": bound_obj.goods_weight,
  557. "status": 1,
  558. "month": data['month'],
  559. "creater": data.get('creater', 'zl') # 默认值兜底
  560. }
  561. serializer = self.get_serializer(data=detail_data)
  562. serializer.is_valid(raise_exception=True)
  563. serializer.save() # 必须保存到数据库
  564. operate_data = {
  565. "month" : data['month'],
  566. "container": data['container'], # 托盘ID
  567. "operation_type" : 'container',
  568. "batch" : bound_obj.id, # 外键关联批次
  569. "goods_code": bound_obj.goods_code,
  570. "goods_desc": bound_obj.goods_desc,
  571. "goods_qty": goods_qty,
  572. "goods_weight": bound_obj.goods_weight,
  573. "operator": data.get('creater', 'zl'), # 默认值兜底
  574. "timestamp": timezone.now(),
  575. "from_location": "container",
  576. "to_location": "container",
  577. "memo": "入库PDA组盘,pda入库"+str(bound_obj.goods_code)+"数量"+str(goods_qty)
  578. }
  579. serializer_operate = ContainerOperationPostSerializer(data=operate_data)
  580. serializer_operate.is_valid(raise_exception=True)
  581. serializer_operate.save() # 必须保存到数据库
  582. elif bound_obj.goods_in_qty-last_qty > 0:
  583. print('批次数量不一致')
  584. detail_data = {
  585. "container": data['container'], # 托盘ID
  586. "batch": bound_obj.id, # 外键关联批次
  587. "goods_code": bound_obj.goods_code,
  588. "goods_desc": bound_obj.goods_desc,
  589. "goods_qty": bound_obj.goods_in_qty-last_qty,
  590. "goods_weight": bound_obj.goods_weight,
  591. "status": 1,
  592. "month": data['month'],
  593. "creater": data.get('creater', 'zl') # 默认值兜底
  594. }
  595. serializer = self.get_serializer(data=detail_data)
  596. serializer.is_valid(raise_exception=True)
  597. serializer.save() # 必须保存到数据库
  598. operate_data = {
  599. "month" : data['month'],
  600. "container": data['container'], # 托盘ID
  601. "operation_type" : 'container',
  602. "batch" : bound_obj.id, # 外键关联批次
  603. "goods_code": bound_obj.goods_code,
  604. "goods_desc": bound_obj.goods_desc,
  605. "goods_qty": bound_obj.goods_in_qty-last_qty,
  606. "goods_weight": bound_obj.goods_weight,
  607. "operator": data.get('creater', 'zl'), # 默认值兜底
  608. "timestamp": timezone.now(),
  609. "from_location": "container",
  610. "to_location": "container",
  611. "memo": "入库PDA组盘,(数量不一致)pda入库"+str(bound_obj.goods_code)+"数量"+str(goods_qty)
  612. }
  613. serializer_operate = ContainerOperationPostSerializer(data=operate_data)
  614. serializer_operate.is_valid(raise_exception=True)
  615. serializer_operate.save() # 必须保存到数据库
  616. else :
  617. print('重复组盘')
  618. except Exception as e:
  619. print(f"更新批次 {bound_number} 失败: {str(e)}")
  620. continue
  621. # 将处理后的数据返回(或根据业务需求保存到数据库)
  622. res_data={
  623. "code": "200",
  624. "msg": "Success Create",
  625. "data": data
  626. }
  627. return Response(res_data, status=200)
  628. def update(self, request, pk):
  629. qs = self.get_object()
  630. data = self.request.data
  631. serializer = self.get_serializer(qs, data=data)
  632. serializer.is_valid(raise_exception=True)
  633. serializer.save()
  634. headers = self.get_success_headers(serializer.data)
  635. return Response(serializer.data, status=200, headers=headers)
  636. class ContainerOperateViewSet(viewsets.ModelViewSet):
  637. """
  638. retrieve:
  639. Response a data list(get)
  640. list:
  641. Response a data list(all)
  642. create:
  643. Create a data line(post)
  644. delete:
  645. Delete a data line(delete)
  646. """
  647. # authentication_classes = [] # 禁用所有认证类
  648. # permission_classes = [AllowAny] # 允许任意访问
  649. pagination_class = MyPageNumberPagination
  650. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  651. ordering_fields = ['id', "timestamp" ]
  652. filter_class = ContainerOperationFilter
  653. def get_project(self):
  654. try:
  655. id = self.kwargs.get('pk')
  656. return id
  657. except:
  658. return None
  659. def get_queryset(self):
  660. id = self.get_project()
  661. if self.request.user:
  662. if id is None:
  663. return ContainerOperationModel.objects.filter( is_delete=False)
  664. else:
  665. return ContainerOperationModel.objects.filter( id=id, is_delete=False)
  666. else:
  667. return ContainerOperationModel.objects.none()
  668. def get_serializer_class(self):
  669. if self.action in ['list', 'destroy','retrieve']:
  670. return ContainerOperationGetSerializer
  671. elif self.action in ['create', 'update']:
  672. return ContainerOperationPostSerializer
  673. else:
  674. return self.http_method_not_allowed(request=self.request)
  675. def create(self, request, *args, **kwargs):
  676. data = self.request.data
  677. serializer = self.get_serializer(data=data)
  678. serializer.is_valid(raise_exception=True)
  679. serializer.save()
  680. headers = self.get_success_headers(serializer.data)
  681. return Response(serializer.data, status=200, headers=headers)
  682. def update(self, request, pk):
  683. qs = self.get_object()
  684. data = self.request.data
  685. serializer = self.get_serializer(qs, data=data)
  686. serializer.is_valid(raise_exception=True)
  687. serializer.save()
  688. headers = self.get_success_headers(serializer.data)
  689. return Response(serializer.data, status=200, headers=headers)
  690. class OutTaskViewSet(viewsets.ModelViewSet):
  691. """
  692. # fun:get_out_task:下发出库任务
  693. # fun:get_batch_count_by_boundlist:获取出库申请下的批次数量
  694. """
  695. # authentication_classes = [] # 禁用所有认证类
  696. # permission_classes = [AllowAny] # 允许任意访问
  697. def get_out_task(self, request, *args, **kwargs):
  698. try:
  699. data = self.request.data
  700. logger.info(f"收到 WMS 推送数据: {data}")
  701. send_data= {
  702. "container": "12345",
  703. "current_location": "in1",
  704. "month": 202503,
  705. "current_location": "W01-01-01-01",
  706. "target_location": "W01-01-01-01",
  707. "tasktype": "inbound",
  708. "taskid": "inbound-202503-0001",
  709. "message": "生成入库任务",
  710. "status": 103
  711. }
  712. post_json_to_url("http://127.0.0.1:8008/container/batch/", send_data)
  713. return Response({"code": "200", "msg": "Success"}, status=200)
  714. except Exception as e:
  715. return Response({"code": "500", "msg": str(e)}, status=500)
  716. def get_batch_count_by_boundlist(self,bound_list_id):
  717. try:
  718. bound_list_obj_all = OutBoundDetailModel.objects.filter(bound_list=bound_list_id).all()
  719. if bound_list_obj_all:
  720. logger.info(f"查询批次数量成功: {bound_list_obj_all.count()}")
  721. batch_count_dict = {}
  722. # 统计批次数量(创建哈希表,去重)
  723. for batch in bound_list_obj_all:
  724. if batch.bound_batch_number_id not in batch_count_dict:
  725. batch_count_dict[batch.bound_batch_number_id] = batch.bound_batch.goods_out_qty
  726. else:
  727. batch_count_dict[batch.bound_batch_number_id] += batch.bound_batch.goods_out_qty
  728. return batch_count_dict
  729. else:
  730. logger.error(f"查询批次数量失败: {bound_list_id} 不存在")
  731. return {}
  732. except Exception as e:
  733. logger.error(f"查询批次数量失败: {str(e)}")
  734. return {}
  735. def get_location_by_status_and_batch(self,status,bound_id):
  736. try:
  737. container_obj = ContainerDetailModel.objects.filter(batch=bound_id,status=status).all()
  738. if container_obj:
  739. logger.info(f"查询{status}状态的批次数量成功: {container_obj.count()}")
  740. container_dict = {}
  741. # 统计托盘数量(创建哈希表,去重)
  742. for obj in container_obj:
  743. if obj.container_id not in container_dict:
  744. container_dict[obj.container_id] = obj.goods_qty
  745. else:
  746. container_dict[obj.container_id] += obj.goods_qty
  747. return container_dict
  748. else:
  749. logger.error(f"查询{status}状态的批次数量失败: {bound_id} 不存在")
  750. return {}
  751. except Exception as e:
  752. logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
  753. return {}
  754. def get_order_by_batch(self,container_list):
  755. try:
  756. container_dict = {}
  757. for container in container_list:
  758. location_container = LocationContainerLink.objects.filter(container_id=container,is_active=True).first()
  759. if location_container:
  760. location_c_number = location_container.location.c_number
  761. if container not in container_dict:
  762. container_dict[container] = {
  763. "container_number":container,
  764. "location_c_number":location_c_number,
  765. "location_id ":location_container.location.id,
  766. "location_type":location_container.location.location_type
  767. }
  768. if len(container_dict.keys()) == len(container_list):
  769. return container_dict
  770. else:
  771. logger.error(f"查询批次数量失败: {container_list} 不存在")
  772. return {}
  773. except Exception as e:
  774. logger.error(f"查询批次数量失败: {str(e)}")
  775. return {}
  776. except Exception as e:
  777. logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
  778. return {}
  779. def generate_location_by_demand(self,demand_list):
  780. # demand_list {1: 25, 2: 17}
  781. try:
  782. return_location =[]
  783. for demand_id, demand_qty in demand_list.items():
  784. container_list = self.get_location_by_status_and_batch(1, demand_id)
  785. if not container_list:
  786. return {"code": "500", "msg": f"批次 {demand_id} 不存在"}
  787. container_id_list = container_list.keys()
  788. container_order = self.get_order_by_batch(container_id_list)
  789. if not container_order:
  790. return {"code": "500", "msg": f"托盘 {container_id_list} 不存在"}
  791. order = sorted(
  792. container_order.values(),
  793. key=lambda x: (
  794. int(x['location_type'][-1]), # 提取最后一位数字并转为整数
  795. -x['location_c_number'] # 按location_c_number降序
  796. )
  797. )
  798. current_qty = 0
  799. for container in order:
  800. container_detail_obj = ContainerDetailModel.objects.filter(container_id=container['container_number'],batch_id=demand_id,status=1).all()
  801. if not container_detail_obj:
  802. return {"code": "500", "msg": f"托盘上无该批次,请检查{container['container_number']} 不存在"}
  803. goods_qty = 0
  804. for obj in container_detail_obj:
  805. goods_qty += obj.goods_qty
  806. if current_qty < demand_qty:
  807. current_qty += goods_qty
  808. return_location.append(container)
  809. else:
  810. break
  811. return {"code": "200", "msg": "Success", "data": return_location}
  812. except Exception as e:
  813. return {"code": "500", "msg": str(e)}
  814. class BatchViewSet(viewsets.ModelViewSet):
  815. authentication_classes = [] # 禁用所有认证类
  816. permission_classes = [AllowAny] # 允许任意访问
  817. def wcs_post(self, request, *args, **kwargs):
  818. data = self.request.data
  819. logger.info(f"收到 WMS 推送数据: {data}")
  820. return Response({"code": "200", "msg": "Success"}, status=200)
  821. def post_json_to_url(url, data):
  822. """
  823. 发送 JSON 数据到指定 URL
  824. :param url: 目标地址 (e.g., "https://api.example.com/endpoint")
  825. :param data: 要发送的字典数据 (e.g., {"key": "value"})
  826. :return: 响应内容 (字典) 或错误信息
  827. """
  828. headers = {
  829. 'Content-Type': 'application/json',
  830. # 可选:添加认证头 (如 Token)
  831. # 'Authorization': f'Bearer {settings.API_TOKEN}'
  832. }
  833. try:
  834. # 发送 POST 请求
  835. response = requests.post(
  836. url,
  837. data=json.dumps(data), # 序列化为 JSON 字符串
  838. headers=headers,
  839. timeout=10 # 超时时间(秒)
  840. )
  841. # 检查 HTTP 状态码
  842. response.raise_for_status()
  843. # 解析 JSON 响应
  844. return response.json()
  845. except requests.exceptions.RequestException as e:
  846. # 处理网络错误、超时等
  847. return {"error": str(e)}
  848. except json.JSONDecodeError:
  849. # 处理响应非 JSON 格式的情况
  850. return {"error": "Invalid JSON response"}