123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- from rest_framework.views import APIView
- from rest_framework.response import Response
- from rest_framework.permissions import AllowAny
- from rest_framework import status
- from rest_framework import viewsets
- from django_filters.rest_framework import DjangoFilterBackend, OrderingFilter
- from django.utils import timezone
- from django.db.models import Q, F, Case, When
- from utils.page import MyPageNumberPagination
- from .models import *
- from .serializers import *
- from .filter import *
- import logging
- logger = logging.getLogger('wms.boundBill')
- class WMSResponse:
- """
- 入库申请专用响应格式
- 使用示例:
- return WMSResponse.success(data=serializer.data, total=2, success=2)
- return WMSResponse.error(message='验证失败', total=2, fail_count=1,
- fail_materials=[{...}])
- """
-
- @staticmethod
- def success(data, total, success, status=status.HTTP_201_CREATED):
- """成功响应格式"""
- logger.info('成功响应 | 数据: %s', data)
- return Response({
- "status": True,
- "errorCode": status,
- "message": "success",
- "data": {
- "failCount": 0,
- "totalCount": total,
- "successCount": success,
- "fail_materials": []
- }
- }, status=status)
- @staticmethod
- def error(message, total, fail_count, fail_materials=None,
- status=status.HTTP_400_BAD_REQUEST, exception=None):
- """错误响应格式"""
- if fail_materials is None:
- fail_materials = []
-
- # 记录详细错误日志
- if exception:
- logger.error(f"入库申请错误: {message}", exc_info=exception)
-
- return Response({
- "status": False,
- "errorCode": status,
- "message": message,
- "data": {
- "failCount": fail_count,
- "totalCount": total,
- "successCount": total - fail_count,
- "fail_materials": fail_materials
- }
- }, status=status)
- # Create your views here.
- # urlpatterns = [
- # path('createInboundApply', views.InboundApplyCreate.as_view()),
- # path('createOutboundApply', views.OutboundApplyCreate.as_view()),
- # path('updateBatchInfo', views.BatchUpdate.as_view()),
- # path('productInfo', views.ProductInfo.as_view()),
- # ]
- class InboundApplyCreate(APIView):
- """
- 生产入库申请
- """
- authentication_classes = []
- permission_classes = [AllowAny]
- def post(self, request):
- logger.info('生产入库申请请求 | 原始数据: %s', request.data)
-
- try:
- total_count = len(request.data.get('materials', []))
- if total_count == 0 :
- return WMSResponse.error(
- message="物料清单不能为空",
- total=0,
- fail_count=0
- )
- if total_count != request.data.get('totalCount', 0):
- return WMSResponse.error(
- message="物料数量不匹配",
- total=total_count,
- fail_count=total_count
- )
-
- serializer = boundPostSerializer(data=request.data)
- if not serializer.is_valid():
-
- return self._handle_validation_error(serializer.errors, total_count)
- unique_result,error_details = self.find_unique_billid_and_number(serializer)
- if not unique_result:
- return WMSResponse.error(
- message="单据编号或原始单据ID重复",
- total=total_count,
- fail_count=total_count,
- fail_materials=[{
- "entryIds": None,
- "production_batch": None,
- "errors": error_details
- }]
- )
- # 保存或更新入库单
- bound_bill = self.save_or_update_inbound_bill(serializer)
- # 保存或更新物料明细
- self.save_or_update_material_detail(bound_bill, serializer)
- return WMSResponse.success(
- data=serializer.data,
- total=total_count,
- success=total_count
- )
- except Exception as e:
- logger.exception("服务器内部错误")
- return WMSResponse.error(
- message="系统处理异常",
- total=total_count,
- fail_count=total_count,
- status=status.HTTP_500_INTERNAL_SERVER_ERROR,
- exception=e
- )
- def _handle_validation_error(self, errors, total_count):
- """增强错误解析"""
- fail_materials = []
-
- # 提取嵌套错误信息
- material_errors = errors.get('materials', [])
-
- for error in material_errors:
- # 解析DRF的错误结构
- if isinstance(error, dict) and 'metadata' in error:
- fail_materials.append({
- "entryIds": error['metadata'].get('entryIds'),
- "production_batch": error['metadata'].get('production_batch'),
- "errors": {
- "missing_fields": error['metadata']['missing_fields'],
- "message": error['detail']
- }
- })
-
- return WMSResponse.error(
- message="物料数据不完整",
- total=total_count,
- fail_count=len(fail_materials),
- fail_materials=fail_materials
- )
- def _format_material_errors(self, error_dict):
- """格式化单个物料的错误信息"""
- return {
- field: details[0] if isinstance(details, list) else details
- for field, details in error_dict.items()
- }
- def find_unique_billid_and_number(self, serializer):
- """增强版唯一性验证"""
- bill_id = serializer.validated_data['billId']
- number = serializer.validated_data['number']
-
- # 使用Q对象进行联合查询
- duplicates_id = InboundBill.objects.filter(
- Q(billId=bill_id)
- ).only('billId')
- # 使用Q对象进行联合查询
- duplicates_nu = InboundBill.objects.filter(
- Q(number=number)
- ).only( 'number')
-
- error_details = {}
-
- # 检查单据编号重复
- if any(obj.billId != bill_id for obj in duplicates_nu):
- error_details['number'] = ["number入库单编码已存在,但是系统中与之前入库单单据ID不一致,请检查"]
-
-
- # 检查业务编号重复
- if any(obj.number != number for obj in duplicates_id):
-
- error_details['billId'] = ["billId入库单单据ID已存在,但是系统中与之前入库申请单编码不一致,请检查"]
-
-
- if error_details:
- return False,error_details
- return True,None
-
- def save_or_update_inbound_bill(self, serializer):
- """保存或更新入库单"""
- # 保存或更新入库单
- try:
- bound_bill = InboundBill.objects.get(billId=serializer.validated_data['billId'])
- bound_bill.number = serializer.validated_data['number']
- bound_bill.type = serializer.validated_data['type']
- bound_bill.date = serializer.validated_data['date']
- bound_bill.department = serializer.validated_data['department']
- bound_bill.warehouse = serializer.validated_data['warehouse']
- bound_bill.creater = serializer.validated_data['creater']
- bound_bill.note = (serializer.validated_data['note'] if 'note' in serializer.validated_data else '')
- bound_bill.totalCount = serializer.validated_data['totalCount']
- bound_bill.update_time = timezone.now()
- bound_bill.save()
- except InboundBill.DoesNotExist:
- bound_bill = InboundBill.objects.create(
- billId=serializer.validated_data['billId'],
- number=serializer.validated_data['number'],
- type=serializer.validated_data['type'],
- date=serializer.validated_data['date'],
- department=serializer.validated_data['department'],
- warehouse=serializer.validated_data['warehouse'],
- creater=serializer.validated_data['creater'],
- note=(serializer.validated_data['note'] if 'note' in serializer.validated_data else ''),
- totalCount=serializer.validated_data['totalCount'],
- create_time=timezone.now(),
- update_time=timezone.now()
- )
- return bound_bill
- def save_or_update_material_detail(self, bound_bill, serializer):
- """保存或更新物料明细"""
- # 保存或更新物料明细
- for item in serializer.validated_data['materials']:
- try:
- material_detail = MaterialDetail.objects.get(bound_billId=bound_bill, entryIds=item['entryIds'])
- material_detail.production_batch = item['production_batch']
-
- material_detail.goods_code = item['goods_code']
- material_detail.goods_name = item['goods_name']
- material_detail.goods_std = item['goods_std']
-
- material_detail.plan_qty = item['plan_qty']
- material_detail.goods_total_weight = item['plan_qty']
- material_detail.goods_unit = item['goods_unit']
- material_detail.note = (item['note'] if 'note' in item else '')
- material_detail.update_time = timezone.now()
- material_detail.save()
- except MaterialDetail.DoesNotExist:
- material_detail = MaterialDetail.objects.create(
- bound_billId=bound_bill,
- entryIds=item['entryIds'],
- production_batch=item['production_batch'],
- goods_code=item['goods_code'],
- goods_name=item['goods_name'],
- goods_std=item['goods_std'],
- goods_weight=1,
- plan_qty=item['plan_qty'],
- goods_total_weight=item['plan_qty'],
- goods_unit=item['goods_unit'],
- note=(item['note'] if 'note' in item else ''),
- create_time=timezone.now(),
- update_time=timezone.now()
- )
- return material_detail
- class OutboundApplyCreate(APIView):
- """
- 生产出库申请
- """
- authentication_classes = [] # 禁用所有认证类
- permission_classes = [AllowAny] # 允许任意访问
- def post(self, request):
- return Response(status=status.HTTP_200_OK)
- class BatchUpdate(APIView):
- """
- 批次信息更新
- """
- authentication_classes = [] # 禁用所有认证类
- permission_classes = [AllowAny] # 允许任意访问
- def post(self, request):
- logger.info('批次信息更新')
- serializer = BatchUpdateSerializer(data=request.data)
- return Response(serializer.data, status=status.HTTP_200_OK)
- class ProductInfo(APIView):
- """
- 商品信息查询
- """
- authentication_classes = [] # 禁用所有认证类
- permission_classes = [AllowAny] # 允许任意访问
- def post(self, request):
- logger.info('商品信息查询')
- serializer = ProductInfoSerializer(data=request.data)
- return Response(serializer.data, status=status.HTTP_200_OK)
- class InboundBills(viewsets.ModelViewSet):
- """
- retrieve:
- Response a data list(get)
- list:
- Response a data list(all)
- """
- authentication_classes = [] # 禁用所有认证类
- permission_classes = [AllowAny] # 允许任意访问
-
- pagination_class = MyPageNumberPagination
- filter_backends = [DjangoFilterBackend, OrderingFilter, ]
- ordering_fields = ['id', "create_time", "update_time", ]
- filter_class = InboundBillFilter
- 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 InboundBill.objects.filter(is_delete=False)
- else:
- return InboundBill.objects.filter(id=id)
- else:
- return InboundBill.objects.none()
- def get_serializer_class(self):
- if self.action in ['retrieve', 'list']:
- return InboundApplySerializer
- else:
- return self.http_method_not_allowed(request=self.request)
- class Materials(viewsets.ModelViewSet):
- """
- retrieve:
- Response a data list(get)
- list:
- Response a data list(all)
- """
- authentication_classes = [] # 禁用所有认证类
- permission_classes = [AllowAny] # 允许任意访问
-
- pagination_class = MyPageNumberPagination
- filter_backends = [DjangoFilterBackend, OrderingFilter, ]
- ordering_fields = ['id', "create_time", "update_time", ]
- filter_class = MaterialDetailFilter
- 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 MaterialDetail.objects.filter(is_delete=False)
- else:
- return MaterialDetail.objects.filter(id=id)
- else:
- return MaterialDetail.objects.none()
- def get_serializer_class(self):
- if self.action in ['retrieve', 'list']:
- return MaterialDetailSerializer
- else:
- return self.http_method_not_allowed(request=self.request)
|