123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938 |
- from wsgiref import headers
- from rest_framework.views import APIView
- from rest_framework import viewsets
- from utils.page import MyPageNumberPagination
- from django.db.models import Prefetch
- from rest_framework.filters import OrderingFilter
- from django_filters.rest_framework import DjangoFilterBackend
- from rest_framework.response import Response
- from django.db.models import F, Case, When
- from django.db.models import OuterRef, Subquery
- from django.utils import timezone
- import requests
- from django.db import transaction
- import logging
- from rest_framework import status
- from .models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,ContainerWCSModel,TaskModel,out_batch_detail
- from bound.models import BoundDetailModel,BoundListModel,OutBoundDetailModel
- from bin.views import LocationAllocation,base_location
- from bin.models import LocationModel,LocationContainerLink,LocationGroupModel
- from bound.models import BoundBatchModel,OutBatchModel,BatchLogModel
- from .serializers import ContainerDetailGetSerializer,ContainerDetailPostSerializer,ContainerDetailSimpleGetSerializer
- from .serializers import ContainerListGetSerializer,ContainerListPostSerializer
- from .serializers import ContainerOperationGetSerializer,ContainerOperationPostSerializer
- from .serializers import TaskGetSerializer,TaskPostSerializer
- from .serializers import WCSTaskGetSerializer
- from .serializers import OutBoundFullDetailSerializer,OutBoundDetailSerializer
- from .filter import ContainerDetailFilter,ContainerListFilter,ContainerOperationFilter,TaskFilter,WCSTaskFilter
- from rest_framework.permissions import AllowAny
- import threading
- from django.db import close_old_connections
- from bin.services import AllocationService
- from collections import defaultdict
- logger = logging.getLogger(__name__)
- # 托盘列表视图
- class ContainerListViewSet(viewsets.ModelViewSet):
- """
- retrieve:
- Response a data list(get)
- list:
- Response a data list(all)
- create:
- Create a data line(post)
- delete:
- Delete a data line(delete)
- """
- # authentication_classes = [] # 禁用所有认证类
- # permission_classes = [AllowAny] # 允许任意访问
-
- pagination_class = MyPageNumberPagination
- filter_backends = [DjangoFilterBackend, OrderingFilter, ]
- ordering_fields = ['id', "create_time", "update_time", ]
- filter_class = ContainerListFilter
- def get_project(self):
- try:
- id = self.kwargs.get('pk')
- return id
- except:
- return None
- def get_queryset(self):
- id = self.get_project()
- if self.request.user:
- if id is None:
- return ContainerListModel.objects.filter()
- else:
- return ContainerListModel.objects.filter(id=id)
- else:
- return ContainerListModel.objects.none()
- def get_serializer_class(self):
- if self.action in ['list', 'destroy','retrieve']:
- return ContainerListGetSerializer
- elif self.action in ['create', 'update']:
- return ContainerListPostSerializer
- else:
- return self.http_method_not_allowed(request=self.request)
- def create(self, request, *args, **kwargs):
- # 创建托盘:托盘码五位数字(唯一),当前库位,目标库位,状态,最后操作时间
- container_all = ContainerListModel.objects.all().order_by('container_code')
- if container_all.count() == 0:
- container_code = 12345
- else:
- container_code = container_all.last().container_code + 1
- container_obj = ContainerListModel.objects.create(
- container_code=container_code,
- current_location='N/A',
- target_location='N/A',
- status=0,
- last_operation=timezone.now()
- )
- serializer = ContainerListGetSerializer(container_obj)
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=201, headers=headers)
-
-
- def update(self, request, pk):
- qs = self.get_object()
- data = self.request.data
- serializer = self.get_serializer(qs, data=data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=200, headers=headers)
- # wcs任务视图
- class WCSTaskViewSet(viewsets.ModelViewSet):
- """
- retrieve:
- Response a data list(get)
- list:
- Response a data list(all)
- create:
- Create a data line(post)
- delete:
- Delete a data line(delete)
- """
- # authentication_classes = [] # 禁用所有认证类
- # permission_classes = [AllowAny] # 允许任意访问
- pagination_class = MyPageNumberPagination
- filter_backends = [DjangoFilterBackend, OrderingFilter, ]
- ordering_fields = ['-id', "-create_time", "update_time", ]
- filter_class = WCSTaskFilter
-
- def get_project(self):
- try:
- id = self.kwargs.get('pk')
- return id
- except:
- return None
-
- def get_queryset(self):
- id = self.get_project()
- if self.request.user:
- if id is None:
- return ContainerWCSModel.objects.filter()
- else:
- return ContainerWCSModel.objects.filter(id=id)
- else:
- return ContainerWCSModel.objects.none()
- def get_serializer_class(self):
- if self.action in ['list', 'destroy','retrieve']:
- return WCSTaskGetSerializer
- else:
- return self.http_method_not_allowed(request=self.request)
- # 入库任务视图
- class TaskViewSet(viewsets.ModelViewSet):
- """
- retrieve:
- Response a data list(get)
- list:
- Response a data list(all)
- create:
- Create a data line(post)
- delete:
- Delete a data line(delete)
- """
- pagination_class = MyPageNumberPagination
- filter_backends = [DjangoFilterBackend, OrderingFilter, ]
- ordering_fields = ['id', "create_time", "update_time", ]
- filter_class = TaskFilter
- def get_project(self):
- try:
- id = self.kwargs.get('pk')
- return id
- except:
- return None
- def get_queryset(self):
- id = self.get_project()
- if self.request.user:
- if id is None:
- return TaskModel.objects.filter()
- else:
- return TaskModel.objects.filter(id=id)
- else:
- return TaskModel.objects.none()
- def get_serializer_class(self):
- if self.action in ['list', 'destroy','retrieve']:
- return TaskGetSerializer
- elif self.action in ['create', 'update']:
- return TaskPostSerializer
- else:
- return self.http_method_not_allowed(request=self.request)
- def create(self, request, *args, **kwargs):
- data = self.request.data
- return Response(data, status=200, headers=headers)
-
- def update(self, request, pk):
- qs = self.get_object()
- data = self.request.data
- serializer = self.get_serializer(qs, data=data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=200, headers=headers)
- # 任务回滚
- class TaskRollbackMixin:
- @transaction.atomic
- def rollback_task(self, request, task_id, *args, **kwargs):
- """
- 撤销入库任务并回滚相关状态
- """
- try:
- # 获取任务实例并锁定数据库记录
- task = ContainerWCSModel.objects.select_for_update().get(taskid=task_id)
- container_code = task.container
- target_location = task.target_location
- batch = task.batch
- # 初始化库位分配器
- allocator = LocationAllocation()
- # ==================== 库位状态回滚 ====================
- # 解析目标库位信息(格式:仓库代码-行-列-层)
- try:
- warehouse_code, row, col, layer = target_location.split('-')
- location = LocationModel.objects.get(
- warehouse_code=warehouse_code,
- row=int(row),
- col=int(col),
- layer=int(layer)
- )
-
- # 回滚库位状态到可用状态
- allocator.update_location_status(location.location_code, 'available')
-
- # 更新库位组状态(需要根据实际逻辑实现)
- allocator.update_location_group_status(location.location_code)
-
- # 解除库位与托盘的关联
- allocator.update_location_container_link(location.location_code, None)
-
- # 清除库位组的批次关联
- allocator.update_location_group_batch(location, None)
- except (ValueError, LocationModel.DoesNotExist) as e:
- logger.error(f"库位解析失败: {str(e)}")
- raise Exception("关联库位信息无效")
- # ==================== 批次状态回滚 ====================
- if batch:
- # 将批次状态恢复为未处理状态(假设原状态为1)
- allocator.update_batch_status(batch.bound_number, '1')
- # ==================== 容器状态回滚 ====================
- container_obj = ContainerListModel.objects.get(container_code=container_code)
-
- # 恢复容器详细状态为初始状态(假设原状态为1)
- allocator.update_container_detail_status(container_code, 1)
-
- # 恢复容器的目标位置为当前所在位置
- container_obj.target_location = task.current_location
- container_obj.save()
- # ==================== 删除任务记录 ====================
- task.delete()
- # ==================== 其他关联清理 ====================
- # 如果有其他关联数据(如inport_update_task的操作),在此处添加清理逻辑
- return Response(
- {'code': '200', 'message': '任务回滚成功', 'data': None},
- status=status.HTTP_200_OK
- )
- except ContainerWCSModel.DoesNotExist:
- logger.warning(f"任务不存在: {task_id}")
- return Response(
- {'code': '404', 'message': '任务不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
- except Exception as e:
- logger.error(f"任务回滚失败: {str(e)}", exc_info=True)
- return Response(
- {'code': '500', 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
- # 入库任务下发
- class ContainerWCSViewSet(viewsets.ModelViewSet):
- """
- retrieve:
- Response a data list(get)
- list:
- Response a data list(all)
- create:
- Create a data line(post)
- delete:
- Delete a data line(delete)
- """
- authentication_classes = [] # 禁用所有认证类
- permission_classes = [AllowAny] # 允许任意访问
-
- def get_container_wcs(self, request, *args, **kwargs):
- data = self.request.data
- container = data.get('container_number')
- current_location = data.get('current_location')
- logger.info(f"请求托盘:{container},请求位置:{current_location}")
- data_return = {}
- try:
- container_obj = ContainerListModel.objects.filter(container_code=container).first()
- if not container_obj:
- data_return = {
- 'code': '400',
- 'message': '托盘编码不存在',
- 'data': data
- }
- return Response(data_return, status=status.HTTP_400_BAD_REQUEST)
- # 更新容器数据(部分更新)
- serializer = ContainerListPostSerializer(
- container_obj,
- data=data,
- partial=True # 允许部分字段更新
- )
- serializer.is_valid(raise_exception=True)
- serializer.save()
-
- # 检查是否已在目标位置
- if current_location == str(container_obj.target_location) and current_location!= '203' and current_location!= '103':
- logger.info(f"托盘 {container} 已在目标位置")
- data_return = {
- 'code': '200',
- 'message': '当前位置已是目标位置',
- 'data': data
- }
- else:
- current_task = ContainerWCSModel.objects.filter(
- container=container,
- tasktype='inbound',
- working = 1,
-
- ).exclude(status=300).first()
- if current_task:
-
- data_return = {
- 'code': '200',
- 'message': '任务已存在,重新下发',
- 'data': current_task.to_dict()
- }
- else:
- # todo: 这里的入库操作记录里面的记录的数量不对
- location_min_value,allocation_target_location, batch_info = AllocationService.allocate(container, current_location)
- batch_id = batch_info['number']
- if batch_info['class'] == 2:
- self.generate_task_no_batch(container, current_location, allocation_target_location,batch_id,location_min_value.c_number)
- self.generate_container_operate_no_batch(container_obj, batch_id, allocation_target_location)
- elif batch_info['class'] == 3:
- self.generate_task_no_batch(container, current_location, allocation_target_location,batch_id,location_min_value.c_number)
- self.generate_container_operate_no_batch(container_obj, batch_id, allocation_target_location)
- else:
- self.generate_task(container, current_location, allocation_target_location,batch_id,location_min_value.c_number) # 生成任务
- self.generate_container_operate(container_obj, batch_id, allocation_target_location)
- current_task = ContainerWCSModel.objects.get(
- container=container,
- tasktype='inbound',
- working=1,
- )
- data_return = {
- 'code': '200',
- 'message': '任务下发成功',
- 'data': current_task.to_dict()
- }
- container_obj.target_location = allocation_target_location
- container_obj.save()
- if batch_info['class'] == 1 or batch_info['class'] == 3:
- self.inport_update_task(current_task.id, container_obj.id)
- http_status = status.HTTP_200_OK if data_return['code'] == '200' else status.HTTP_400_BAD_REQUEST
- return Response(data_return, status=http_status)
- except Exception as e:
- logger.error(f"处理请求时发生错误: {str(e)}", exc_info=True)
- return Response(
- {'code': '500', 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
- @transaction.atomic
- def generate_container_operate(self, container_obj, bound_number,allocation_target_location):
- batch_obj = BoundBatchModel.objects.filter(bound_number=bound_number).first()
- ContainerOperationModel.objects.create(
- month = int(timezone.now().strftime("%Y%m")),
- container = container_obj,
- goods_code = batch_obj.goods_code,
- goods_desc = batch_obj.goods_desc,
- operation_type ="inbound",
- batch_id = batch_obj.id,
-
- goods_qty = batch_obj.goods_qty,
- goods_weight = batch_obj.goods_qty,
- from_location = container_obj.current_location,
- to_location= allocation_target_location,
- timestamp=timezone.now(),
- operator="WMS",
- memo=f"WCS入库: 批次: {bound_number}, 数量: {batch_obj.goods_qty}"
- )
- @transaction.atomic
- def generate_container_operate_no_batch(self, container_obj, bound_number,allocation_target_location):
-
- ContainerOperationModel.objects.create(
- month = int(timezone.now().strftime("%Y%m")),
- container = container_obj,
- goods_code = 'container',
- goods_desc = '托盘组',
- operation_type ="inbound",
- goods_qty = 1,
- goods_weight = 0,
- from_location = container_obj.current_location,
- to_location= allocation_target_location,
- timestamp=timezone.now(),
- memo=f"WCS入库: 批次: {bound_number}, 数量: 1"
- )
- @transaction.atomic
- def generate_task(self, container, current_location, target_location,batch_id,location_c_number):
- batch = BoundBatchModel.objects.filter(bound_number=batch_id).first()
- batch_detail = BoundDetailModel.objects.filter(bound_batch=batch).first()
- if not batch:
- logger.error(f"批次号 {batch_id} 不存在")
- return False
- data_tosave = {
- 'container': container,
- 'batch': batch,
- 'batch_number': batch_id,
- 'batch_out': None,
- 'bound_list': batch_detail.bound_list,
- 'sequence': 1,
- 'order_number' :location_c_number,
- 'priority': 1,
- 'current_location': current_location,
- 'month': timezone.now().strftime('%Y%m'),
- 'target_location': target_location,
- 'tasktype': 'inbound',
- 'status': 103,
- 'is_delete': False
- }
- # 生成唯一递增的 taskid
- last_task = ContainerWCSModel.objects.filter(
- month=data_tosave['month'],
- ).order_by('-tasknumber').first()
- if last_task:
-
- number_id = last_task.tasknumber + 1
- new_id = f"{number_id:05d}"
- else:
- new_id = "00001"
- number_id = f"{data_tosave['month']}{new_id}"
-
- data_tosave['taskid'] = f"inbound-{data_tosave['month']}-{new_id}"
- logger.info(f"生成入库任务: {data_tosave['taskid']}")
- # 每月生成唯一递增的 taskNumber
- data_tosave['tasknumber'] = number_id
- ContainerWCSModel.objects.create(**data_tosave)
- def generate_task_no_batch(self, container, current_location, target_location,batch_id,location_c_number):
-
-
- data_tosave = {
- 'container': container,
- 'batch': None,
- 'batch_number': batch_id,
- 'batch_out': None,
- 'bound_list': None,
- 'sequence': 1,
- 'order_number' :location_c_number,
- 'priority': 1,
- 'current_location': current_location,
- 'month': timezone.now().strftime('%Y%m'),
- 'target_location': target_location,
- 'tasktype': 'inbound',
- 'status': 103,
- 'is_delete': False
- }
- # 生成唯一递增的 taskid
- last_task = ContainerWCSModel.objects.filter(
- month=data_tosave['month'],
- ).order_by('-tasknumber').first()
- if last_task:
-
- number_id = last_task.tasknumber + 1
- new_id = f"{number_id:05d}"
- else:
- new_id = "00001"
- number_id = f"{data_tosave['month']}{new_id}"
-
- data_tosave['taskid'] = f"inbound-{data_tosave['month']}-{new_id}"
- logger.info(f"生成入库任务: {data_tosave['taskid']}")
- # 每月生成唯一递增的 taskNumber
- data_tosave['tasknumber'] = number_id
- ContainerWCSModel.objects.create(**data_tosave)
-
- def update_container_wcs(self, request, *args, **kwargs):
- data = self.request.data
- logger.info(f"请求托盘:{data.get('container_number')}, 请求位置:{data.get('current_location')}, 任务号:{data.get('taskNumber')}")
- try:
- # 前置校验
- container_obj, error_response = self.validate_container(data)
- if error_response:
- return error_response
- # 更新容器数据
- if not self.update_container_data(container_obj, data):
- return Response(
- {'code': '400', 'message': '数据更新失败', 'data': data},
- status=status.HTTP_400_BAD_REQUEST
- )
- # 处理位置逻辑
- task = ContainerWCSModel.objects.filter(
- container=container_obj.container_code,
- tasktype='inbound'
- ).first()
- if self.is_already_at_target(container_obj, data.get('current_location')):
- return self.handle_target_reached(container_obj, data)
- elif task:
- data_return = {
- 'code': '200',
- 'message': '任务已存在,重新下发',
- 'data': task.to_dict()
- }
- return Response(data_return, status=status.HTTP_200_OK)
- else:
- return self.handle_new_allocation(container_obj, data)
- except Exception as e:
- logger.error(f"处理请求时发生错误: {str(e)}", exc_info=True)
- return Response({'code': '500', 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR)
- # ---------- 辅助函数 ----------
- def validate_container(self, data):
- """验证容器是否存在"""
- container = data.get('container_number')
- container_obj = ContainerListModel.objects.filter(container_code=container).first()
- if not container_obj:
- return None, Response({
- 'code': '400',
- 'message': '托盘编码不存在',
- 'data': data
- }, status=status.HTTP_400_BAD_REQUEST)
- return container_obj, None
- def update_container_data(self, container_obj, data):
- """更新容器数据"""
- serializer = ContainerListPostSerializer(
- container_obj,
- data=data,
- partial=True
- )
- if serializer.is_valid():
- serializer.save()
- return True
- return False
- def is_already_at_target(self, container_obj, current_location):
- """检查是否已在目标位置"""
- print (current_location)
- print (str(container_obj.target_location))
- return current_location == str(container_obj.target_location)
- def handle_target_reached(self, container_obj, data):
- """处理已到达目标位置的逻辑"""
- logger.info(f"托盘 {container_obj.container_code} 已在目标位置")
- task = self.get_task_by_tasknumber(data)
- self.update_pressure_values(task, container_obj)
- # if task.working == 1:
- # alloca = LocationAllocation()
- # alloca.update_batch_goods_in_location_qty(container_obj.container_code, 1)
- task = self.process_task_completion(data)
- if not task:
- return Response({'code': '400', 'message': '任务不存在', 'data': data},
- status=status.HTTP_400_BAD_REQUEST)
- if task and task.tasktype == 'inbound':
- self.update_storage_system(container_obj)
-
- if task and task.tasktype == 'outbound' and task.status == 300:
- success = self.handle_outbound_completion(container_obj, task)
- if not success:
- return Response({'code': '500', 'message': '出库状态更新失败', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR)
- OutboundService.process_next_task()
-
- return Response({
- 'code': '200',
- 'message': '当前位置已是目标位置',
- 'data': data
- }, status=status.HTTP_200_OK)
- def get_task_by_tasknumber(self, data):
-
- taskNumber = data.get('taskNumber') + 20000000000
- task = ContainerWCSModel.objects.filter(tasknumber=taskNumber).first()
- if task:
- return task
- else:
- return None
- def process_task_completion(self, data):
- """处理任务完成状态"""
- taskNumber = data.get('taskNumber') + 20000000000
- task = ContainerWCSModel.objects.filter(tasknumber=taskNumber).first()
- if task:
- task.status = 300
- task.message = '任务已完成'
- task.working = 0
- task.save()
- return task
- def update_pressure_values(self, task, container_obj):
- """更新压力值计算"""
- if task :
- base_location_obj = base_location.objects.get(id=1)
- layer = int(container_obj.target_location.split('-')[-1])
- pressure_field = f"layer{layer}_pressure"
- logger.info(f"更新压力值,压力字段:{pressure_field}")
- current_pressure = getattr(base_location_obj, pressure_field, 0)
- updated_pressure = max(current_pressure - task.working, 0)
- setattr(base_location_obj, pressure_field, updated_pressure)
- base_location_obj.save()
- def update_storage_system(self, container_obj):
- """更新仓储系统状态"""
- allocator = LocationAllocation()
- location_code = self.get_location_code(container_obj.target_location)
-
- # 链式更新操作
- update_operations = [
- (allocator.update_location_status, location_code, 'occupied'),
- (allocator.update_location_container_link, location_code, container_obj.container_code),
- (allocator.update_container_detail_status, container_obj.container_code, 2)
- ]
-
- for func, *args in update_operations:
- if not func(*args):
- logger.error(f"操作失败: {func.__name__}")
- return False
- return True
- def get_location_code(self, target_location):
- """从目标位置解析获取位置编码"""
- parts = target_location.split('-')
- coordinate = f"{int(parts[1])}-{int(parts[2])}-{int(parts[3])}"
- return LocationModel.objects.filter(coordinate=coordinate).first().location_code
- def handle_new_allocation(self, container_obj, data):
- """处理新库位分配逻辑"""
- allocator = LocationAllocation()
- container_code = container_obj.container_code
-
- # 获取并验证库位分配
- location = allocator.get_location_by_status(container_code, data.get('current_location'))
- if not location or not self.perform_initial_allocation(allocator, location, container_code):
- return Response({'code': '400', 'message': '库位分配失败', 'data': data},
- status=status.HTTP_400_BAD_REQUEST)
-
- # 生成目标位置并更新容器
- target_location = self.generate_target_location(location)
- container_obj.target_location = target_location
- container_obj.save()
-
- # 创建任务并返回响应
- task = self.create_inbound_task(container_code, data, target_location, location)
- return Response({
- 'code': '200',
- 'message': '任务下发成功',
- 'data': task.to_dict()
- }, status=status.HTTP_200_OK)
- def perform_initial_allocation(self, allocator, location, container_code):
- """执行初始库位分配操作"""
- operations = [
- (allocator.update_location_status, location.location_code, 'reserved'),
- (allocator.update_location_group_status, location.location_code),
- (allocator.update_batch_status, container_code, '2'),
- (allocator.update_location_group_batch, location, container_code),
- (allocator.update_location_container_link, location.location_code, container_code),
- (allocator.update_container_detail_status, container_code, 2)
- ]
-
- for func, *args in operations:
- if not func(*args):
- logger.error(f"分配操作失败: {func.__name__}")
- return False
- return True
- def generate_target_location(self, location):
- """生成目标位置字符串"""
- return (
- f"{location.warehouse_code}-"
- f"{int(location.row):02d}-"
- f"{int(location.col):02d}-"
- f"{int(location.layer):02d}"
- )
- def create_inbound_task(self, container_code, data, target_location, location):
- """创建入库任务"""
- batch_id = LocationAllocation().get_batch(container_code)
- self.generate_task(
- container_code,
- data.get('current_location'),
- target_location,
- batch_id,
- location.c_number
- )
- task = ContainerWCSModel.objects.get(container=container_code, tasktype='inbound')
- self.inport_update_task(task.id, container_code)
- return task
- @transaction.atomic
- def inport_update_task(self, wcs_id,container_id):
- try:
- task_obj = ContainerWCSModel.objects.filter(id=wcs_id).first()
- if task_obj:
- container_detail_obj = ContainerDetailModel.objects.filter(container=container_id,is_delete=False).all()
- if container_detail_obj:
- for detail in container_detail_obj:
- # 保存到数据库
- batch = BoundDetailModel.objects.filter(bound_batch_id=detail.batch.id).first()
- TaskModel.objects.create(
- task_wcs = task_obj,
- container_detail = detail,
- batch_detail = batch
- )
- logger.info(f"入库任务 {wcs_id} 已更新")
- else:
- logger.info(f"入库任务 {container_id} 批次不存在")
- else:
- logger.info(f"入库任务 {wcs_id} 不存在")
- except Exception as e:
- logger.error(f"处理入库任务时发生错误: {str(e)}", exc_info=True)
- return Response(
- {'code': '500', 'message': '服务器内部错误', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
- def handle_outbound_completion(self, container_obj, task):
- """处理出库完成后的库位释放和状态更新"""
- try:
- allocator = LocationAllocation()
- location_task = task.current_location
- location_row = location_task.split('-')[1]
- location_col = location_task.split('-')[2]
- location_layer = location_task.split('-')[3]
- location= LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first()
- location_code = location.location_code
-
- # 事务确保原子性
- with transaction.atomic():
- # 解除库位与托盘的关联
- if not allocator.release_location(location_code):
- raise Exception("解除库位关联失败")
- # 更新库位状态为可用
- if not allocator.update_location_status(location_code, 'available'):
- raise Exception("库位状态更新失败")
- # 更新库位组的统计信息
- self.handle_group_location_status(location_code, location.location_group)
- # 更新容器状态为已出库(假设状态3表示已出库)
- container_obj.status = 3
- container_obj.save()
- return True
- except Exception as e:
- logger.error(f"出库完成处理失败: {str(e)}")
- return False
- def handle_group_location_status(self,location_code,location_group):
- """
- 处理库位组和库位的关联关系
- :param location_code: 库位编码
- :param location_group: 库位组编码
- :return:
- """
- # 1. 获取库位空闲状态的库位数目
- location_obj_number = LocationModel.objects.filter(
- location_group=location_group,
- status='available'
- ).all().count()
- # 2. 获取库位组对象
- logger.info(f"库位组 {location_group} 下的库位数目:{location_obj_number}")
- # 1. 获取库位和库位组的关联关系
- location_group_obj = LocationGroupModel.objects.filter(
- group_code=location_group
- ).first()
- if not location_group_obj:
- logger.info(f"库位组 {location_group} 不存在")
- return None
- else:
- if location_obj_number == 0:
- # 库位组库位已满,更新库位组状态为full
- location_group_obj.status = 'full'
- location_group_obj.save()
- elif location_obj_number < location_group_obj.max_capacity:
- location_group_obj.status = 'occupied'
- location_group_obj.save()
- else:
- location_group_obj.status = 'available'
- location_group_obj.current_batch = ''
- location_group_obj.current_goods_code = ''
- location_group_obj.save()
- # PDA组盘入库 将扫描到的托盘编码和批次信息保存到数据库
- # 1. 先查询托盘对象,如果不存在,则创建托盘对象
- # 2. 循环处理每个批次,查询批次对象,
- # 3. 更新批次数据(根据业务规则)
- # 4. 保存到数据库
- # 5. 保存操作记录到数据库
- class ContainerDetailViewSet(viewsets.ModelViewSet):
- """
- retrieve:
- Response a data list(get)
- list:
- Response a data list(all)
- create:
- Create a data line(post)
- delete:
- Delete a data line(delete)
- """
- # authentication_classes = [] # 禁用所有认证类
- # permission_classes = [AllowAny] # 允许任意访问
-
- pagination_class = MyPageNumberPagination
- filter_backends = [DjangoFilterBackend, OrderingFilter, ]
- ordering_fields = ['id', "create_time", "update_time", ]
- filter_class = ContainerDetailFilter
- def get_project(self):
- try:
- id = self.kwargs.get('pk')
- return id
- except:
- return None
- def get_queryset(self):
- id = self.get_project()
- if self.request.user:
- if id is None:
- return ContainerDetailModel.objects.filter( is_delete=False)
- else:
- return ContainerDetailModel.objects.filter( id=id, is_delete=False)
- else:
- return ContainerDetailModel.objects.none()
- def get_serializer_class(self):
- if self.action in ['list', 'destroy','retrieve']:
- return ContainerDetailGetSerializer
- elif self.action in ['create', 'update']:
- return ContainerDetailPostSerializer
- else:
- return self.http_method_not_allowed(request=self.request)
- def create(self, request, *args, **kwargs):
- data = self.request.data
- from .container_operate import ContainerService
- ContainerService.create_container_operation(data,logger=logger)
- # 将处理后的数据返回(或根据业务需求保存到数据库)
- res_data={
- "code": "200",
- "msg": "Success Create",
- "data": data
- }
- return Response(res_data, status=200)
- def update(self, request, pk):
- qs = self.get_object()
- data = self.request.data
- serializer = self.get_serializer(qs, data=data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=200, headers=headers)
-
- def destroy(self, request, pk):
- qs = self.get_object()
- qs.is_delete = True
- qs.save()
- return Response({'code': 200,'message': '删除成功', 'data': None}, status=200)
- def containerdetail_list(self, request):
- """
- 获取容器详情列表
- """
- try:
- container_id = request.query_params.get('container')
- if not container_id:
- return Response(
- {'code': 400, 'message': '缺少容器ID参数', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
- # 获取容器对象
- try:
- container = ContainerListModel.objects.get(id=container_id)
- except ContainerListModel.DoesNotExist:
- return Response(
- {'code': 404, 'message': '指定容器不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
- # 查询关联批次明细(排除状态0和3)
- details = ContainerDetailModel.objects.filter(
- container=container,is_delete=False
- ).exclude(
- status__in=[0, 3]
- ).select_related('batch')
-
- details_serializer = ContainerDetailSimpleGetSerializer(details, many=True)
- return Response(
- {'code': 200, 'message': 'Success', 'data': details_serializer.data},
- status=status.HTTP_200_OK
- )
- except Exception as e:
- return Response(
- {'code': 500, 'message': f'服务器错误: {str(e)}', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
-
- def locationdetail_list(self, request):
- """
- 获取库位所处托盘的信息(按批次号 + 数量分组)
- 新增批次总量统计功能
- """
- try:
- container_id = request.query_params.get('container')
- if not container_id:
- return Response(
- {'code': 400, 'message': '缺少容器ID参数', 'data': None},
- status=status.HTTP_400_BAD_REQUEST
- )
- # 获取容器对象
- try:
- container = ContainerListModel.objects.get(id=container_id)
- except ContainerListModel.DoesNotExist:
- return Response(
- {'code': 404, 'message': '指定容器不存在', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
- # 查询关联批次明细(排除状态0和3)
- details = ContainerDetailModel.objects.filter(
- container=container,is_delete=False
- ).exclude(
- status__in=[0, 3]
- ).select_related('batch')
- if not details.exists():
- return Response(
- {'code': 404, 'message': '未找到有效批次数据', 'data': None},
- status=status.HTTP_404_NOT_FOUND
- )
- # 按批次号 + 数量分组统计
- batch_dict = {}
- batch_qty_dict = defaultdict(int) # 使用默认字典自动初始化
- for detail in details:
- if not detail.batch:
- continue
- bound_number = detail.batch.bound_number
- goods_qty = detail.goods_qty - detail.goods_out_qty # 剔除出库数量
- # 组合键:批次号 + 当前数量
- batch_key = (bound_number, goods_qty)
-
-
- batch_qty_dict[bound_number] += goods_qty # 自动处理键初始化
- # 分组统计
- if batch_key not in batch_dict:
- batch_obj = BoundBatchModel.objects.filter( bound_number=bound_number).first()
- batch_dict[batch_key] = {
- "goods_code": detail.goods_code,
- "goods_desc": detail.goods_desc,
- "goods_qty": goods_qty,
- "goods_class": detail.goods_class,
- "goods_package": batch_obj.goods_package,
- "batch_total_qty": batch_obj.goods_qty,
- "batch_total_in_qty": batch_obj.goods_in_qty - batch_obj.goods_out_qty,
- "status": detail.status,
- "group_qty": 1,
- "create_time": detail.create_time,
- }
- else:
- batch_dict[batch_key]["group_qty"] += 1
- # 重构数据结构
- results = []
- for (bound_number, qty), data in batch_dict.items():
- results.append({
- **data,
- "bound_number": bound_number,
- "current_qty": qty,
- "total_batch_qty": batch_qty_dict[bound_number] # 添加批次总量
- })
- batch_totals =[]
- for bound_number, qty in batch_qty_dict.items():
- batch_totals.append({
- "bound_number": bound_number,
- "total_batch_qty": qty
- })
- return Response(
- {
- "code": 200,
- "message": "Success",
- "data": {
- "container_code": container.container_code,
- "count": len(results),
- "results": results,
- "batch_totals": batch_totals # 可选:单独返回批次总量
- }
- },
- status=status.HTTP_200_OK
- )
- except Exception as e:
- return Response(
- {'code': 500, 'message': f'服务器错误: {str(e)}', 'data': None},
- status=status.HTTP_500_INTERNAL_SERVER_ERROR
- )
- def pdadetail_list(self, request):
- """
- 获取PDA组盘入库的容器详情列表
- """
- try:
- container_code = request.query_params.get('container_code')
- if not container_code:
- return Response(
- {'code': 400, 'message': '缺少托盘编码参数', 'data': None},
- status=status.HTTP_200_OK
- )
-
- # 获取容器对象
- try:
- container = ContainerListModel.objects.get(container_code=container_code)
- except ContainerListModel.DoesNotExist:
- return Response(
- {'code': 404, 'message': '指定托盘不存在', 'data': None},
- status=status.HTTP_200_OK
- )
- # 查询关联批次明细(排除状态0和3)
- details = ContainerDetailModel.objects.filter(
- container=container,is_delete=False
- ).exclude(
- status__in=[0, 3]
- ).select_related('batch')
- if not details.exists():
- return Response(
- {'code': 404, 'message': '未找到有效批次数据', 'data': None},
- status=status.HTTP_200_OK
- )
- # 按批次号 + 数量分组统计
- batch_dict = {}
- batch_qty_dict = defaultdict(int) # 使用默认字典自动初始化
- for detail in details:
- if not detail.batch:
- continue
- bound_number = detail.batch.bound_number
- goods_qty = detail.goods_qty - detail.goods_out_qty # 剔除出库数量
- # 组合键:批次号 + 当前数量
- batch_key = (bound_number, goods_qty)
-
-
- batch_qty_dict[bound_number] += goods_qty # 自动处理键初始化
- # 分组统计
- if batch_key not in batch_dict:
- batch_obj = BoundBatchModel.objects.filter( bound_number=bound_number).first()
- batch_dict[batch_key] = {
- "goods_code": detail.goods_code,
- "goods_desc": detail.goods_desc,
- "goods_qty": goods_qty,
- "goods_class": detail.goods_class,
- "goods_package": batch_obj.goods_package,
- "batch_total_qty": batch_obj.goods_qty,
- "batch_total_in_qty": batch_obj.goods_in_qty - batch_obj.goods_out_qty,
- "status": detail.status,
- "group_qty": 1,
- "create_time": detail.create_time,
- }
- else:
- batch_dict[batch_key]["group_qty"] += 1
- # 重构数据结构
- results = []
- for (bound_number, qty), data in batch_dict.items():
- results.append({
- **data,
- "bound_number": bound_number,
- "current_qty": qty,
- "total_batch_qty": batch_qty_dict[bound_number] # 添加批次总量
- })
- batch_totals =[]
- for bound_number, qty in batch_qty_dict.items():
- batch_totals.append({
- "bound_number": bound_number,
- "total_batch_qty": qty
- })
- return Response(
- {
- "code": 200,
- "message": "Success",
- "data": {
- "count": len(results),
- "results": results,
- "batch_totals": batch_totals # 可选:单独返回批次总量
- }
- },
- status=status.HTTP_200_OK
- )
- except Exception as e:
- return Response(
- {'code': 500, 'message': f'服务器错误: {str(e)}', 'data': None},
- status=status.HTTP_200_OK
- )
- # 托盘操作历史记录
- class ContainerOperateViewSet(viewsets.ModelViewSet):
- """
- retrieve:
- Response a data list(get)
- list:
- Response a data list(all)
- create:
- Create a data line(post)
- delete:
- Delete a data line(delete)
- """
- # authentication_classes = [] # 禁用所有认证类
- # permission_classes = [AllowAny] # 允许任意访问
-
- pagination_class = MyPageNumberPagination
- filter_backends = [DjangoFilterBackend, OrderingFilter, ]
- ordering_fields = ['id', "timestamp" ]
- filter_class = ContainerOperationFilter
- def get_project(self):
- try:
- id = self.kwargs.get('pk')
- return id
- except:
- return None
- def get_queryset(self):
- id = self.get_project()
- if self.request.user:
- if id is None:
- return ContainerOperationModel.objects.filter( is_delete=False)
- else:
- return ContainerOperationModel.objects.filter( id=id, is_delete=False)
- else:
- return ContainerOperationModel.objects.none()
- def get_serializer_class(self):
- if self.action in ['list', 'destroy','retrieve']:
- return ContainerOperationGetSerializer
- elif self.action in ['create', 'update']:
- return ContainerOperationPostSerializer
- else:
- return self.http_method_not_allowed(request=self.request)
- def create(self, request, *args, **kwargs):
- data = self.request.data
- serializer = self.get_serializer(data=data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=200, headers=headers)
-
- def update(self, request, pk):
- qs = self.get_object()
- data = self.request.data
- serializer = self.get_serializer(qs, data=data)
- serializer.is_valid(raise_exception=True)
- serializer.save()
- headers = self.get_success_headers(serializer.data)
- return Response(serializer.data, status=200, headers=headers)
-
- # 出库任务生成
- class OutboundService:
- @staticmethod
- def generate_task_id():
- """生成唯一任务ID(格式: outbound-年月-顺序号)"""
- month = timezone.now().strftime("%Y%m")
- last_task = ContainerWCSModel.objects.filter(
- tasktype='outbound',
- month=int(month)
- ).order_by('-sequence').first()
- sequence = last_task.sequence + 1 if last_task else 1
- return f"outbound-{month}-{sequence:05d}"
- @staticmethod
- def send_task_to_wcs(task):
- """异步发送任务到WCS(非阻塞版本)"""
- # 提取任务关键数据用于线程(避免直接传递ORM对象)
- task_data = {
- 'task_id': task.pk, # 使用主键而不是对象
- 'send_data': {
- "code":'200',
- "message": task.message,
- "data":{
- "taskid": task.taskid,
- "container": task.container,
- "current_location": task.current_location,
- "target_location": task.target_location,
- "tasktype": task.tasktype,
- "month": task.month,
- "message": task.message,
- "status": task.status,
- "taskNumber": task.tasknumber-20000000000,
- "order_number":task.order_number,
- "sequence":task.sequence
- }
- }
- }
-
- # 创建并启动线程
- thread = threading.Thread(
- target=OutboundService._async_send_handler,
- kwargs=task_data,
- daemon=True # 守护线程(主程序退出时自动终止)
- )
- thread.start()
- return True # 立即返回表示已开始处理
- @staticmethod
- def _async_send_handler(task_id, send_data):
- """异步处理的实际工作函数"""
- try:
- # 每个线程需要独立的数据库连接
- close_old_connections()
-
- # 重新获取任务对象(确保使用最新数据)
- task = ContainerWCSModel.objects.get(pk=task_id)
-
- # 发送第一个请求(不处理结果)
- requests.post(
- "http://127.0.0.1:8008/container/batch/",
- json=send_data,
- timeout=10
- )
-
- # 发送关键请求
- response = requests.post(
- "http://192.168.18.200:1616/wcs/WebApi/getOutTask",
- json=send_data,
- timeout=10
- )
-
- # 处理响应
- if response.status_code == 200:
- task.status = 200
- task.save()
- logger.info(f"任务 {task.taskid} 已发送")
- else:
- logger.error(f"WCS返回错误: {response.text}")
-
- except Exception as e:
- logger.error(f"发送失败: {str(e)}")
- finally:
- close_old_connections() # 清理数据库连接
- @staticmethod
- def create_initial_tasks(container_list,bound_list_id):
- """生成初始任务队列"""
- with transaction.atomic():
- current_WCS = ContainerWCSModel.objects.filter(tasktype='outbound',bound_list_id = bound_list_id).first()
- if current_WCS:
- logger.error(f"当前{bound_list_id}已有出库任务")
- return False
- tasks = []
- start_sequence = ContainerWCSModel.objects.filter(tasktype='outbound').count() + 1
- tasknumber = ContainerWCSModel.objects.filter().count()
- tasknumber_index = 1
- for index, container in enumerate(container_list, start=start_sequence):
- container_obj = ContainerListModel.objects.filter(id =container['container_number']).first()
- if container_obj.current_location != container_obj.target_location:
- logger.error(f"托盘 {container_obj.container_code} 未到达目的地,不生成任务")
- return False
- OutBoundDetail_obj = OutBoundDetailModel.objects.filter(bound_list=bound_list_id,bound_batch_number_id=container['batch_id']).first()
- if not OutBoundDetail_obj:
- logger.error(f"批次 {container['batch_id']} 不存在")
- return False
- month = int(timezone.now().strftime("%Y%m"))
- task = ContainerWCSModel(
- taskid=OutboundService.generate_task_id(),
- batch = OutBoundDetail_obj.bound_batch_number,
- batch_out = OutBoundDetail_obj.bound_batch,
- bound_list = OutBoundDetail_obj.bound_list,
- sequence=index,
- order_number = container['c_number'],
- priority=100,
- tasknumber = month*100000+tasknumber_index+tasknumber,
- container=container_obj.container_code,
- current_location=container_obj.current_location,
- target_location="103",
- tasktype="outbound",
- month=int(timezone.now().strftime("%Y%m")),
- message="等待出库",
- status=100,
- )
- tasknumber_index += 1
- tasks.append(task)
- container_obj = ContainerListModel.objects.filter(container_code=task.container).first()
- container_obj.target_location = task.target_location
- container_obj.save()
- ContainerWCSModel.objects.bulk_create(tasks)
- logger.info(f"已创建 {len(tasks)} 个初始任务")
- @staticmethod
- def insert_new_tasks(new_tasks):
- """动态插入新任务并重新排序"""
- with transaction.atomic():
- pending_tasks = list(ContainerWCSModel.objects.select_for_update().filter(status=100))
-
- # 插入新任务
- for new_task_data in new_tasks:
- new_task = ContainerWCSModel(
- taskid=OutboundService.generate_task_id(),
- priority=new_task_data.get('priority', 100),
- container=new_task_data['container'],
- current_location=new_task_data['current_location'],
- target_location=new_task_data.get('target_location', 'OUT01'),
- tasktype="outbound",
- month=int(timezone.now().strftime("%Y%m")),
- message="等待出库",
- status=100,
- )
- # 找到插入位置
- insert_pos = 0
- for i, task in enumerate(pending_tasks):
- if new_task.priority < task.priority:
- insert_pos = i
- break
- else:
- insert_pos = len(pending_tasks)
- pending_tasks.insert(insert_pos, new_task)
-
- # 重新分配顺序号
- for i, task in enumerate(pending_tasks, start=1):
- task.sequence = i
- if task.pk is None:
- task.save()
- else:
- task.save(update_fields=['sequence'])
-
- logger.info(f"已插入 {len(new_tasks)} 个新任务")
- @staticmethod
- def process_next_task():
- """处理下一个任务"""
- next_task = ContainerWCSModel.objects.filter(status=100).order_by('sequence').first()
- if not next_task:
- logger.info("没有待处理任务")
- return
- allocator = LocationAllocation()
- OutboundService.perform_initial_allocation(allocator, next_task.current_location)
- OutboundService.send_task_to_wcs(next_task)
- def perform_initial_allocation(allocator, location):
- """执行初始库位分配操作"""
- location_row = location.split('-')[1]
- location_col = location.split('-')[2]
- location_layer = location.split('-')[3]
- location_code = LocationModel.objects.filter(row=location_row, col=location_col, layer=location_layer).first().location_code
- if not location_code:
- logger.error(f"未找到库位: {location}")
- operations = [
- (allocator.update_location_status,location_code, 'reserved'),
- (allocator.update_location_group_status,location_code)
- ]
-
- for func, *args in operations:
- if not func(*args):
- logger.error(f"分配操作失败: {func.__name__}")
- return False
- return True
- # 出库任务下发
- class OutTaskViewSet(APIView):
- """
- # fun:get_out_task:下发出库任务
- # fun:get_batch_count_by_boundlist:获取出库申请下的批次数量
- # fun:generate_location_by_demand:根据出库需求生成出库任务
- """
- # authentication_classes = [] # 禁用所有认证类
- # permission_classes = [AllowAny] # 允许任意访问
- def post(self, request):
- try:
- data = self.request.data
- logger.info(f"收到 WMS 推送数据: {data}")
-
- # 假设从请求中获取 bound_list_id
- bound_list_id = data.get('bound_list_id')
- # 获取关联的出库批次
- out_batches = OutBatchModel.objects.filter(
- bound_list_id=bound_list_id,
- is_delete=False
- ).select_related('batch_number')
-
- if not out_batches.exists():
- return Response({"code": "404", "msg": "未找到相关出库批次"}, status=404)
- # 构建批次需求字典
- batch_demand = {}
- for ob in out_batches:
- if ob.batch_number_id not in batch_demand:
- batch_demand[ob.batch_number_id] = {
- 'required': ob.goods_out_qty,
- 'allocated': ob.goods_qty,
- 'remaining': ob.goods_qty - ob.goods_out_qty
- }
- else:
- batch_demand[ob.batch_number_id]['required'] += ob.goods_out_qty
- batch_demand[ob.batch_number_id]['allocated'] += ob.goods_qty
- batch_demand[ob.batch_number_id]['remaining'] += (ob.goods_out_qty - ob.goods_qty)
-
- # 生成出库任务
- generate_result = self.generate_location_by_demand(
- batch_demand=batch_demand,
- bound_list_id=bound_list_id
- )
-
- if generate_result['code'] != '200':
- return Response(generate_result, status=500)
-
- # 创建并处理出库任务
- container_list = generate_result['data']
- # 2. 生成初始任务
- OutboundService.create_initial_tasks(container_list,bound_list_id)
-
- # 3. 立即发送第一个任务
- OutboundService.process_next_task()
-
- return Response({"code": "200", "msg": "Success"}, status=200)
- except Exception as e:
- logger.error(f"任务生成失败: {str(e)}")
- return Response({"code": "500", "msg": str(e)}, status=500)
- # 获取出库需求
- def get_batch_count_by_boundlist(self,bound_list_id):
- try:
- bound_list_obj_all = OutBoundDetailModel.objects.filter(bound_list=bound_list_id).all()
- if bound_list_obj_all:
-
- batch_count_dict = {}
- # 统计批次数量(创建哈希表,去重)
- for batch in bound_list_obj_all:
- if batch.bound_batch_number_id not in batch_count_dict:
- batch_count_dict[batch.bound_batch_number_id] = batch.bound_batch.goods_out_qty
- else:
- batch_count_dict[batch.bound_batch_number_id] += batch.bound_batch.goods_out_qty
- return batch_count_dict
- else:
- logger.error(f"查询批次数量失败: {bound_list_id} 不存在")
- return {}
- except Exception as e:
- logger.error(f"查询批次数量失败: {str(e)}")
- return {}
- def get_location_by_status_and_batch(self,status,bound_id):
- try:
- container_obj = ContainerDetailModel.objects.filter(batch=bound_id,status=status,is_delete=False).all()
- if container_obj:
-
- container_dict = {}
- # 统计托盘数量(创建哈希表,去重)
- for obj in container_obj:
- if obj.container_id not in container_dict:
- container_dict[obj.container_id] = obj.goods_qty
- else:
- container_dict[obj.container_id] += obj.goods_qty
- return container_dict
- else:
- logger.error(f"查询{status}状态的批次数量失败: {bound_id} 不存在")
- return {}
- except Exception as e:
- logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
- return {}
-
- def get_order_by_batch(self,container_list,bound_id):
- try:
- container_dict = {}
- for container in container_list:
- location_container = LocationContainerLink.objects.filter(container_id=container,is_active=True).first()
- if location_container:
- location_c_number = location_container.location.c_number
- if container not in container_dict:
- container_dict[container] = {
- "container_number":container,
- "location_c_number":location_c_number,
- "location_id ":location_container.location.id,
- "location_type":location_container.location.location_type,
- "batch_id":bound_id,
- }
- if len(container_dict.keys()) == len(container_list):
- return container_dict
- else:
- logger.error(f"查询批次数量失败: {container_list} 不存在")
- return {}
- except Exception as e:
- logger.error(f"查询批次数量失败: {str(e)}")
- return {}
-
- except Exception as e:
- logger.error(f"查询{status}状态的批次数量失败: {str(e)}")
- return {}
- def get_container_allocation(self, batch_id):
- """兼容所有数据库的去重方案"""
- # 获取唯一容器ID列表
- container_ids = (
- ContainerDetailModel.objects
- .filter(batch_id=batch_id, status=2,is_delete=False)
- .values_list('container_id', flat=True)
- .distinct()
- )
- # 获取每个容器的最新明细(按id倒序)
- return (
- ContainerDetailModel.objects
- .filter(container_id__in=container_ids,batch_id=batch_id, status=2,is_delete=False)
- .select_related('container')
- .prefetch_related(
- Prefetch('container__location_links',
- queryset=LocationContainerLink.objects.select_related('location'),
- to_attr='active_location')
- )
- .order_by('container_id', '-id')
- )
-
- def generate_location_by_demand(self, batch_demand, bound_list_id):
- try:
- return_data = []
-
- for batch_id, demand in batch_demand.items():
- # 获取已去重的容器列表
- container_qs = self.get_container_allocation(batch_id)
-
- # 构建容器信息字典(自动去重)
- container_map = {}
- for cd in container_qs:
- if cd.container_id in container_map:
- container_map[cd.container_id]['goods_qty'] += cd.goods_qty - cd.goods_out_qty
- continue
- # 获取有效库位信息
- active_location = next(
- (link.location for link in cd.container.active_location
- if link.is_active),
- None
- )
-
- container_map[cd.container_id] = {
- 'detail': cd,
- 'goods_qty': cd.goods_qty - cd.goods_out_qty,
- 'container': cd.container,
- 'location': active_location
- }
- # 转换为排序列表
- container_list = list(container_map.values())
-
- # 多维度排序(优化性能版)
- sorted_containers = sorted(
- container_list,
- key=lambda x: (
- self._get_goods_class_priority(x['detail'].goods_class),
- -(x['location'].c_number if x['location'] else 0),
-
- # -(x['location'].layer if x['location'] else 0),
- # x['location'].row if x['location'] else 0,
- # x['location'].col if x['location'] else 0
- )
- )
-
- # 分配逻辑
- required = demand['required']
- for item in sorted_containers:
- if required <= 0:
- break
-
- # 获取可分配数量
- allocatable = item['goods_qty']
- allocate_qty = min(required, allocatable)
-
- # 记录分配信息
- allocate_container = {
- "container_number": item['container'].id,
- "batch_id": batch_id,
- "location_code": item['location'].location_code if item['location'] else 'N/A',
- "allocate_qty": allocate_qty,
- "c_number": item['location'].c_number if item['location'] else 0
- }
- return_data.append(allocate_container)
-
- required -= allocate_qty
- # 更新数据库状态(需要事务处理)
- self._update_allocation_status(allocate_container, allocate_qty,bound_list_id)
- # 降重 return_data,以container_number为key
- return_data = list({v['container_number']: v for v in return_data}.values())
-
- # 排序
- return_data = sorted(return_data, key=lambda x: -x['c_number'])
- return {"code": "200", "msg": "Success", "data": return_data}
-
- except Exception as e:
- logger.error(f"出库任务生成失败: {str(e)}", exc_info=True)
- return {"code": "500", "msg": str(e)}
-
- def _get_goods_class_priority(self, goods_class):
- """货物类型优先级权重"""
- return {
- 3: 0, # 散盘最高
- 1: 1, # 成品次之
- 2: 2 # 空盘最低
- }.get(goods_class, 99)
- @transaction.atomic
- def _update_allocation_status(self, allocate_container, allocate_qty,bound_list_id):
- """事务化更新分配状态"""
- try:
- # 更新容器明细
- container_detail_all = ContainerDetailModel.objects.filter(
- container_id=allocate_container['container_number'],
- batch_id=allocate_container['batch_id'],
- is_delete=False
- ).all()
- left_qty = 0
- for cd in container_detail_all:
- if left_qty - allocate_qty >= 0:
- break
- add_qty = min(allocate_qty-left_qty, cd.goods_qty - cd.goods_out_qty)
- last_out_qty = cd.goods_out_qty
- cd.goods_out_qty += add_qty
- left_qty += add_qty
- cd.save()
- if cd.goods_qty - cd.goods_out_qty == 0:
- cd.status = 3
- cd.save()
- # 新建出库detail
- out_batch_detail.objects.create(
- out_bound_id=bound_list_id,
- container_id=cd.container_id,
- container_detail_id=cd.id,
- out_goods_qty=add_qty,
- last_out_goods_qty = last_out_qty,
- working = 1,
- is_delete = False
- )
-
- return True
- except Exception as e:
- logger.error(f"状态更新失败: {str(e)}")
- return False
- def create_or_update_container_operation(self,container_obj,batch_id,bound_id,to_location,goods_qty,goods_weight):
- try:
- container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,bound_id=bound_id,operation_type="outbound").first()
-
- if container_operation_obj:
- logger.info(f"[0]查询出库任务: {container_operation_obj.operation_type} ")
- logger.info(f"更新出库任务: {container_obj.container_code} 批次 {batch_id} 出库需求: {bound_id} 数量: {goods_qty} 重量: {goods_weight}")
- container_operation_obj.to_location = to_location
- container_operation_obj.goods_qty = goods_qty
- container_operation_obj.goods_weight = goods_weight
- container_operation_obj.save()
-
- else:
- logger.info(f"创建出库任务: {container_obj.container_code} 批次 {batch_id} 出库需求: {bound_id} 数量: {goods_qty} 重量: {goods_weight}")
- batch = BoundBatchModel.objects.filter(id=batch_id).first()
- if not batch:
- return {"code": "500", "msg": f"批次 {batch_id} 不存在"}
- ContainerOperationModel.objects.create(
- month = int(timezone.now().strftime("%Y%m")),
- container = container_obj,
- goods_code = batch.goods_code,
- goods_desc = batch.goods_desc,
- operation_type ="outbound",
- batch_id = batch_id,
- bound_id = bound_id,
- goods_qty = goods_qty,
- goods_weight = goods_weight,
- from_location = container_obj.current_location,
- to_location= to_location,
- timestamp=timezone.now(),
- operator="WMS",
- memo=f"出库需求: {bound_id}, 批次: {batch_id}, 数量: {goods_qty}"
- )
-
- return {"code": "200", "msg": "Success"}
- except Exception as e:
- return {"code": "500", "msg": str(e)}
-
- def update_container_detail_out_qty(self,container_obj,batch_id):
- try:
- logger.info(f"[1]更新托盘出库数量: {container_obj.container_code} 批次 {batch_id} ")
- container_operation_obj = ContainerOperationModel.objects.filter(container=container_obj,batch_id=batch_id,operation_type="outbound").all()
- if not container_operation_obj:
- logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无出库任务")
- return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无出库任务"}
- container_detail_obj = ContainerDetailModel.objects.filter(container=container_obj,batch_id=batch_id,status=2,is_delete=False).first()
- if not container_detail_obj:
- logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无批次信息")
- return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无批次信息"}
- out_qty = 0
- for obj in container_operation_obj:
- out_qty += obj.goods_qty
- if out_qty >= container_detail_obj.goods_qty:
- out_qty = container_detail_obj.goods_qty
- container_detail_obj.status = 3
- break
- if out_qty == 0:
- logger.error(f"[1]批次 {batch_id} 托盘 {container_obj.container_code} 无出库数量")
- return {"code": "500", "msg": f"批次 {batch_id} 托盘 {container_obj.container_code} 无出库数量"}
- container_detail_obj.goods_out_qty = out_qty
- container_detail_obj.save()
- return {"code": "200", "msg": "Success"}
- except Exception as e:
- return {"code": "500", "msg": str(e)}
- # 出库任务监测
- class BatchViewSet(viewsets.ModelViewSet):
- authentication_classes = [] # 禁用所有认证类
- permission_classes = [AllowAny] # 允许任意访问
- def wcs_post(self, request, *args, **kwargs):
- data = self.request.data
- logger.info(f"收到 WMS 推送数据: {data}")
- return Response({"code": "200", "msg": "Success"}, status=200)
- # views.py
- class OutDetailViewSet(viewsets.ModelViewSet):
- pagination_class = MyPageNumberPagination
- serializer_class = OutBoundDetailSerializer
- def get_project(self):
- try:
- id = self.kwargs.get('pk')
- return id
- except:
- return None
- def get_queryset(self):
- """根据不同的action调整查询集"""
- if self.action == 'list':
- # 获取每个out_bound的最新一条记录
- # 子查询,用于获取每个out_bound对应的最新out_batch_detail记录
- subquery = out_batch_detail.objects.filter(
- out_bound=OuterRef('out_bound')
- ).order_by('-id')
- # 返回最新的out_batch_detail记录,通过子查询的结果进行过滤
- return out_batch_detail.objects.filter(
- id=Subquery(subquery.values('id')[:1])
- )
- return out_batch_detail.objects.all()
- def retrieve(self, request, *args, **kwargs):
- """重写retrieve方法返回关联集合"""
- qs = self.get_project()
- queryset = self.filter_queryset(
- out_batch_detail.objects.filter(out_bound = qs)
- )
-
- # 分页处理
- # page = self.paginate_queryset(queryset)
- # if queryset is not None:
- # serializer = self.get_serializer(queryset, many=True)
- # return self.get_paginated_response(serializer.data)
-
- serializer = self.get_serializer(queryset, many=True)
- return Response(serializer.data)
- def get_serializer_class(self):
- """根据action切换序列化器"""
- if self.action == 'retrieve':
- return OutBoundFullDetailSerializer
- return super().get_serializer_class()
-
- def get_out_batch_detail(self, request):
- """获取某个容器的出库明细"""
- try:
- container_code = request.query_params.get('container_code')
- container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
- if not container_obj:
- return Response({"code": "500", "message": f"容器 {container_code} 不存在"}, status=status.HTTP_200_OK)
- out_batch_detail_all = out_batch_detail.objects.filter(container=container_obj,working=1,is_delete=False).all()
- if not out_batch_detail_all:
- return Response({"code": "500", "message": f"容器 {container_code} 无出库明细"}, status=status.HTTP_200_OK)
- serializer = OutBoundFullDetailSerializer(out_batch_detail_all, many=True)
- return Response({"code": "200", "message": "Success", "data": serializer.data}, status=status.HTTP_200_OK)
- except Exception as e:
- return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)
-
- def confirm_out_batch_detail(self, request):
- """确认出库"""
- try:
- container_code = request.query_params.get('container_code')
- container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
- if not container_obj:
- return Response({"code": "500", "message": f"容器 {container_code} 不存在"}, status=status.HTTP_200_OK)
- out_batch_detail_all = out_batch_detail.objects.filter(container=container_obj,working=1,is_delete=False).all()
- if not out_batch_detail_all:
- return Response({"code": "500", "message": f"容器 {container_code} 无出库明细"}, status=status.HTTP_200_OK)
- for obj in out_batch_detail_all:
- obj.working = 0
- obj.save()
- BatchLogModel.objects.create(
- batch_id = obj.container_detail.batch,
- log_type = 1,
- log_date = timezone.now(),
- goods_code = obj.container_detail.batch.goods_code,
- goods_desc = obj.container_detail.batch.goods_desc,
- goods_qty = obj.out_goods_qty,
- log_content = f"出库容器 {container_code} 批次 {obj.container_detail.batch.id} 数量 {obj.out_goods_qty} 重量 {obj.container_detail.batch.goods_weight} 到 {obj.to_location.location_code} 库位",
- creater = "WMS",
- openid = "WMS"
- )
- return Response({"code": "200", "message": "出库成功"}, status=status.HTTP_200_OK)
- except Exception as e:
- return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)
-
- def cancel_out_batch_detail(self, request):
- """取消出库"""
- try:
- container_code = request.query_params.get('container_code')
- container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
- if not container_obj:
- return Response({"code": "500", "message": f"容器 {container_code} 不存在"}, status=status.HTTP_200_OK)
- out_batch_detail_all = out_batch_detail.objects.filter(container=container_obj,working=1,is_delete=False).all()
- if not out_batch_detail_all:
- return Response({"code": "500", "message": f"容器 {container_code} 无出库明细"}, status=status.HTTP_200_OK)
- for obj in out_batch_detail_all:
- obj.container_detail.goods_out_qty = obj.last_out_goods_qty
- obj.container_detail.save()
- obj.is_delete = True
- obj.working = 0
- obj.save()
- return Response({"code": "200", "message": "出库取消成功"}, status=status.HTTP_200_OK)
- except Exception as e:
- return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)
-
- def get_contianer_detail(self, request):
- try:
- container_code = request.query_params.get('container_code')
- container_obj = ContainerListModel.objects.filter(container_code=container_code).first()
- if not container_obj:
- return Response({"code": "500", "message": f"容器 {container_code} 不存在"}, status=status.HTTP_200_OK)
- container_detail_all = ContainerDetailModel.objects.filter(container=container_obj,is_delete=False).all().exclude(status__in=[3])
- return_data=[]
- for obj in container_detail_all:
- return_data.append({
- "id": obj.id,
- "batch": obj.batch.bound_number,
- "goods_code": obj.goods_code,
- "goods_desc": obj.goods_desc,
- "goods_qty" :obj.goods_qty,
- "out_goods_qty": obj.goods_out_qty
- })
- return Response({"code": "200", "message": "Success", "data": return_data}, status=status.HTTP_200_OK)
- except Exception as e:
- return Response({"code": "500", "message": str(e)}, status=status.HTTP_200_OK)
|