views.py 106 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539
  1. from wsgiref import headers
  2. from rest_framework.viewsets import ViewSet
  3. from rest_framework import viewsets
  4. from utils.page import MyPageNumberPagination
  5. from django.db.models import Prefetch
  6. from rest_framework.filters import OrderingFilter
  7. from django_filters.rest_framework import DjangoFilterBackend
  8. from rest_framework.response import Response
  9. from django.db.models import F, Case, When
  10. from django.db.models import OuterRef, Subquery
  11. from django.utils import timezone
  12. import requests
  13. from django.db import transaction
  14. import logging
  15. from rest_framework import status
  16. from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,ContainerWCSModel,TaskModel,out_batch_detail,ContainerDetailLogModel,batchLogModel
  17. from bound.models import BoundDetailModel,BoundListModel,OutBoundDetailModel
  18. from bin.views import LocationAllocation,base_location
  19. from bin.models import LocationModel,LocationContainerLink,LocationGroupModel
  20. from bound.models import BoundBatchModel,OutBatchModel,BatchOperateLogModel
  21. from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSerializer,ContainerDetailSimpleGetSerializer
  22. from .serializers import ContainerListGetSerializer,ContainerListPostSerializer
  23. from .serializers import ContainerOperationGetSerializer,ContainerOperationPostSerializer
  24. from .serializers import TaskGetSerializer,TaskPostSerializer
  25. from .serializers import WCSTaskGetSerializer
  26. from .serializers import OutBoundFullDetailSerializer,OutBoundDetailSerializer
  27. from .serializers import ContainerDetailLogSerializer
  28. from .serializers import batchLogModelSerializer
  29. from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter,WCSTaskFilter,ContainerDetailLogFilter,batchLogFilter
  30. from rest_framework.permissions import AllowAny
  31. import threading
  32. from django.db import close_old_connections
  33. from bin.services import AllocationService
  34. from collections import defaultdict
  35. from django.db.models import Sum
  36. from staff.models import ListModel as StaffListModel
  37. logger = logging.getLogger(__name__)
  38. # 托盘流水汇总批次流水
  39. class batchLogModelViewSet(viewsets.ModelViewSet):
  40. """
  41. retrieve:
  42. Response a data list(get)
  43. list:
  44. Response a data list(all)
  45. create:
  46. Create a data line(post)
  47. delete:
  48. Delete a data line(delete)
  49. """
  50. pagination_class = MyPageNumberPagination
  51. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  52. ordering_fields = ['id', "create_time", "update_time", ]
  53. filter_class = batchLogFilter
  54. def get_project(self):
  55. try:
  56. id = self.kwargs.get('pk')
  57. return id
  58. except:
  59. return None
  60. def get_queryset(self):
  61. id = self.get_project()
  62. if self.request.user:
  63. if id is None:
  64. return batchLogModel.objects.filter()
  65. else:
  66. return batchLogModel.objects.filter(id=id)
  67. else:
  68. return batchLogModel.objects.none()
  69. def get_serializer_class(self):
  70. if self.action in ['list', 'destroy','retrieve']:
  71. return batchLogModelSerializer
  72. else:
  73. return self.http_method_not_allowed(request=self.request)
  74. def create(self, request, *args, **kwargs):
  75. data = self.request.data
  76. return Response(data, status=200, headers=headers)
  77. def update(self, request, pk):
  78. qs = self.get_object()
  79. data = self.request.data
  80. serializer = self.get_serializer(qs, data=data)
  81. serializer.is_valid(raise_exception=True)
  82. serializer.save()
  83. headers = self.get_success_headers(serializer.data)
  84. return Response(serializer.data, status=200, headers=headers)
  85. def get_container_operation_log(self,request):
  86. batchlog_id = self.request.query_params.get('batchlog_id')
  87. batch_obj = batchLogModel.objects.get(id=batchlog_id)
  88. container_operation_log = batch_obj.detail_logs.all()
  89. serializer = ContainerDetailLogSerializer(container_operation_log, many=True)
  90. return Response(serializer.data, status=200)
  91. # 进出库log查看
  92. class ContainerDetailLogModelViewSet(viewsets.ModelViewSet):
  93. """
  94. retrieve:
  95. Response a data list(get)
  96. list:
  97. Response a data list(all)
  98. create:
  99. Create a data line(post)
  100. delete:
  101. Delete a data line(delete)
  102. """
  103. # authentication_classes = [] # 禁用所有认证类
  104. # permission_classes = [AllowAny] # 允许任意访问
  105. pagination_class = MyPageNumberPagination
  106. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  107. ordering_fields = ['id', "create_time", "update_time", ]
  108. filter_class = ContainerDetailLogFilter
  109. def get_project(self):
  110. try:
  111. id = self.kwargs.get('pk')
  112. return id
  113. except:
  114. return None
  115. def get_queryset(self):
  116. id = self.get_project()
  117. if self.request.user:
  118. if id is None:
  119. return ContainerDetailLogModel.objects.filter()
  120. else:
  121. return ContainerDetailLogModel.objects.filter(id=id)
  122. else:
  123. return ContainerDetailLogModel.objects.none()
  124. def get_serializer_class(self):
  125. if self.action in ['list', 'destroy','retrieve']:
  126. return ContainerDetailLogSerializer
  127. else:
  128. return self.http_method_not_allowed(request=self.request)
  129. def create(self, request, *args, **kwargs):
  130. data = self.request.data
  131. return Response(data, status=200, headers=headers)
  132. def update(self, request, pk):
  133. qs = self.get_object()
  134. data = self.request.data
  135. serializer = self.get_serializer(qs, data=data)
  136. serializer.is_valid(raise_exception=True)
  137. serializer.save()
  138. headers = self.get_success_headers(serializer.data)
  139. return Response(serializer.data, status=200, headers=headers)
  140. # 托盘列表视图
  141. class ContainerListViewSet(viewsets.ModelViewSet):
  142. """
  143. retrieve:
  144. Response a data list(get)
  145. list:
  146. Response a data list(all)
  147. create:
  148. Create a data line(post)
  149. delete:
  150. Delete a data line(delete)
  151. """
  152. # authentication_classes = [] # 禁用所有认证类
  153. # permission_classes = [AllowAny] # 允许任意访问
  154. pagination_class = MyPageNumberPagination
  155. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  156. ordering_fields = ['id', "create_time", "update_time", ]
  157. filter_class = ContainerListFilter
  158. def get_project(self):
  159. try:
  160. id = self.kwargs.get('pk')
  161. return id
  162. except:
  163. return None
  164. def get_queryset(self):
  165. id = self.get_project()
  166. if self.request.user:
  167. if id is None:
  168. return ContainerListModel.objects.filter()
  169. else:
  170. return ContainerListModel.objects.filter(id=id)
  171. else:
  172. return ContainerListModel.objects.none()
  173. def get_serializer_class(self):
  174. if self.action in ['list', 'destroy','retrieve']:
  175. return ContainerListGetSerializer
  176. elif self.action in ['create', 'update']:
  177. return ContainerListPostSerializer
  178. else:
  179. return self.http_method_not_allowed(request=self.request)
  180. def create(self, request, *args, **kwargs):
  181. # 创建托盘:托盘码五位数字(唯一),当前库位,目标库位,状态,最后操作时间
  182. container_all = ContainerListModel.objects.all().order_by('container_code')
  183. if container_all.count() == 0:
  184. container_code = 12345
  185. else:
  186. container_code = container_all.last().container_code + 1
  187. container_obj = ContainerListModel.objects.create(
  188. container_code=container_code,
  189. current_location='N/A',
  190. target_location='N/A',
  191. status=0,
  192. last_operation=timezone.now()
  193. )
  194. serializer = ContainerListGetSerializer(container_obj)
  195. headers = self.get_success_headers(serializer.data)
  196. return Response(serializer.data, status=201, headers=headers)
  197. def update(self, request, pk):
  198. qs = self.get_object()
  199. data = self.request.data
  200. serializer = self.get_serializer(qs, data=data)
  201. serializer.is_valid(raise_exception=True)
  202. serializer.save()
  203. headers = self.get_success_headers(serializer.data)
  204. return Response(serializer.data, status=200, headers=headers)
  205. def check_container_postion(self, request, *args, **kwargs):
  206. # 获取查询集
  207. container_list = ContainerListModel.objects.exclude(current_location=F('target_location'))
  208. # 手动应用分页
  209. page = self.paginate_queryset(container_list)
  210. if page is not None:
  211. serializer = ContainerListGetSerializer(page, many=True)
  212. return self.get_paginated_response(serializer.data)
  213. # 如果没有分页,返回完整结果(不推荐)
  214. serializer = ContainerListGetSerializer(container_list, many=True)
  215. return Response(serializer.data, status=200)
  216. # wcs任务视图
  217. class WCSTaskViewSet(viewsets.ModelViewSet):
  218. """
  219. retrieve:
  220. Response a data list(get)
  221. list:
  222. Response a data list(all)
  223. create:
  224. Create a data line(post)
  225. delete:
  226. Delete a data line(delete)
  227. """
  228. # authentication_classes = [] # 禁用所有认证类
  229. # permission_classes = [AllowAny] # 允许任意访问
  230. pagination_class = MyPageNumberPagination
  231. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  232. ordering_fields = ['-id', "-create_time", "update_time", ]
  233. filter_class = WCSTaskFilter
  234. def get_project(self):
  235. try:
  236. id = self.kwargs.get('pk')
  237. return id
  238. except:
  239. return None
  240. def get_queryset(self):
  241. id = self.get_project()
  242. if self.request.user:
  243. if id is None:
  244. return ContainerWCSModel.objects.filter()
  245. else:
  246. return ContainerWCSModel.objects.filter(id=id)
  247. else:
  248. return ContainerWCSModel.objects.none()
  249. def get_serializer_class(self):
  250. if self.action in ['list', 'destroy','retrieve']:
  251. return WCSTaskGetSerializer
  252. else:
  253. return self.http_method_not_allowed(request=self.request)
  254. def send_task_to_wcs(self, request, *args, **kwargs):
  255. data = self.request.data
  256. task_id = data.get('taskid')
  257. logger.info(f"请求任务:{task_id}")
  258. data_return = {}
  259. try:
  260. task_obj = ContainerWCSModel.objects.get(id=task_id)
  261. if not task_obj:
  262. data_return = {
  263. 'code': '400',
  264. 'message': '任务不存在',
  265. 'data': data
  266. }
  267. return Response(data_return, status=status.HTTP_400_BAD_REQUEST)
  268. if task_obj.working == 1:
  269. OutboundService.send_task_to_wcs(task_obj)
  270. data_return = {
  271. 'code': '200',
  272. 'message': '任务已在执行中,再次下发',
  273. 'data': data
  274. }
  275. else:
  276. data_return = {
  277. 'code': '200',
  278. 'message': '任务已执行完成',
  279. 'data': data
  280. }
  281. except ContainerWCSModel.DoesNotExist:
  282. data_return = {
  283. 'code': '404',
  284. 'message': '任务不存在',
  285. 'data': data
  286. }
  287. return Response(data_return, status=status.HTTP_200_OK)
  288. # 入库任务视图
  289. class TaskViewSet(viewsets.ModelViewSet):
  290. """
  291. retrieve:
  292. Response a data list(get)
  293. list:
  294. Response a data list(all)
  295. create:
  296. Create a data line(post)
  297. delete:
  298. Delete a data line(delete)
  299. """
  300. pagination_class = MyPageNumberPagination
  301. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  302. ordering_fields = ['id', "create_time", "update_time", ]
  303. filter_class = TaskFilter
  304. def get_project(self):
  305. try:
  306. id = self.kwargs.get('pk')
  307. return id
  308. except:
  309. return None
  310. def get_queryset(self):
  311. id = self.get_project()
  312. if self.request.user:
  313. if id is None:
  314. return TaskModel.objects.filter()
  315. else:
  316. return TaskModel.objects.filter(id=id)
  317. else:
  318. return TaskModel.objects.none()
  319. def get_serializer_class(self):
  320. if self.action in ['list', 'destroy','retrieve']:
  321. return TaskGetSerializer
  322. elif self.action in ['create', 'update']:
  323. return TaskPostSerializer
  324. else:
  325. return self.http_method_not_allowed(request=self.request)
  326. def create(self, request, *args, **kwargs):
  327. data = self.request.data
  328. return Response(data, status=200, headers=headers)
  329. def update(self, request, pk):
  330. qs = self.get_object()
  331. data = self.request.data
  332. serializer = self.get_serializer(qs, data=data)
  333. serializer.is_valid(raise_exception=True)
  334. serializer.save()
  335. headers = self.get_success_headers(serializer.data)
  336. return Response(serializer.data, status=200, headers=headers)
  337. # 任务回滚
  338. class TaskRollbackMixin:
  339. def rollback_task(self, request, task_id, *args, **kwargs):
  340. """
  341. 撤销入库任务并回滚相关状态
  342. """
  343. try:
  344. # 获取任务实例并锁定数据库记录
  345. task = ContainerWCSModel.objects.select_for_update().get(taskid=task_id)
  346. container_code = task.container
  347. target_location = task.target_location
  348. batch = task.batch
  349. # 初始化库位分配器
  350. allocator = LocationAllocation()
  351. # ==================== 库位状态回滚 ====================
  352. # 解析目标库位信息(格式:仓库代码-行-列-层)
  353. try:
  354. warehouse_code, row, col, layer = target_location.split('-')
  355. location = LocationModel.objects.get(
  356. warehouse_code=warehouse_code,
  357. row=int(row),
  358. col=int(col),
  359. layer=int(layer)
  360. )
  361. # 回滚库位状态到可用状态
  362. allocator.update_location_status(location.location_code, 'available')
  363. # 更新库位组状态(需要根据实际逻辑实现)
  364. allocator.update_location_group_status(location.location_code)
  365. # 解除库位与托盘的关联
  366. allocator.update_location_container_link(location.location_code, None)
  367. # 清除库位组的批次关联
  368. allocator.update_location_group_batch(location, None)
  369. except (ValueError, LocationModel.DoesNotExist) as e:
  370. logger.error(f"库位解析失败: {str(e)}")
  371. raise Exception("关联库位信息无效")
  372. # ==================== 批次状态回滚 ====================
  373. if batch:
  374. # 将批次状态恢复为未处理状态(假设原状态为1)
  375. allocator.update_batch_status(batch.bound_number, '1')
  376. # ==================== 托盘状态回滚 ====================
  377. container_obj = ContainerListModel.objects.get(container_code=container_code)
  378. # 恢复托盘详细状态为初始状态(假设原状态为1)
  379. allocator.update_container_detail_status(container_code, 1)
  380. # 恢复托盘的目标位置为当前所在位置
  381. container_obj.target_location = task.current_location
  382. container_obj.save()
  383. # ==================== 删除任务记录 ====================
  384. task.delete()
  385. # ==================== 其他关联清理 ====================
  386. # 如果有其他关联数据(如inport_update_task的操作),在此处添加清理逻辑
  387. return Response(
  388. {'code': '200', 'message': '任务回滚成功', 'data': None},
  389. status=status.HTTP_200_OK
  390. )
  391. except ContainerWCSModel.DoesNotExist:
  392. logger.warning(f"任务不存在: {task_id}")
  393. return Response(
  394. {'code': '404', 'message': '任务不存在', 'data': None},
  395. status=status.HTTP_404_NOT_FOUND
  396. )
  397. except Exception as e:
  398. logger.error(f"任务回滚失败: {str(e)}", exc_info=True)
  399. return Response(
  400. {'code': '500', 'message': '服务器内部错误', 'data': None},
  401. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  402. )
  403. # 入库任务下发
  404. class ContainerWCSViewSet(viewsets.ModelViewSet):
  405. """
  406. retrieve:
  407. Response a data list(get)
  408. list:
  409. Response a data list(all)
  410. create:
  411. Create a data line(post)
  412. delete:
  413. Delete a data line(delete)
  414. """
  415. authentication_classes = [] # 禁用所有认证类
  416. permission_classes = [AllowAny] # 允许任意访问
  417. def generate_move_task(self, request, *args, **kwargs):
  418. data = self.request.data
  419. container = data.get('container_code')
  420. start_location = data.get('start_location')
  421. target_location = data.get('target_location')
  422. logger.info(f"请求托盘:{container},起始位置:{start_location},目标位置:{target_location}")
  423. data_return = {}
  424. try:
  425. container_obj = ContainerListModel.objects.filter(container_code=container).first()
  426. if not container_obj:
  427. data_return = {
  428. 'code': '400',
  429. 'message': '托盘编码不存在',
  430. 'data': data
  431. }
  432. return Response(data_return, status=status.HTTP_400_BAD_REQUEST)
  433. # 检查是否已在目标位置
  434. if target_location == str(container_obj.target_location) and target_location!= '203' and target_location!= '103':
  435. logger.info(f"托盘 {container} 已在目标位置")
  436. data_return = {
  437. 'code': '200',
  438. 'message': '当前位置已是目标位置',
  439. 'data': data
  440. }
  441. else:
  442. # 生成任务
  443. current_task = ContainerWCSModel.objects.filter(
  444. container=container,
  445. tasktype='inbound',
  446. working = 1,
  447. ).exclude(status=300).first()
  448. if current_task:
  449. data_return = {
  450. 'code': '200',
  451. 'message': '任务已存在,重新下发',
  452. 'data': current_task.to_dict()
  453. }
  454. else:
  455. # todo: 这里的入库操作记录里面的记录的数量不对
  456. location_min_value,allocation_target_location, batch_info = AllocationService._move_allocation(start_location, target_location,container)
  457. batch_id = batch_info['number']
  458. if batch_info['class'] == 2:
  459. self.generate_task_no_batch(container, start_location, allocation_target_location,batch_id,location_min_value.c_number)
  460. self.generate_container_operate_no_batch(container_obj, batch_id, allocation_target_location)
  461. elif batch_info['class'] == 3:
  462. self.generate_task_no_batch(container, start_location, allocation_target_location,batch_id,location_min_value.c_number)
  463. self.generate_move_container_operate(container_obj, allocation_target_location)
  464. else:
  465. self.generate_task(container, start_location, allocation_target_location,batch_id,location_min_value.c_number) # 生成任务
  466. self.generate_move_container_operate(container_obj, allocation_target_location)
  467. current_task = ContainerWCSModel.objects.get(
  468. container=container,
  469. tasktype='inbound',
  470. working=1,
  471. )
  472. OutboundService.send_task_to_wcs(current_task)
  473. data_return = {
  474. 'code': '200',
  475. 'message': '任务下发成功',
  476. 'data': current_task.to_dict()
  477. }
  478. container_obj.target_location = allocation_target_location
  479. container_obj.save()
  480. if batch_info['class'] == 1 or batch_info['class'] == 3:
  481. self.inport_update_task(current_task.id, container_obj.id)
  482. http_status = status.HTTP_200_OK if data_return['code'] == '200' else status.HTTP_400_BAD_REQUEST
  483. return Response(data_return, status=http_status)
  484. except Exception as e:
  485. logger.error(f"处理请求时发生错误: {str(e)}", exc_info=True)
  486. return Response(
  487. {'code': '500', 'message': '服务器内部错误', 'data': None},
  488. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  489. )
  490. def get_container_wcs(self, request, *args, **kwargs):
  491. data = self.request.data
  492. container = data.get('container_number')
  493. current_location = data.get('current_location')
  494. logger.info(f"请求托盘:{container},请求位置:{current_location}")
  495. data_return = {}
  496. try:
  497. container_obj = ContainerListModel.objects.filter(container_code=container).first()
  498. if not container_obj:
  499. data_return = {
  500. 'code': '400',
  501. 'message': '托盘编码不存在',
  502. 'data': data
  503. }
  504. return Response(data_return, status=status.HTTP_400_BAD_REQUEST)
  505. # 更新托盘数据(部分更新)
  506. serializer = ContainerListPostSerializer(
  507. container_obj,
  508. data=data,
  509. partial=True # 允许部分字段更新
  510. )
  511. serializer.is_valid(raise_exception=True)
  512. serializer.save()
  513. # 检查是否已在目标位置
  514. if current_location == str(container_obj.target_location) and current_location!= '203' and current_location!= '103':
  515. logger.info(f"托盘 {container} 已在目标位置")
  516. data_return = {
  517. 'code': '200',
  518. 'message': '当前位置已是目标位置',
  519. 'data': data
  520. }
  521. else:
  522. current_task = ContainerWCSModel.objects.filter(
  523. container=container,
  524. tasktype='inbound',
  525. working = 1,
  526. ).exclude(status=300).first()
  527. if current_task:
  528. data_return = {
  529. 'code': '200',
  530. 'message': '任务已存在,重新下发',
  531. 'data': current_task.to_dict()
  532. }
  533. else:
  534. # todo: 这里的入库操作记录里面的记录的数量不对
  535. location_min_value,allocation_target_location, batch_info = AllocationService.allocate(container, current_location)
  536. batch_id = batch_info['number']
  537. if batch_info['class'] == 2:
  538. self.generate_task_no_batch(container, current_location, allocation_target_location,batch_id,location_min_value.c_number)
  539. self.generate_container_operate_no_batch(container_obj, batch_id, allocation_target_location)
  540. elif batch_info['class'] == 3:
  541. self.generate_task_no_batch(container, current_location, allocation_target_location,batch_id,location_min_value.c_number)
  542. self.generate_container_operate(container_obj, allocation_target_location)
  543. else:
  544. self.generate_task(container, current_location, allocation_target_location,batch_id,location_min_value.c_number) # 生成任务
  545. self.generate_container_operate(container_obj, allocation_target_location)
  546. current_task = ContainerWCSModel.objects.get(
  547. container=container,
  548. tasktype='inbound',
  549. working=1,
  550. )
  551. data_return = {
  552. 'code': '200',
  553. 'message': '任务下发成功',
  554. 'data': current_task.to_dict()
  555. }
  556. container_obj.target_location = allocation_target_location
  557. container_obj.save()
  558. if batch_info['class'] == 1 or batch_info['class'] == 3:
  559. self.inport_update_task(current_task.id, container_obj.id)
  560. http_status = status.HTTP_200_OK if data_return['code'] == '200' else status.HTTP_400_BAD_REQUEST
  561. return Response(data_return, status=http_status)
  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 generate_container_operate(self, container_obj, bound_number,allocation_target_location):
  569. def generate_container_operate(self, container_obj, allocation_target_location):
  570. # 获取容器中所有有效的批次明细(排除已删除和状态3的)
  571. container_detaillist = ContainerDetailModel.objects.filter(
  572. container=container_obj,
  573. is_delete=False
  574. ).exclude(status=3)
  575. # 优化查询 - 预取批次对象
  576. container_detaillist = container_detaillist.select_related('batch')
  577. # 创建批次数量字典
  578. batch_totals = {}
  579. for detail in container_detaillist:
  580. batch_id = detail.batch_id
  581. if batch_id not in batch_totals:
  582. batch_totals[batch_id] = {
  583. 'obj': detail.batch, # 批次对象
  584. 'total_qty': 0
  585. }
  586. batch_totals[batch_id]['total_qty'] += detail.goods_qty
  587. # 当前月份(单次计算多次使用)
  588. current_month = int(timezone.now().strftime("%Y%m"))
  589. current_time = timezone.now()
  590. current_location = container_obj.current_location
  591. # 为每个批次创建操作记录
  592. for batch_id, data in batch_totals.items():
  593. batch_obj = data['obj']
  594. goods_qty = data['total_qty']
  595. ContainerOperationModel.objects.create(
  596. month=current_month,
  597. container=container_obj,
  598. goods_code=batch_obj.goods_code,
  599. goods_desc=batch_obj.goods_desc,
  600. operation_type="inbound",
  601. batch_id=batch_obj.id,
  602. goods_qty=goods_qty,
  603. goods_weight=goods_qty,
  604. from_location=current_location,
  605. to_location=allocation_target_location,
  606. timestamp=current_time,
  607. operator="WMS",
  608. memo=f"WCS入库: 批次: {batch_obj.bound_number}, 数量: {goods_qty}" # 使用实际容器中的数量
  609. )
  610. def generate_move_container_operate(self, container_obj, allocation_target_location):
  611. # 获取容器中所有有效的批次明细
  612. container_detaillist = ContainerDetailModel.objects.filter(
  613. container=container_obj,
  614. is_delete=False
  615. ).exclude(status=3)
  616. # 优化查询 - 预取批次对象
  617. container_detaillist = container_detaillist.select_related('batch')
  618. # 创建批次数量字典
  619. batch_totals = {}
  620. for detail in container_detaillist:
  621. batch_id = detail.batch_id
  622. if batch_id not in batch_totals:
  623. batch_totals[batch_id] = {
  624. 'obj': detail.batch, # 批次对象
  625. 'total_qty': 0
  626. }
  627. batch_totals[batch_id]['total_qty'] += detail.goods_qty
  628. # 获取当前时间和位置信息
  629. current_month = int(timezone.now().strftime("%Y%m"))
  630. current_time = timezone.now()
  631. current_location = container_obj.current_location
  632. # 为每个批次创建操作记录
  633. for batch_id, data in batch_totals.items():
  634. batch_obj = data['obj']
  635. goods_qty = data['total_qty']
  636. ContainerOperationModel.objects.create(
  637. month=current_month,
  638. container=container_obj,
  639. goods_code=batch_obj.goods_code,
  640. goods_desc=batch_obj.goods_desc,
  641. operation_type="adjust", # 操作类型改为移动
  642. batch_id=batch_obj.id,
  643. goods_qty=goods_qty,
  644. goods_weight=goods_qty,
  645. from_location=current_location,
  646. to_location=allocation_target_location,
  647. timestamp=current_time,
  648. operator="WMS",
  649. memo=f"托盘移动: 批次: {batch_obj.bound_number}, 数量: {goods_qty}"
  650. )
  651. def generate_move_container_operate_no_batch(self, container_obj, bound_number,allocation_target_location):
  652. ContainerOperationModel.objects.create(
  653. month = int(timezone.now().strftime("%Y%m")),
  654. container = container_obj,
  655. goods_code = 'container',
  656. goods_desc = '托盘组',
  657. operation_type ="adjust",
  658. goods_qty = 1,
  659. goods_weight = 0,
  660. from_location = container_obj.current_location,
  661. to_location= allocation_target_location,
  662. timestamp=timezone.now(),
  663. memo=f"托盘组移库:从{container_obj.current_location}移库到{allocation_target_location}"
  664. )
  665. def generate_container_operate_no_batch(self, container_obj, bound_number,allocation_target_location):
  666. ContainerOperationModel.objects.create(
  667. month = int(timezone.now().strftime("%Y%m")),
  668. container = container_obj,
  669. goods_code = 'container',
  670. goods_desc = '托盘组',
  671. operation_type ="inbound",
  672. goods_qty = 1,
  673. goods_weight = 0,
  674. from_location = container_obj.current_location,
  675. to_location= allocation_target_location,
  676. timestamp=timezone.now(),
  677. memo=f"WCS入库: 批次: {bound_number}, 数量: 1"
  678. )
  679. def generate_task(self, container, current_location, target_location,batch_id,location_c_number):
  680. batch = BoundBatchModel.objects.filter(bound_number=batch_id).first()
  681. batch_detail = BoundDetailModel.objects.filter(bound_batch=batch).first()
  682. if not batch:
  683. logger.error(f"批次号 {batch_id} 不存在")
  684. return False
  685. data_tosave = {
  686. 'container': container,
  687. 'batch': batch,
  688. 'batch_number': batch_id,
  689. 'batch_out': None,
  690. 'bound_list': batch_detail.bound_list,
  691. 'sequence': 1,
  692. 'order_number' :location_c_number,
  693. 'priority': 1,
  694. 'current_location': current_location,
  695. 'month': timezone.now().strftime('%Y%m'),
  696. 'target_location': target_location,
  697. 'tasktype': 'inbound',
  698. 'status': 103,
  699. 'is_delete': False
  700. }
  701. # 生成唯一递增的 taskid
  702. last_task = ContainerWCSModel.objects.filter(
  703. month=data_tosave['month'],
  704. ).order_by('-tasknumber').first()
  705. if last_task:
  706. number_id = last_task.tasknumber + 1
  707. new_id = f"{number_id:05d}"
  708. else:
  709. new_id = "00001"
  710. number_id = f"{data_tosave['month']}{new_id}"
  711. data_tosave['taskid'] = f"inbound-{data_tosave['month']}-{new_id}"
  712. logger.info(f"生成入库任务: {data_tosave['taskid']}")
  713. # 每月生成唯一递增的 taskNumber
  714. data_tosave['tasknumber'] = number_id
  715. ContainerWCSModel.objects.create(**data_tosave)
  716. def generate_task_no_batch(self, container, current_location, target_location,batch_id,location_c_number):
  717. data_tosave = {
  718. 'container': container,
  719. 'batch': None,
  720. 'batch_number': batch_id,
  721. 'batch_out': None,
  722. 'bound_list': None,
  723. 'sequence': 1,
  724. 'order_number' :location_c_number,
  725. 'priority': 1,
  726. 'current_location': current_location,
  727. 'month': timezone.now().strftime('%Y%m'),
  728. 'target_location': target_location,
  729. 'tasktype': 'inbound',
  730. 'status': 103,
  731. 'is_delete': False
  732. }
  733. # 生成唯一递增的 taskid
  734. last_task = ContainerWCSModel.objects.filter(
  735. month=data_tosave['month'],
  736. ).order_by('-tasknumber').first()
  737. if last_task:
  738. number_id = last_task.tasknumber + 1
  739. new_id = f"{number_id:05d}"
  740. else:
  741. new_id = "00001"
  742. number_id = f"{data_tosave['month']}{new_id}"
  743. data_tosave['taskid'] = f"inbound-{data_tosave['month']}-{new_id}"
  744. logger.info(f"生成入库任务: {data_tosave['taskid']}")
  745. # 每月生成唯一递增的 taskNumber
  746. data_tosave['tasknumber'] = number_id
  747. ContainerWCSModel.objects.create(**data_tosave)
  748. def update_container_wcs(self, request, *args, **kwargs):
  749. data = self.request.data
  750. logger.info(f"请求托盘:{data.get('container_number')}, 请求位置:{data.get('current_location')}, 任务号:{data.get('taskNumber')}")
  751. try:
  752. # 前置校验
  753. container_obj, error_response = self.validate_container(data)
  754. if error_response:
  755. return error_response
  756. # 更新托盘数据
  757. if not self.update_container_data(container_obj, data):
  758. return Response(
  759. {'code': '400', 'message': '数据更新失败', 'data': data},
  760. status=status.HTTP_400_BAD_REQUEST
  761. )
  762. # 处理位置逻辑
  763. task = ContainerWCSModel.objects.filter(
  764. container=container_obj.container_code,
  765. tasktype='inbound'
  766. ).first()
  767. if self.is_already_at_target(container_obj, data.get('current_location')):
  768. return self.handle_target_reached(container_obj, data)
  769. elif task:
  770. data_return = {
  771. 'code': '200',
  772. 'message': '任务已存在,重新下发',
  773. 'data': task.to_dict()
  774. }
  775. return Response(data_return, status=status.HTTP_200_OK)
  776. else:
  777. return self.handle_new_allocation(container_obj, data)
  778. except Exception as e:
  779. logger.error(f"处理请求时发生错误: {str(e)}", exc_info=True)
  780. return Response({'code': '500', 'message': '服务器内部错误', 'data': None},
  781. status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  782. # ---------- 辅助函数 ----------
  783. def validate_container(self, data):
  784. """验证托盘是否存在"""
  785. container = data.get('container_number')
  786. container_obj = ContainerListModel.objects.filter(container_code=container).first()
  787. if not container_obj:
  788. return None, Response({
  789. 'code': '400',
  790. 'message': '托盘编码不存在',
  791. 'data': data
  792. }, status=status.HTTP_400_BAD_REQUEST)
  793. return container_obj, None
  794. def update_container_data(self, container_obj, data):
  795. """更新托盘数据"""
  796. serializer = ContainerListPostSerializer(
  797. container_obj,
  798. data=data,
  799. partial=True
  800. )
  801. if serializer.is_valid():
  802. serializer.save()
  803. return True
  804. return False
  805. def is_already_at_target(self, container_obj, current_location):
  806. """检查是否已在目标位置"""
  807. print (current_location)
  808. print (str(container_obj.target_location))
  809. return current_location == str(container_obj.target_location)
  810. def handle_target_reached(self, container_obj, data):
  811. """处理已到达目标位置的逻辑"""
  812. logger.info(f"托盘 {container_obj.container_code} 已在目标位置")
  813. task = self.get_task_by_tasknumber(data)
  814. self.update_pressure_values(task, container_obj)
  815. # if task.working == 1:
  816. # alloca = LocationAllocation()
  817. # alloca.update_batch_goods_in_location_qty(container_obj.container_code, 1)
  818. task = self.process_task_completion(data)
  819. if not task:
  820. return Response({'code': '400', 'message': '任务不存在', 'data': data},
  821. status=status.HTTP_400_BAD_REQUEST)
  822. if task and task.tasktype == 'inbound':
  823. self.update_storage_system(container_obj)
  824. if task and task.tasktype == 'outbound' and task.status == 300:
  825. success = self.handle_outbound_completion(container_obj, task)
  826. if not success:
  827. return Response({'code': '500', 'message': '出库状态更新失败', 'data': None},
  828. status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  829. OutboundService.process_next_task()
  830. if task and task.tasktype == 'check' and task.status == 300:
  831. success = self.handle_outbound_completion(container_obj, task)
  832. if not success:
  833. return Response({'code': '500', 'message': '出库状态更新失败', 'data': None},
  834. status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  835. OutboundService.process_next_task()
  836. return Response({
  837. 'code': '200',
  838. 'message': '当前位置已是目标位置',
  839. 'data': data
  840. }, status=status.HTTP_200_OK)
  841. def get_task_by_tasknumber(self, data):
  842. taskNumber = data.get('taskNumber') + 20000000000
  843. task = ContainerWCSModel.objects.filter(tasknumber=taskNumber).first()
  844. if task:
  845. return task
  846. else:
  847. return None
  848. def process_task_completion(self, data):
  849. """处理任务完成状态"""
  850. taskNumber = data.get('taskNumber') + 20000000000
  851. task = ContainerWCSModel.objects.filter(tasknumber=taskNumber).first()
  852. if task:
  853. task.status = 300
  854. task.message = '任务已完成'
  855. task.working = 0
  856. task.save()
  857. return task
  858. def update_pressure_values(self, task, container_obj):
  859. """更新压力值计算"""
  860. if task :
  861. base_location_obj = base_location.objects.get(id=1)
  862. layer = int(container_obj.target_location.split('-')[-1])
  863. pressure_field = f"layer{layer}_pressure"
  864. logger.info(f"更新压力值,压力字段:{pressure_field}")
  865. current_pressure = getattr(base_location_obj, pressure_field, 0)
  866. updated_pressure = max(current_pressure - task.working, 0)
  867. setattr(base_location_obj, pressure_field, updated_pressure)
  868. base_location_obj.save()
  869. def update_storage_system(self, container_obj):
  870. """更新仓储系统状态"""
  871. allocator = LocationAllocation()
  872. location_code = self.get_location_code(container_obj.target_location)
  873. # 链式更新操作
  874. update_operations = [
  875. (allocator.update_location_status, location_code, 'occupied'),
  876. (allocator.update_location_container_link, location_code, container_obj.container_code),
  877. (allocator.update_container_detail_status, container_obj.container_code, 2)
  878. ]
  879. for func, *args in update_operations:
  880. if not func(*args):
  881. logger.error(f"操作失败: {func.__name__}")
  882. return False
  883. return True
  884. def get_location_code(self, target_location):
  885. """从目标位置解析获取位置编码"""
  886. parts = target_location.split('-')
  887. coordinate = f"{int(parts[1])}-{int(parts[2])}-{int(parts[3])}"
  888. return LocationModel.objects.filter(coordinate=coordinate).first().location_code
  889. def handle_new_allocation(self, container_obj, data):
  890. """处理新库位分配逻辑"""
  891. allocator = LocationAllocation()
  892. container_code = container_obj.container_code
  893. # 获取并验证库位分配
  894. location = allocator.get_location_by_status(container_code, data.get('current_location'))
  895. if not location or not self.perform_initial_allocation(allocator, location, container_code):
  896. return Response({'code': '400', 'message': '库位分配失败', 'data': data},
  897. status=status.HTTP_400_BAD_REQUEST)
  898. # 生成目标位置并更新托盘
  899. target_location = self.generate_target_location(location)
  900. container_obj.target_location = target_location
  901. container_obj.save()
  902. # 创建任务并返回响应
  903. task = self.create_inbound_task(container_code, data, target_location, location)
  904. return Response({
  905. 'code': '200',
  906. 'message': '任务下发成功',
  907. 'data': task.to_dict()
  908. }, status=status.HTTP_200_OK)
  909. def perform_initial_allocation(self, allocator, location, container_code):
  910. """执行初始库位分配操作"""
  911. operations = [
  912. (allocator.update_location_status, location.location_code, 'reserved'),
  913. (allocator.update_location_group_status, location.location_code),
  914. (allocator.update_batch_status, container_code, '2'),
  915. (allocator.update_location_group_batch, location, container_code),
  916. (allocator.update_location_container_link, location.location_code, container_code),
  917. (allocator.update_container_detail_status, container_code, 2)
  918. ]
  919. for func, *args in operations:
  920. if not func(*args):
  921. logger.error(f"分配操作失败: {func.__name__}")
  922. return False
  923. return True
  924. def generate_target_location(self, location):
  925. """生成目标位置字符串"""
  926. return (
  927. f"{location.warehouse_code}-"
  928. f"{int(location.row):02d}-"
  929. f"{int(location.col):02d}-"
  930. f"{int(location.layer):02d}"
  931. )
  932. def create_inbound_task(self, container_code, data, target_location, location):
  933. """创建入库任务"""
  934. batch_id = LocationAllocation().get_batch(container_code)
  935. self.generate_task(
  936. container_code,
  937. data.get('current_location'),
  938. target_location,
  939. batch_id,
  940. location.c_number
  941. )
  942. task = ContainerWCSModel.objects.get(container=container_code, tasktype='inbound')
  943. self.inport_update_task(task.id, container_code)
  944. return task
  945. def inport_update_task(self, wcs_id,container_id):
  946. try:
  947. task_obj = ContainerWCSModel.objects.filter(id=wcs_id).first()
  948. if task_obj:
  949. container_detail_obj = ContainerDetailModel.objects.filter(container=container_id,is_delete=False).all()
  950. if container_detail_obj:
  951. for detail in container_detail_obj:
  952. # 保存到数据库
  953. batch = BoundDetailModel.objects.filter(bound_batch_id=detail.batch.id).first()
  954. TaskModel.objects.create(
  955. task_wcs = task_obj,
  956. container_detail = detail,
  957. batch_detail = batch
  958. )
  959. logger.info(f"入库任务 {wcs_id} 已更新")
  960. else:
  961. logger.info(f"入库任务 {container_id} 批次不存在")
  962. else:
  963. logger.info(f"入库任务 {wcs_id} 不存在")
  964. except Exception as e:
  965. logger.error(f"处理入库任务时发生错误: {str(e)}", exc_info=True)
  966. return Response(
  967. {'code': '500', 'message': '服务器内部错误', 'data': None},
  968. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  969. )
  970. def release_location(self,request):
  971. """释放库位"""
  972. """添加权限管理"""
  973. token = self.request.META.get('HTTP_TOKEN')
  974. if not token:
  975. return Response({'code': '401', 'message': '请登录', 'data': None},
  976. status=status.HTTP_200_OK)
  977. user = StaffListModel.objects.filter(openid=token).first()
  978. if not user:
  979. return Response({'code': '401', 'message': '请登录', 'data': None},
  980. status=status.HTTP_200_OK)
  981. if user.staff_type not in ['Supervisor', 'Manager','Admin']:
  982. return Response({'code': '401', 'message': '无权限', 'data': None},
  983. status=status.HTTP_200_OK)
  984. try:
  985. location_release = request.data.get('location_release')
  986. location_row = location_release.split('-')[1]
  987. location_col = location_release.split('-')[2]
  988. location_layer = location_release.split('-')[3]
  989. location= LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first()
  990. location_code = location.location_code
  991. allocator = LocationAllocation()
  992. with transaction.atomic():
  993. if not allocator.release_location(location_code):
  994. raise Exception("解除库位关联失败")
  995. if not allocator.update_location_status(location_code, 'available'):
  996. raise Exception("库位状态更新失败")
  997. if not allocator.update_location_group_status(location_code):
  998. raise Exception("库位组状态更新失败")
  999. except Exception as e:
  1000. logger.error(f"解除库位关联失败: {str(e)}")
  1001. return Response({'code': '500', 'message': e, 'data': None},
  1002. status=status.HTTP_200_OK)
  1003. return Response({'code': '200', 'message': '解除库位关联成功', 'data': None},
  1004. status=status.HTTP_200_OK)
  1005. def handle_outbound_completion(self, container_obj, task):
  1006. """处理出库完成后的库位释放和状态更新"""
  1007. try:
  1008. allocator = LocationAllocation()
  1009. location_task = task.current_location
  1010. if location_task == '103' or location_task == '203':
  1011. return True
  1012. else:
  1013. location_row = location_task.split('-')[1]
  1014. location_col = location_task.split('-')[2]
  1015. location_layer = location_task.split('-')[3]
  1016. location= LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first()
  1017. location_code = location.location_code
  1018. # 事务确保原子性
  1019. with transaction.atomic():
  1020. # 解除库位与托盘的关联
  1021. if not allocator.release_location(location_code):
  1022. raise Exception("解除库位关联失败")
  1023. # 更新库位状态为可用
  1024. if not allocator.update_location_status(location_code, 'available'):
  1025. raise Exception("库位状态更新失败")
  1026. # 更新库位组的统计信息
  1027. self.handle_group_location_status(location_code, location.location_group)
  1028. # 更新托盘状态为已出库(假设状态3表示已出库)
  1029. container_obj.status = 3
  1030. container_obj.save()
  1031. return True
  1032. except Exception as e:
  1033. logger.error(f"出库完成处理失败: {str(e)}")
  1034. return False
  1035. def handle_group_location_status(self,location_code,location_group):
  1036. """
  1037. 处理库位组和库位的关联关系
  1038. :param location_code: 库位编码
  1039. :param location_group: 库位组编码
  1040. :return:
  1041. """
  1042. # 1. 获取库位空闲状态的库位数目
  1043. location_obj_number = LocationModel.objects.filter(
  1044. location_group=location_group,
  1045. status='available'
  1046. ).all().count()
  1047. # 2. 获取库位组对象
  1048. logger.info(f"库位组 {location_group} 下的库位数目:{location_obj_number}")
  1049. # 1. 获取库位和库位组的关联关系
  1050. location_group_obj = LocationGroupModel.objects.filter(
  1051. group_code=location_group
  1052. ).first()
  1053. if not location_group_obj:
  1054. logger.info(f"库位组 {location_group} 不存在")
  1055. return None
  1056. else:
  1057. if location_obj_number == 0:
  1058. # 库位组库位已满,更新库位组状态为full
  1059. location_group_obj.status = 'full'
  1060. location_group_obj.save()
  1061. elif location_obj_number < location_group_obj.max_capacity:
  1062. location_group_obj.status = 'occupied'
  1063. location_group_obj.save()
  1064. else:
  1065. location_group_obj.status = 'available'
  1066. location_group_obj.current_batch = ''
  1067. location_group_obj.current_goods_code = ''
  1068. location_group_obj.save()
  1069. # PDA组盘入库 将扫描到的托盘编码和批次信息保存到数据库
  1070. # 1. 先查询托盘对象,如果不存在,则创建托盘对象
  1071. # 2. 循环处理每个批次,查询批次对象,
  1072. # 3. 更新批次数据(根据业务规则)
  1073. # 4. 保存到数据库
  1074. # 5. 保存操作记录到数据库
  1075. class ContainerDetailViewSet(viewsets.ModelViewSet):
  1076. """
  1077. retrieve:
  1078. Response a data list(get)
  1079. list:
  1080. Response a data list(all)
  1081. create:
  1082. Create a data line(post)
  1083. delete:
  1084. Delete a data line(delete)
  1085. """
  1086. # authentication_classes = [] # 禁用所有认证类
  1087. # permission_classes = [AllowAny] # 允许任意访问
  1088. pagination_class = MyPageNumberPagination
  1089. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  1090. ordering_fields = ['id', "create_time", "update_time", ]
  1091. filter_class = ContainerDetailFilter
  1092. def get_project(self):
  1093. try:
  1094. id = self.kwargs.get('pk')
  1095. return id
  1096. except:
  1097. return None
  1098. def get_queryset(self):
  1099. id = self.get_project()
  1100. if self.request.user:
  1101. if id is None:
  1102. return ContainerDetailModel.objects.filter( is_delete=False)
  1103. else:
  1104. return ContainerDetailModel.objects.filter( id=id, is_delete=False)
  1105. else:
  1106. return ContainerDetailModel.objects.none()
  1107. def get_serializer_class(self):
  1108. if self.action in ['list', 'destroy','retrieve']:
  1109. return ContainerDetailGetSerializer
  1110. elif self.action in ['create', 'update']:
  1111. return ContainerDetailPostSerializer
  1112. else:
  1113. return self.http_method_not_allowed(request=self.request)
  1114. def create(self, request, *args, **kwargs):
  1115. data = self.request.data
  1116. from .container_operate import ContainerService
  1117. ContainerService.create_container_operation(data,logger=logger)
  1118. # 将处理后的数据返回(或根据业务需求保存到数据库)
  1119. res_data={
  1120. "code": "200",
  1121. "msg": "Success Create",
  1122. "data": data
  1123. }
  1124. return Response(res_data, status=200)
  1125. def update(self, request, pk):
  1126. qs = self.get_object()
  1127. data = self.request.data
  1128. serializer = self.get_serializer(qs, data=data)
  1129. serializer.is_valid(raise_exception=True)
  1130. serializer.save()
  1131. headers = self.get_success_headers(serializer.data)
  1132. return Response(serializer.data, status=200, headers=headers)
  1133. def destroy(self, request, pk):
  1134. qs = self.get_object()
  1135. qs.is_delete = True
  1136. qs.save()
  1137. return Response({'code': 200,'message': '删除成功', 'data': None}, status=200)
  1138. def containerdetail_list(self, request):
  1139. """
  1140. 获取托盘详情列表
  1141. """
  1142. try:
  1143. container_id = request.query_params.get('container')
  1144. if not container_id:
  1145. return Response(
  1146. {'code': 400, 'message': '缺少托盘ID参数', 'data': None},
  1147. status=status.HTTP_400_BAD_REQUEST
  1148. )
  1149. # 获取托盘对象
  1150. try:
  1151. container = ContainerListModel.objects.get(id=container_id)
  1152. except ContainerListModel.DoesNotExist:
  1153. return Response(
  1154. {'code': 404, 'message': '指定托盘不存在', 'data': None},
  1155. status=status.HTTP_404_NOT_FOUND
  1156. )
  1157. # 查询关联批次明细(排除状态0和3)
  1158. details = ContainerDetailModel.objects.filter(
  1159. container=container,is_delete=False
  1160. ).exclude(
  1161. status__in=[0, 3]
  1162. ).select_related('batch')
  1163. details_serializer = ContainerDetailSimpleGetSerializer(details, many=True)
  1164. return Response(
  1165. {'code': 200, 'message': 'Success', 'data': details_serializer.data},
  1166. status=status.HTTP_200_OK
  1167. )
  1168. except Exception as e:
  1169. return Response(
  1170. {'code': 500, 'message': f'服务器错误: {str(e)}', 'data': None},
  1171. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  1172. )
  1173. def locationdetail_list(self, request):
  1174. """
  1175. 获取库位所处托盘的信息(按批次号 + 数量分组)
  1176. 新增批次总量统计功能
  1177. """
  1178. try:
  1179. container_id = request.query_params.get('container')
  1180. if not container_id:
  1181. return Response(
  1182. {'code': 400, 'message': '缺少托盘ID参数', 'data': None},
  1183. status=status.HTTP_400_BAD_REQUEST
  1184. )
  1185. # 获取托盘对象
  1186. try:
  1187. container = ContainerListModel.objects.get(id=container_id)
  1188. except ContainerListModel.DoesNotExist:
  1189. return Response(
  1190. {'code': 404, 'message': '指定托盘不存在', 'data': None},
  1191. status=status.HTTP_404_NOT_FOUND
  1192. )
  1193. # 查询关联批次明细(排除状态0和3)
  1194. details = ContainerDetailModel.objects.filter(
  1195. container=container,is_delete=False
  1196. ).exclude(
  1197. status__in=[0, 3]
  1198. ).select_related('batch')
  1199. if not details.exists():
  1200. return Response(
  1201. {'code': 404, 'message': '未找到有效批次数据', 'data': None},
  1202. status=status.HTTP_404_NOT_FOUND
  1203. )
  1204. # 按批次号 + 数量分组统计
  1205. batch_dict = {}
  1206. batch_qty_dict = defaultdict(int) # 使用默认字典自动初始化
  1207. for detail in details:
  1208. if not detail.batch:
  1209. continue
  1210. bound_number = detail.batch.bound_number
  1211. goods_qty = detail.goods_qty - detail.goods_out_qty # 剔除出库数量
  1212. # 组合键:批次号 + 当前数量
  1213. batch_key = (bound_number, goods_qty)
  1214. batch_qty_dict[bound_number] += goods_qty # 自动处理键初始化
  1215. # 分组统计
  1216. if batch_key not in batch_dict:
  1217. batch_obj = BoundBatchModel.objects.filter( bound_number=bound_number).first()
  1218. batch_dict[batch_key] = {
  1219. "goods_code": detail.goods_code,
  1220. "goods_desc": detail.goods_desc,
  1221. "goods_qty": goods_qty,
  1222. "goods_class": detail.goods_class,
  1223. "goods_package": batch_obj.goods_package,
  1224. "batch_total_qty": batch_obj.goods_qty,
  1225. "batch_total_in_qty": batch_obj.goods_in_qty - batch_obj.goods_out_qty,
  1226. "status": detail.status,
  1227. "group_qty": 1,
  1228. "create_time": detail.create_time,
  1229. }
  1230. else:
  1231. batch_dict[batch_key]["group_qty"] += 1
  1232. # 重构数据结构
  1233. results = []
  1234. for (bound_number, qty), data in batch_dict.items():
  1235. results.append({
  1236. **data,
  1237. "bound_number": bound_number,
  1238. "current_qty": qty,
  1239. "total_batch_qty": batch_qty_dict[bound_number] # 添加批次总量
  1240. })
  1241. batch_totals =[]
  1242. for bound_number, qty in batch_qty_dict.items():
  1243. batch_totals.append({
  1244. "bound_number": bound_number,
  1245. "total_batch_qty": qty
  1246. })
  1247. return Response(
  1248. {
  1249. "code": 200,
  1250. "message": "Success",
  1251. "data": {
  1252. "container_code": container.container_code,
  1253. "count": len(results),
  1254. "results": results,
  1255. "batch_totals": batch_totals # 可选:单独返回批次总量
  1256. }
  1257. },
  1258. status=status.HTTP_200_OK
  1259. )
  1260. except Exception as e:
  1261. return Response(
  1262. {'code': 500, 'message': f'服务器错误: {str(e)}', 'data': None},
  1263. status=status.HTTP_500_INTERNAL_SERVER_ERROR
  1264. )
  1265. def pdadetail_list(self, request):
  1266. """
  1267. 获取PDA组盘入库的托盘详情列表
  1268. """
  1269. try:
  1270. container_code = request.query_params.get('container_code')
  1271. if not container_code:
  1272. return Response(
  1273. {'code': 400, 'message': '缺少托盘编码参数', 'data': None},
  1274. status=status.HTTP_200_OK
  1275. )
  1276. # 获取托盘对象
  1277. try:
  1278. container = ContainerListModel.objects.get(container_code=container_code)
  1279. except ContainerListModel.DoesNotExist:
  1280. return Response(
  1281. {'code': 404, 'message': '指定托盘不存在', 'data': None},
  1282. status=status.HTTP_200_OK
  1283. )
  1284. # 查询关联批次明细(排除状态0和3)
  1285. details = ContainerDetailModel.objects.filter(
  1286. container=container,is_delete=False
  1287. ).exclude(
  1288. status__in=[0, 3]
  1289. ).select_related('batch')
  1290. if not details.exists():
  1291. return Response(
  1292. {'code': 404, 'message': '未找到有效批次数据', 'data': None},
  1293. status=status.HTTP_200_OK
  1294. )
  1295. # 按批次号 + 数量分组统计
  1296. batch_dict = {}
  1297. batch_qty_dict = defaultdict(int) # 使用默认字典自动初始化
  1298. for detail in details:
  1299. if not detail.batch:
  1300. continue
  1301. bound_number = detail.batch.bound_number
  1302. goods_qty = detail.goods_qty - detail.goods_out_qty # 剔除出库数量
  1303. # 组合键:批次号 + 当前数量
  1304. batch_key = (bound_number, goods_qty)
  1305. batch_qty_dict[bound_number] += goods_qty # 自动处理键初始化
  1306. # 分组统计
  1307. if batch_key not in batch_dict:
  1308. batch_obj = BoundBatchModel.objects.filter( bound_number=bound_number).first()
  1309. batch_dict[batch_key] = {
  1310. "goods_code": detail.goods_code,
  1311. "goods_desc": detail.goods_desc,
  1312. "goods_qty": goods_qty,
  1313. "goods_class": detail.goods_class,
  1314. "goods_package": batch_obj.goods_package,
  1315. "batch_total_qty": batch_obj.goods_qty,
  1316. "batch_total_in_qty": batch_obj.goods_in_qty - batch_obj.goods_out_qty,
  1317. "status": detail.status,
  1318. "group_qty": 1,
  1319. "create_time": detail.create_time,
  1320. }
  1321. else:
  1322. batch_dict[batch_key]["group_qty"] += 1
  1323. # 重构数据结构
  1324. results = []
  1325. for (bound_number, qty), data in batch_dict.items():
  1326. results.append({
  1327. **data,
  1328. "bound_number": bound_number,
  1329. "current_qty": qty,
  1330. "total_batch_qty": batch_qty_dict[bound_number] # 添加批次总量
  1331. })
  1332. batch_totals =[]
  1333. for bound_number, qty in batch_qty_dict.items():
  1334. batch_totals.append({
  1335. "bound_number": bound_number,
  1336. "total_batch_qty": qty
  1337. })
  1338. return Response(
  1339. {
  1340. "code": 200,
  1341. "message": "Success",
  1342. "data": {
  1343. "count": len(results),
  1344. "results": results,
  1345. "batch_totals": batch_totals # 可选:单独返回批次总量
  1346. }
  1347. },
  1348. status=status.HTTP_200_OK
  1349. )
  1350. except Exception as e:
  1351. return Response(
  1352. {'code': 500, 'message': f'服务器错误: {str(e)}', 'data': None},
  1353. status=status.HTTP_200_OK
  1354. )
  1355. # 托盘操作历史记录
  1356. class ContainerOperateViewSet(viewsets.ModelViewSet):
  1357. """
  1358. retrieve:
  1359. Response a data list(get)
  1360. list:
  1361. Response a data list(all)
  1362. create:
  1363. Create a data line(post)
  1364. delete:
  1365. Delete a data line(delete)
  1366. """
  1367. # authentication_classes = [] # 禁用所有认证类
  1368. # permission_classes = [AllowAny] # 允许任意访问
  1369. pagination_class = MyPageNumberPagination
  1370. filter_backends = [DjangoFilterBackend, OrderingFilter, ]
  1371. ordering_fields = ['id', "timestamp" ]
  1372. filter_class = ContainerOperationFilter
  1373. def get_project(self):
  1374. try:
  1375. id = self.kwargs.get('pk')
  1376. return id
  1377. except:
  1378. return None
  1379. def get_queryset(self):
  1380. id = self.get_project()
  1381. if self.request.user:
  1382. if id is None:
  1383. return ContainerOperationModel.objects.filter( is_delete=False)
  1384. else:
  1385. return ContainerOperationModel.objects.filter( id=id, is_delete=False)
  1386. else:
  1387. return ContainerOperationModel.objects.none()
  1388. def get_serializer_class(self):
  1389. if self.action in ['list', 'destroy','retrieve']:
  1390. return ContainerOperationGetSerializer
  1391. elif self.action in ['create', 'update']:
  1392. return ContainerOperationPostSerializer
  1393. else:
  1394. return self.http_method_not_allowed(request=self.request)
  1395. def create(self, request, *args, **kwargs):
  1396. data = self.request.data
  1397. serializer = self.get_serializer(data=data)
  1398. serializer.is_valid(raise_exception=True)
  1399. serializer.save()
  1400. headers = self.get_success_headers(serializer.data)
  1401. return Response(serializer.data, status=200, headers=headers)
  1402. def update(self, request, pk):
  1403. qs = self.get_object()
  1404. data = self.request.data
  1405. serializer = self.get_serializer(qs, data=data)
  1406. serializer.is_valid(raise_exception=True)
  1407. serializer.save()
  1408. headers = self.get_success_headers(serializer.data)
  1409. return Response(serializer.data, status=200, headers=headers)
  1410. # 出库任务生成
  1411. class OutboundService:
  1412. @staticmethod
  1413. def generate_task_id():
  1414. """生成唯一任务ID(格式: outbound-年月-顺序号)"""
  1415. month = timezone.now().strftime("%Y%m")
  1416. last_task = ContainerWCSModel.objects.filter(
  1417. tasktype='outbound',
  1418. month=int(month)
  1419. ).order_by('-sequence').first()
  1420. sequence = last_task.sequence + 1 if last_task else 1
  1421. return f"outbound-{month}-{sequence:05d}"
  1422. @staticmethod
  1423. def send_task_to_wcs(task):
  1424. """异步发送任务到WCS(非阻塞版本)"""
  1425. # 提取任务关键数据用于线程(避免直接传递ORM对象)
  1426. task_data = {
  1427. 'task_id': task.pk, # 使用主键而不是对象
  1428. 'send_data': {
  1429. "code":'200',
  1430. "message": task.message,
  1431. "data":{
  1432. "taskid": task.taskid,
  1433. "container": task.container,
  1434. "current_location": task.current_location,
  1435. "target_location": task.target_location,
  1436. "tasktype": task.tasktype,
  1437. "month": task.month,
  1438. "message": task.message,
  1439. "status": task.status,
  1440. "taskNumber": task.tasknumber-20000000000,
  1441. "order_number":task.order_number,
  1442. "sequence":task.sequence
  1443. }
  1444. }
  1445. }
  1446. # 创建并启动线程
  1447. thread = threading.Thread(
  1448. target=OutboundService._async_send_handler,
  1449. kwargs=task_data,
  1450. daemon=True # 守护线程(主程序退出时自动终止)
  1451. )
  1452. thread.start()
  1453. return True # 立即返回表示已开始处理
  1454. @staticmethod
  1455. def _async_send_handler(task_id, send_data):
  1456. """异步处理的实际工作函数"""
  1457. try:
  1458. # 每个线程需要独立的数据库连接
  1459. close_old_connections()
  1460. # 重新获取任务对象(确保使用最新数据)
  1461. task = ContainerWCSModel.objects.get(pk=task_id)
  1462. # 发送第一个请求(不处理结果)
  1463. requests.post(
  1464. "http://127.0.0.1:8008/container/batch/",
  1465. json=send_data,
  1466. timeout=10
  1467. )
  1468. # 发送关键请求
  1469. response = requests.post(
  1470. "http://192.168.18.200:1616/wcs/WebApi/getOutTask",
  1471. json=send_data,
  1472. timeout=10
  1473. )
  1474. # 处理响应
  1475. if response.status_code == 200:
  1476. task.status = 200
  1477. task.save()
  1478. logger.info(f"任务 {task.taskid} 已发送")
  1479. else:
  1480. logger.error(f"WCS返回错误: {response.text}")
  1481. except Exception as e:
  1482. logger.error(f"发送失败: {str(e)}")
  1483. finally:
  1484. close_old_connections() # 清理数据库连接
  1485. @staticmethod
  1486. def create_initial_tasks(container_list,bound_list_id):
  1487. """生成初始任务队列"""
  1488. with transaction.atomic():
  1489. current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id).first()
  1490. if current_WCS:
  1491. logger.error(f"当前{bound_list_id}已有出库任务")
  1492. return False
  1493. tasks = []
  1494. start_sequence = ContainerWCSModel.objects.filter(tasktype='outbound').count() + 1
  1495. tasknumber = ContainerWCSModel.objects.filter().count()
  1496. tasknumber_index = 1
  1497. for index, container in enumerate(container_list, start=start_sequence):
  1498. container_obj = ContainerListModel.objects.filter(id =container['container_number']).first()
  1499. if container_obj.current_location != container_obj.target_location:
  1500. logger.error(f"托盘 {container_obj.container_code} 未到达目的地,不生成任务")
  1501. return False
  1502. OutBoundDetail_obj = OutBoundDetailModel.objects.filter(bound_list=bound_list_id,bound_batch_number_id=container['batch_id']).first()
  1503. if not OutBoundDetail_obj:
  1504. logger.error(f"批次 {container['batch_id']} 不存在")
  1505. return False
  1506. month = int(timezone.now().strftime("%Y%m"))
  1507. task = ContainerWCSModel(
  1508. taskid=OutboundService.generate_task_id(),
  1509. batch = OutBoundDetail_obj.bound_batch_number,
  1510. batch_out = OutBoundDetail_obj.bound_batch,
  1511. bound_list = OutBoundDetail_obj.bound_list,
  1512. sequence=index,
  1513. order_number = container['c_number'],
  1514. priority=100,
  1515. tasknumber = month*100000+tasknumber_index+tasknumber,
  1516. container=container_obj.container_code,
  1517. current_location=container_obj.current_location,
  1518. target_location="103",
  1519. tasktype="outbound",
  1520. month=int(timezone.now().strftime("%Y%m")),
  1521. message="等待出库",
  1522. status=100,
  1523. )
  1524. tasknumber_index += 1
  1525. tasks.append(task)
  1526. container_obj = ContainerListModel.objects.filter(container_code=task.container).first()
  1527. container_obj.target_location = task.target_location
  1528. container_obj.save()
  1529. ContainerWCSModel.objects.bulk_create(tasks)
  1530. logger.info(f"已创建 {len(tasks)} 个初始任务")
  1531. @staticmethod
  1532. def create_initial_check_tasks(container_list,batch_id):
  1533. """生成初始任务队列"""
  1534. with transaction.atomic():
  1535. current_WCS = ContainerWCSModel.objects.filter(tasktype='check',batch_id = batch_id,is_delete=False,working=1).first()
  1536. if current_WCS:
  1537. logger.error(f"当前{batch_id}已有检查任务")
  1538. return False
  1539. tasks = []
  1540. start_sequence = ContainerWCSModel.objects.filter(tasktype='check').count() + 1
  1541. tasknumber = ContainerWCSModel.objects.filter().count()
  1542. tasknumber_index = 1
  1543. for index, container in enumerate(container_list, start=start_sequence):
  1544. container_obj = ContainerListModel.objects.filter(id =container['container_number']).first()
  1545. if container_obj.current_location != container_obj.target_location:
  1546. logger.error(f"托盘 {container_obj.container_code} 未到达目的地,不生成任务")
  1547. return False
  1548. batch_obj = BoundBatchModel.objects.filter(id =batch_id).first()
  1549. month = int(timezone.now().strftime("%Y%m"))
  1550. task = ContainerWCSModel(
  1551. taskid=OutboundService.generate_task_id(),
  1552. batch = batch_obj,
  1553. batch_out = None,
  1554. bound_list = None,
  1555. sequence=index,
  1556. order_number = container['c_number'],
  1557. priority=100,
  1558. tasknumber = month*100000+tasknumber_index+tasknumber,
  1559. container=container_obj.container_code,
  1560. current_location=container_obj.current_location,
  1561. target_location="103",
  1562. tasktype="check",
  1563. month=int(timezone.now().strftime("%Y%m")),
  1564. message="等待出库",
  1565. status=100,
  1566. )
  1567. tasknumber_index += 1
  1568. tasks.append(task)
  1569. container_obj = ContainerListModel.objects.filter(container_code=task.container).first()
  1570. container_obj.target_location = task.target_location
  1571. container_obj.save()
  1572. ContainerWCSModel.objects.bulk_create(tasks)
  1573. logger.info(f"已创建 {len(tasks)} 个初始任务")
  1574. @staticmethod
  1575. def insert_new_tasks(new_tasks):
  1576. """动态插入新任务并重新排序"""
  1577. with transaction.atomic():
  1578. pending_tasks = list(ContainerWCSModel.objects.select_for_update().filter(status=100))
  1579. # 插入新任务
  1580. for new_task_data in new_tasks:
  1581. new_task = ContainerWCSModel(
  1582. taskid=OutboundService.generate_task_id(),
  1583. priority=new_task_data.get('priority', 100),
  1584. container=new_task_data['container'],
  1585. current_location=new_task_data['current_location'],
  1586. target_location=new_task_data.get('target_location', 'OUT01'),
  1587. tasktype="outbound",
  1588. month=int(timezone.now().strftime("%Y%m")),
  1589. message="等待出库",
  1590. status=100,
  1591. )
  1592. # 找到插入位置
  1593. insert_pos = 0
  1594. for i, task in enumerate(pending_tasks):
  1595. if new_task.priority < task.priority:
  1596. insert_pos = i
  1597. break
  1598. else:
  1599. insert_pos = len(pending_tasks)
  1600. pending_tasks.insert(insert_pos, new_task)
  1601. # 重新分配顺序号
  1602. for i, task in enumerate(pending_tasks, start=1):
  1603. task.sequence = i
  1604. if task.pk is None:
  1605. task.save()
  1606. else:
  1607. task.save(update_fields=['sequence'])
  1608. logger.info(f"已插入 {len(new_tasks)} 个新任务")
  1609. @staticmethod
  1610. def process_next_task():
  1611. """处理下一个任务"""
  1612. next_task = ContainerWCSModel.objects.filter(status=100,working=1,is_delete=False).order_by('sequence').first()
  1613. if not next_task:
  1614. logger.info("没有待处理任务")
  1615. return
  1616. location = next_task.current_location
  1617. if location =='103' or location =='203':
  1618. logger.info (f"需要跳过该任务")
  1619. next_task.status = 200
  1620. next_task.working = 0
  1621. next_task.save()
  1622. OutboundService.process_next_task()
  1623. return
  1624. allocator = LocationAllocation()
  1625. OutboundService.perform_initial_allocation(allocator, next_task.current_location)
  1626. OutboundService.send_task_to_wcs(next_task)
  1627. @staticmethod
  1628. def process_current_task(task_id):
  1629. """发送指定任务"""
  1630. try:
  1631. task = ContainerWCSModel.objects.get(taskid=task_id)
  1632. allocator = LocationAllocation()
  1633. OutboundService.perform_initial_allocation(allocator, task.current_location)
  1634. OutboundService.send_task_to_wcs(task)
  1635. except Exception as e:
  1636. logger.error(f"任务处理失败: {str(e)}")
  1637. def perform_initial_allocation(allocator, location):
  1638. """执行初始库位分配操作"""
  1639. location_row = location.split('-')[1]
  1640. location_col = location.split('-')[2]
  1641. location_layer = location.split('-')[3]
  1642. location_code = LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first().location_code
  1643. if not location_code:
  1644. logger.error(f"未找到库位: {location}")
  1645. operations = [
  1646. (allocator.update_location_status,location_code, 'reserved'),
  1647. (allocator.update_location_group_status,location_code)
  1648. ]
  1649. for func, *args in operations:
  1650. if not func(*args):
  1651. logger.error(f"分配操作失败: {func.__name__}")
  1652. return False
  1653. return True
  1654. # 出库任务下发
  1655. class OutTaskViewSet(ViewSet):
  1656. """
  1657. # fun:get_out_task:下发出库任务
  1658. # fun:get_batch_count_by_boundlist:获取出库申请下的批次数量
  1659. # fun:generate_location_by_demand:根据出库需求生成出库任务
  1660. """
  1661. # authentication_classes = [] # 禁用所有认证类
  1662. # permission_classes = [AllowAny] # 允许任意访问
  1663. def post(self, request):
  1664. try:
  1665. data = self.request.data
  1666. logger.info(f"收到 出库 推送数据: {data}")
  1667. # 从请求中获取 bound_list_id
  1668. bound_list_id = data.get('bound_list_id')
  1669. current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id,is_delete=False).first()
  1670. if current_WCS:
  1671. logger.info(f"当前{bound_list_id}已有出库任务{current_WCS.taskid}")
  1672. if current_WCS.working == 1:
  1673. OutboundService.process_current_task(current_WCS.taskid)
  1674. return Response({"code": 200, "msg": f"下发任务{ current_WCS.taskid }到WCS成功"}, status=200)
  1675. else :
  1676. return Response({"code": 200, "msg": f"当前任务{current_WCS.taskid}正在处理中"}, status=200)
  1677. # 获取关联的出库批次
  1678. out_batches = OutBatchModel.objects.filter(
  1679. bound_list_id=bound_list_id,
  1680. is_delete=False
  1681. ).select_related('batch_number')
  1682. if not out_batches.exists():
  1683. return Response({"code": 404, "msg": "未找到相关出库批次"}, status=404)
  1684. # 构建批次需求字典
  1685. batch_demand = {}
  1686. for ob in out_batches:
  1687. if ob.batch_number_id not in batch_demand:
  1688. batch_demand[ob.batch_number_id] = {
  1689. 'required': ob.goods_out_qty,
  1690. 'allocated': ob.goods_qty,
  1691. 'remaining': ob.goods_qty - ob.goods_out_qty
  1692. }
  1693. else:
  1694. batch_demand[ob.batch_number_id]['required'] += ob.goods_out_qty
  1695. batch_demand[ob.batch_number_id]['allocated'] += ob.goods_qty
  1696. batch_demand[ob.batch_number_id]['remaining'] += (ob.goods_out_qty - ob.goods_qty)
  1697. # 生成出库任务
  1698. generate_result = self.generate_location_by_demand(
  1699. batch_demand=batch_demand,
  1700. bound_list_id=bound_list_id
  1701. )
  1702. if generate_result['code'] != '200':
  1703. return Response(generate_result, status=500)
  1704. # 创建并处理出库任务
  1705. container_list = generate_result['data']
  1706. # 2. 生成初始任务
  1707. OutboundService.create_initial_tasks(container_list,bound_list_id)
  1708. # 3. 立即发送第一个任务
  1709. OutboundService.process_next_task()
  1710. return Response({"code": 200, "msg": "下发任务成功"}, status=200)
  1711. except Exception as e:
  1712. logger.error(f"任务生成失败: {str(e)}")
  1713. return Response({"code": 200, "msg": str(e)}, status=200)
  1714. def post_check(self, request):
  1715. try:
  1716. data = self.request.data
  1717. logger.info(f"收到 出库抽检 推送数据: {data}")
  1718. # 从请求中获取 batch_id
  1719. batch_id = data.get('batch_id')
  1720. container_demand = int(data.get('container_demand'))
  1721. if not batch_id or not container_demand:
  1722. return Response({"code": "400", "msg": "缺少抽检数目或批次号"}, status=200)
  1723. current_WCS = ContainerWCSModel.objects.filter(batch=batch_id,tasktype='check',is_delete=False,working=1).first()
  1724. if current_WCS:
  1725. logger.info(f"当前{batch_id}已有出库抽检任务{current_WCS.taskid}")
  1726. if current_WCS.working == 1:
  1727. OutboundService.process_current_task(current_WCS.taskid)
  1728. return Response({"code": 200, "msg": f"下发任务{ current_WCS.taskid }到WCS成功"}, status=200)
  1729. else :
  1730. return Response({"code": 200, "msg": f"当前任务{current_WCS.taskid}正在处理中"}, status=200)
  1731. # 获取批次号
  1732. generate_result = self.generate_location_by_check(batch_id,container_demand)
  1733. if generate_result['code'] != '200':
  1734. return Response(generate_result, status=200)
  1735. # 创建并处理出库任务
  1736. container_list = generate_result['data']
  1737. # 3. 立即发送第一个任务
  1738. OutboundService.create_initial_check_tasks(container_list,batch_id)
  1739. OutboundService.process_next_task()
  1740. return Response({"code": 200, "msg": "下发任务成功"}, status=200)
  1741. except Exception as e:
  1742. logger.error(f"任务生成失败: {str(e)}")
  1743. return Response({"code": 200, "msg": str(e)}, status=200)
  1744. def get_batch_count_by_boundlist(self,bound_list_id):
  1745. try:
  1746. bound_list_obj_all = OutBoundDetailModel.objects.filter(bound_list=bound_list_id).all()
  1747. if bound_list_obj_all:
  1748. batch_count_dict = {}
  1749. # 统计批次数量(创建哈希表,去重)
  1750. for batch in bound_list_obj_all:
  1751. if batch.bound_batch_number_id not in batch_count_dict:
  1752. batch_count_dict[batch.bound_batch_number_id] = batch.bound_batch.goods_out_qty
  1753. else:
  1754. batch_count_dict[batch.bound_batch_number_id] += batch.bound_batch.goods_out_qty
  1755. return batch_count_dict
  1756. else:
  1757. logger.error(f"查询批次数量失败: {bound_list_id} 不存在")
  1758. return {}
  1759. except Exception as e:
  1760. logger.error(f"查询批次数量失败: {str(e)}")
  1761. return {}
  1762. def get_location_by_status_and_batch(self,status,bound_id):
  1763. try:
  1764. container_obj = ContainerDetailModel.objects.filter(batch=bound_id,status=status,is_delete=False).all()
  1765. if container_obj:
  1766. container_dict = {}
  1767. # 统计托盘数量(创建哈希表,去重)
  1768. for obj in container_obj:
  1769. if obj.container_id not in container_dict:
  1770. container_dict[obj.container_id] = obj.goods_qty
  1771. else:
  1772. container_dict[obj.container_id] += obj.goods_qty
  1773. return container_dict
  1774. else:
  1775. logger.error(f"查询{status}状态的批次数量失败: {bound_id} 不存在")
  1776. return {}
  1777. except Exception as e:
  1778. logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
  1779. return {}
  1780. def get_order_by_batch(self,container_list,bound_id):
  1781. try:
  1782. container_dict = {}
  1783. for container in container_list:
  1784. location_container = LocationContainerLink.objects.filter(container_id=container,is_active=True).first()
  1785. if location_container:
  1786. location_c_number = location_container.location.c_number
  1787. if container not in container_dict:
  1788. container_dict[container] = {
  1789. "container_number":container,
  1790. "location_c_number":location_c_number,
  1791. "location_id ":location_container.location.id,
  1792. "location_type":location_container.location.location_type,
  1793. "batch_id":bound_id,
  1794. }
  1795. if len(container_dict.keys()) == len(container_list):
  1796. return container_dict
  1797. else:
  1798. logger.error(f"查询批次数量失败: {container_list} 不存在")
  1799. return {}
  1800. except Exception as e:
  1801. logger.error(f"查询批次数量失败: {str(e)}")
  1802. return {}
  1803. except Exception as e:
  1804. logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
  1805. return {}
  1806. def get_container_allocation(self, batch_id):
  1807. """兼容所有数据库的去重方案"""
  1808. # 获取唯一托盘ID列表
  1809. container_ids = (
  1810. ContainerDetailModel.objects
  1811. .filter(batch_id=batch_id, status=2,is_delete=False)
  1812. .values_list('container_id', flat=True)
  1813. .distinct()
  1814. )
  1815. # 获取每个托盘的最新明细(按id倒序)
  1816. return (
  1817. ContainerDetailModel.objects
  1818. .filter(container_id__in=container_ids,batch_id=batch_id, status=2,is_delete=False)
  1819. .select_related('container')
  1820. .prefetch_related(
  1821. Prefetch('container__location_links',
  1822. queryset=LocationContainerLink.objects.select_related('location'),
  1823. to_attr='active_location')
  1824. )
  1825. .order_by('container_id', '-id')
  1826. )
  1827. def generate_location_by_check(self,batch_id,container_demand):
  1828. '''
  1829. 根据抽检托盘数目,把相应的库位给到出库任务
  1830. '''
  1831. try:
  1832. return_data = []
  1833. # 获取已去重的托盘列表
  1834. container_qs = self.get_container_allocation(batch_id)
  1835. # 构建托盘信息字典(自动去重)
  1836. container_map = {}
  1837. for cd in container_qs:
  1838. if cd.container_id in container_map:
  1839. continue
  1840. # 获取有效库位信息
  1841. active_location = next(
  1842. (link.location for link in cd.container.active_location
  1843. if link.is_active),
  1844. None
  1845. )
  1846. container_map[cd.container_id] = {
  1847. 'detail': cd,
  1848. 'container': cd.container,
  1849. 'location': active_location
  1850. }
  1851. # 转换为排序列表
  1852. container_list = list(container_map.values())
  1853. sorted_containers = sorted(
  1854. container_list,
  1855. key=lambda x: (
  1856. self._get_goods_class_priority(x['detail'].goods_class),
  1857. -(x['location'].c_number if x['location'] else 0),
  1858. # -(x['location'].layer if x['location'] else 0),
  1859. # x['location'].row if x['location'] else 0,
  1860. # x['location'].col if x['location'] else 0
  1861. )
  1862. )
  1863. for item in sorted_containers:
  1864. if container_demand <= 0:
  1865. break
  1866. # 获取可分配数量
  1867. # 记录分配信息
  1868. allocate_container = {
  1869. "container_number": item['container'].id,
  1870. "batch_id": batch_id,
  1871. "location_code": item['location'].location_code if item['location'] else 'N/A',
  1872. "allocate_qty": 0,
  1873. "c_number": item['location'].c_number if item['location'] else 0
  1874. }
  1875. return_data.append(allocate_container)
  1876. container_demand -= 1
  1877. # 降重 return_data,以container_number为key
  1878. return_data = list({v['container_number']: v for v in return_data}.values())
  1879. # 排序
  1880. return_data = sorted(return_data, key=lambda x: -x['c_number'])
  1881. return {"code": "200", "msg": "Success", "data": return_data}
  1882. except Exception as e:
  1883. logger.error(f"出库任务生成失败: {str(e)}", exc_info=True)
  1884. return {"code": "500", "msg": str(e)}
  1885. def generate_location_by_demand(self, batch_demand, bound_list_id):
  1886. try:
  1887. return_data = []
  1888. for batch_id, demand in batch_demand.items():
  1889. # 获取已去重的托盘列表
  1890. container_qs = self.get_container_allocation(batch_id)
  1891. # 构建托盘信息字典(自动去重)
  1892. container_map = {}
  1893. for cd in container_qs:
  1894. if cd.container_id in container_map:
  1895. container_map[cd.container_id]['goods_qty'] += cd.goods_qty - cd.goods_out_qty
  1896. continue
  1897. # 获取有效库位信息
  1898. active_location = next(
  1899. (link.location for link in cd.container.active_location
  1900. if link.is_active),
  1901. None
  1902. )
  1903. container_map[cd.container_id] = {
  1904. 'detail': cd,
  1905. 'goods_qty': cd.goods_qty - cd.goods_out_qty,
  1906. 'container': cd.container,
  1907. 'location': active_location
  1908. }
  1909. # 转换为排序列表
  1910. container_list = list(container_map.values())
  1911. # 多维度排序(优化性能版)
  1912. sorted_containers = sorted(
  1913. container_list,
  1914. key=lambda x: (
  1915. self._get_goods_class_priority(x['detail'].goods_class),
  1916. -(x['location'].c_number if x['location'] else 0),
  1917. # -(x['location'].layer if x['location'] else 0),
  1918. # x['location'].row if x['location'] else 0,
  1919. # x['location'].col if x['location'] else 0
  1920. )
  1921. )
  1922. # 分配逻辑
  1923. required = demand['required']
  1924. for item in sorted_containers:
  1925. if required <= 0:
  1926. break
  1927. # 获取可分配数量
  1928. allocatable = item['goods_qty']
  1929. allocate_qty = min(required, allocatable)
  1930. # 记录分配信息
  1931. allocate_container = {
  1932. "container_number": item['container'].id,
  1933. "batch_id": batch_id,
  1934. "location_code": item['location'].location_code if item['location'] else 'N/A',
  1935. "allocate_qty": allocate_qty,
  1936. "c_number": item['location'].c_number if item['location'] else 0
  1937. }
  1938. return_data.append(allocate_container)
  1939. required -= allocate_qty
  1940. # 更新数据库状态(需要事务处理)
  1941. self._update_allocation_status(allocate_container, allocate_qty,bound_list_id)
  1942. # 降重 return_data,以container_number为key
  1943. return_data = list({v['container_number']: v for v in return_data}.values())
  1944. # 排序
  1945. return_data = sorted(return_data, key=lambda x: -x['c_number'])
  1946. return {"code": "200", "msg": "Success", "data": return_data}
  1947. except Exception as e:
  1948. logger.error(f"出库任务生成失败: {str(e)}", exc_info=True)
  1949. return {"code": "500", "msg": str(e)}
  1950. def _get_goods_class_priority(self, goods_class):
  1951. """货物类型优先级权重"""
  1952. return {
  1953. 3: 0, # 散盘最高
  1954. 1: 1, # 成品次之
  1955. 2: 2 # 空盘最低
  1956. }.get(goods_class, 99)
  1957. def _update_allocation_status(self, allocate_container, allocate_qty,bound_list_id):
  1958. """事务化更新分配状态"""
  1959. try:
  1960. # 更新托盘明细
  1961. container_detail_all = ContainerDetailModel.objects.filter(
  1962. container_id=allocate_container['container_number'],
  1963. batch_id=allocate_container['batch_id'],
  1964. is_delete=False
  1965. ).all()
  1966. left_qty = 0
  1967. for cd in container_detail_all:
  1968. if left_qty - allocate_qty >= 0:
  1969. break
  1970. add_qty = min(allocate_qty-left_qty, cd.goods_qty - cd.goods_out_qty)
  1971. if add_qty == 0:
  1972. continue
  1973. left_qty += add_qty
  1974. last_out_qty = cd.goods_out_qty
  1975. cd.goods_out_qty += add_qty
  1976. print(f"{left_qty/25} 更新托盘 {cd.container.container_code} 批次 {cd.batch_id} 出库数量: {cd.goods_out_qty}")
  1977. cd.save()
  1978. out_batch_detail.objects.create(
  1979. out_bound_id=bound_list_id,
  1980. container_id=cd.container_id,
  1981. container_detail_id=cd.id,
  1982. out_goods_qty=add_qty,
  1983. last_out_goods_qty = last_out_qty,
  1984. working = 1,
  1985. is_delete = False
  1986. )
  1987. return True
  1988. except Exception as e:
  1989. logger.error(f"状态更新失败: {str(e)}")
  1990. return False
  1991. def create_or_update_container_operation(self,container_obj,batch_id,bound_id,to_location,goods_qty,goods_weight):
  1992. try:
  1993. container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,bound_id=bound_id,operation_type="outbound").first()
  1994. if container_operation_obj:
  1995. logger.info(f"[0]查询出库任务: {container_operation_obj.operation_type} ")
  1996. logger.info(f"更新出库任务: {container_obj.container_code} 批次 {batch_id} 出库需求: {bound_id} 数量: {goods_qty} 重量: {goods_weight}")
  1997. container_operation_obj.to_location = to_location
  1998. container_operation_obj.goods_qty = goods_qty
  1999. container_operation_obj.goods_weight = goods_weight
  2000. container_operation_obj.save()
  2001. else:
  2002. logger.info(f"创建出库任务: {container_obj.container_code} 批次 {batch_id} 出库需求: {bound_id} 数量: {goods_qty} 重量: {goods_weight}")
  2003. batch = BoundBatchModel.objects.filter(id=batch_id).first()
  2004. if not batch:
  2005. return {"code": "500", "msg": f"批次 {batch_id} 不存在"}
  2006. ContainerOperationModel.objects.create(
  2007. month = int(timezone.now().strftime("%Y%m")),
  2008. container = container_obj,
  2009. goods_code = batch.goods_code,
  2010. goods_desc = batch.goods_desc,
  2011. operation_type ="outbound",
  2012. batch_id = batch_id,
  2013. bound_id = bound_id,
  2014. goods_qty = goods_qty,
  2015. goods_weight = goods_weight,
  2016. from_location = container_obj.current_location,
  2017. to_location= to_location,
  2018. timestamp=timezone.now(),
  2019. operator="WMS",
  2020. memo=f"出库需求: {bound_id}, 批次: {batch_id}, 数量: {goods_qty}"
  2021. )
  2022. return {"code": "200", "msg": "Success"}
  2023. except Exception as e:
  2024. return {"code": "500", "msg": str(e)}
  2025. def update_container_detail_out_qty(self,container_obj,batch_id):
  2026. try:
  2027. logger.info(f"[1]更新托盘出库数量: {container_obj.container_code} 批次 {batch_id} ")
  2028. container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,operation_type="outbound").all()
  2029. if not container_operation_obj:
  2030. logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无出库任务")
  2031. return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无出库任务"}
  2032. container_detail_obj = ContainerDetailModel.objects.filter(container=container_obj,batch_id=batch_id,status=2,is_delete=False).first()
  2033. if not container_detail_obj:
  2034. logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无批次信息")
  2035. return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无批次信息"}
  2036. out_qty = 0
  2037. for obj in container_operation_obj:
  2038. out_qty += obj.goods_qty
  2039. if out_qty >= container_detail_obj.goods_qty:
  2040. out_qty = container_detail_obj.goods_qty
  2041. container_detail_obj.status = 3
  2042. break
  2043. if out_qty == 0:
  2044. logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无出库数量")
  2045. return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无出库数量"}
  2046. container_detail_obj.goods_out_qty = out_qty
  2047. container_detail_obj.save()
  2048. return {"code": "200", "msg": "Success"}
  2049. except Exception as e:
  2050. return {"code": "500", "msg": str(e)}
  2051. # 出库任务监测
  2052. class BatchViewSet(viewsets.ModelViewSet):
  2053. authentication_classes = [] # 禁用所有认证类
  2054. permission_classes = [AllowAny] # 允许任意访问
  2055. def wcs_post(self, request, *args, **kwargs):
  2056. data = self.request.data
  2057. logger.info(f"收到 WMS 推送数据: {data}")
  2058. return Response({"code": "200", "msg": "Success"}, status=200)
  2059. # views.py
  2060. class OutDetailViewSet(viewsets.ModelViewSet):
  2061. pagination_class = MyPageNumberPagination
  2062. serializer_class = OutBoundDetailSerializer
  2063. def get_project(self):
  2064. try:
  2065. id = self.kwargs.get('pk')
  2066. return id
  2067. except:
  2068. return None
  2069. def get_queryset(self):
  2070. """根据不同的action调整查询集"""
  2071. if self.action == 'list':
  2072. # 获取每个out_bound的最新一条记录
  2073. # 子查询,用于获取每个out_bound对应的最新out_batch_detail记录
  2074. subquery = out_batch_detail.objects.filter(
  2075. out_bound=OuterRef('out_bound')
  2076. ).order_by('-id')
  2077. # 返回最新的out_batch_detail记录,通过子查询的结果进行过滤
  2078. return out_batch_detail.objects.filter(
  2079. id=Subquery(subquery.values('id')[:1])
  2080. )
  2081. return out_batch_detail.objects.all()
  2082. def retrieve(self, request, *args, **kwargs):
  2083. """重写retrieve方法返回关联集合"""
  2084. qs = self.get_project()
  2085. queryset = self.filter_queryset(
  2086. out_batch_detail.objects.filter(out_bound = qs)
  2087. )
  2088. # 分页处理
  2089. # page = self.paginate_queryset(queryset)
  2090. # if queryset is not None:
  2091. # serializer = self.get_serializer(queryset, many=True)
  2092. # return self.get_paginated_response(serializer.data)
  2093. serializer = self.get_serializer(queryset, many=True)
  2094. return Response(serializer.data)
  2095. def get_serializer_class(self):
  2096. """根据action切换序列化器"""
  2097. if self.action == 'retrieve':
  2098. return OutBoundFullDetailSerializer
  2099. return super().get_serializer_class()
  2100. def get_out_batch_detail(self, request):
  2101. """获取某个托盘的出库明细"""
  2102. try:
  2103. container_code = request.query_params.get('container_code')
  2104. container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
  2105. if not container_obj:
  2106. return Response({"code": "500", "message": f"托盘 {container_code} 不存在"}, status=status.HTTP_200_OK)
  2107. out_batch_detail_all = out_batch_detail.objects.filter(container=container_obj,working=1,is_delete=False).all()
  2108. if not out_batch_detail_all:
  2109. return Response({"code": "500", "message": f"托盘 {container_code} 无出库明细"}, status=status.HTTP_200_OK)
  2110. serializer = OutBoundFullDetailSerializer(out_batch_detail_all, many=True)
  2111. return Response({"code": "200", "message": "Success", "data": serializer.data}, status=status.HTTP_200_OK)
  2112. except Exception as e:
  2113. return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)
  2114. def confirm_out_batch_detail(self, request):
  2115. """确认出库"""
  2116. try:
  2117. container_code = request.query_params.get('container_code')
  2118. container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
  2119. if not container_obj:
  2120. return Response({"code": "500", "message": f"托盘 {container_code} 不存在"}, status=status.HTTP_200_OK)
  2121. out_batch_detail_all = out_batch_detail.objects.filter(container=container_obj,working=1,is_delete=False).all()
  2122. if not out_batch_detail_all:
  2123. return Response({"code": "500", "message": f"托盘 {container_code} 无出库明细"}, status=status.HTTP_200_OK)
  2124. for obj in out_batch_detail_all:
  2125. obj.working = 0
  2126. obj.save()
  2127. BatchOperateLogModel.objects.create(
  2128. batch_id = obj.container_detail.batch,
  2129. log_type = 1,
  2130. log_date = timezone.now(),
  2131. goods_code = obj.container_detail.batch.goods_code,
  2132. goods_desc = obj.container_detail.batch.goods_desc,
  2133. goods_qty = obj.out_goods_qty,
  2134. log_content = f"出库托盘 {container_code} 批次 {obj.container_detail.batch.id} 数量 {obj.out_goods_qty}",
  2135. creater = "WMS",
  2136. openid = "WMS"
  2137. )
  2138. return Response({"code": "200", "message": "出库成功"}, status=status.HTTP_200_OK)
  2139. except Exception as e:
  2140. return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)
  2141. def cancel_out_batch_detail(self, request):
  2142. """取消出库"""
  2143. try:
  2144. container_code = request.query_params.get('container_code')
  2145. container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
  2146. if not container_obj:
  2147. return Response({"code": "500", "message": f"托盘 {container_code} 不存在"}, status=status.HTTP_200_OK)
  2148. out_batch_detail_all = out_batch_detail.objects.filter(container=container_obj,working=1,is_delete=False).order_by('-id').all()
  2149. if not out_batch_detail_all:
  2150. return Response({"code": "500", "message": f"托盘 {container_code} 无出库明细"}, status=status.HTTP_200_OK)
  2151. for obj in out_batch_detail_all:
  2152. obj.container_detail.goods_out_qty = obj.last_out_goods_qty
  2153. # print(f"取消出库托盘 {container_code} 批次 {obj.container_detail.batch.id},详情 {obj.container_detail.id} 变化前数量 {obj.container_detail.goods_out_qty} 变化后数量 {obj.last_out_goods_qty}")
  2154. obj.container_detail.save()
  2155. obj.is_delete = True
  2156. obj.working = 0
  2157. obj.save()
  2158. return Response({"code": "200", "message": "出库取消成功"}, status=status.HTTP_200_OK)
  2159. except Exception as e:
  2160. return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)
  2161. def get_contianer_detail(self, request):
  2162. try:
  2163. container_code = request.query_params.get('container_code')
  2164. container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
  2165. if not container_obj:
  2166. return Response({"code": "500", "message": f"托盘 {container_code} 不存在"}, status=status.HTTP_200_OK)
  2167. container_detail_all = ContainerDetailModel.objects.filter(container=container_obj,is_delete=False).all().exclude(status__in=[3])
  2168. return_data=[]
  2169. for obj in container_detail_all:
  2170. return_data.append({
  2171. "id": obj.id,
  2172. "batch": obj.batch.bound_number,
  2173. "goods_code": obj.goods_code,
  2174. "goods_desc": obj.goods_desc,
  2175. "goods_qty" :obj.goods_qty,
  2176. "out_goods_qty": obj.goods_out_qty
  2177. })
  2178. return Response({"code": "200", "message": "Success", "data": return_data}, status=status.HTTP_200_OK)
  2179. except Exception as e:
  2180. return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)
  2181. def change_container_out_qty(self, request):
  2182. try:
  2183. container_code = request.data.get('container_code')
  2184. container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
  2185. if not container_obj:
  2186. return Response({"code": "500", "message": f"托盘 {container_code} 不存在"}, status=status.HTTP_200_OK)
  2187. logger.info(f"change_container_out_qty: {request.data}")
  2188. for container_detail_id, out_qty in request.data.get('detail_list').items():
  2189. container_detail_obj = ContainerDetailModel.objects.filter(id=container_detail_id,is_delete=False).first()
  2190. if not container_detail_obj:
  2191. continue
  2192. from decimal import Decimal
  2193. out_qty = Decimal(out_qty)
  2194. container_detail_obj.goods_out_qty += out_qty
  2195. container_detail_obj.save()
  2196. return Response({"code": "200", "message": "Success"}, status=status.HTTP_200_OK)
  2197. except Exception as e:
  2198. return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)