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)