From 6a3ac097e8c8773a3a7858ccadcc69ce3147e284 Mon Sep 17 00:00:00 2001 From: laoxiao Date: Tue, 4 Apr 2023 22:00:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=A5=E5=BA=93=E7=9A=84=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=AE=8C=E6=88=90=EF=BC=8C=E4=BD=86=E6=98=AF=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../serializers/purchase_serializer.py | 15 ++ .../warehouse_info/serializer/__init__.py | 0 .../serializer/in_storage_serializer.py | 91 +++++++++ ERP_5/apps/warehouse_info/views/__init__.py | 0 .../warehouse_info/views/in_storage_views.py | 180 ++++++++++++++++++ ERP_5/settings/dev.py | 1 + 6 files changed, 287 insertions(+) create mode 100644 ERP_5/apps/warehouse_info/serializer/__init__.py create mode 100644 ERP_5/apps/warehouse_info/serializer/in_storage_serializer.py create mode 100644 ERP_5/apps/warehouse_info/views/__init__.py create mode 100644 ERP_5/apps/warehouse_info/views/in_storage_views.py diff --git a/ERP_5/apps/purchase_info/serializers/purchase_serializer.py b/ERP_5/apps/purchase_info/serializers/purchase_serializer.py index c56ac45..8f4e7af 100644 --- a/ERP_5/apps/purchase_info/serializers/purchase_serializer.py +++ b/ERP_5/apps/purchase_info/serializers/purchase_serializer.py @@ -1,20 +1,26 @@ from django.db import transaction +from django.db.models import Sum from rest_framework import serializers from rest_framework.exceptions import ValidationError from ERP_5.utils.get_inventory import get_inventory_by_goods from purchase_info.models import PurchaseModel, PurchaseItemModel +from warehouse_info.models import PurchaseStorageItemModel class PurchaseItemsSerializer(serializers.ModelSerializer): """ 采购单中的项目 的序列化器 """ + model_number = serializers.CharField(source='goods.model_number', read_only=True) color = serializers.CharField(source='goods.color', read_only=True) nits_name = serializers.CharField(source='goods.units.basic_name', read_only=True) cur_inventory = serializers.SerializerMethodField(read_only=True) + # 增加一个已经入库数量的序列化属性(未来在新增入库单的时候需要展示) + already_in_number = serializers.SerializerMethodField(read_only=True) + class Meta: model = PurchaseItemModel fields = '__all__' @@ -22,6 +28,15 @@ class PurchaseItemsSerializer(serializers.ModelSerializer): def get_cur_inventory(self, obj): return get_inventory_by_goods(obj.goods.id) + def get_already_in_number(self, obj): + # 先根据采购单ID作为一个过滤条件, 基于当前货品ID作为第二个过滤条件, 而且入库单的状态是已经审核 + in_count = PurchaseStorageItemModel.objects.filter( + purchase_storage__purchase__id=obj.purchase.id, + goods_id=obj.goods.id).exclude(purchase_storage__status='0')\ + .aggregate(sum=Sum('purchase_count')) + if in_count: + return in_count['sum'] + return 0 class PurchaseSerializer(serializers.ModelSerializer): """ diff --git a/ERP_5/apps/warehouse_info/serializer/__init__.py b/ERP_5/apps/warehouse_info/serializer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ERP_5/apps/warehouse_info/serializer/in_storage_serializer.py b/ERP_5/apps/warehouse_info/serializer/in_storage_serializer.py new file mode 100644 index 0000000..8fd1ce2 --- /dev/null +++ b/ERP_5/apps/warehouse_info/serializer/in_storage_serializer.py @@ -0,0 +1,91 @@ +from django.db import transaction +from rest_framework import serializers +from rest_framework.exceptions import ValidationError + +from ERP_5.utils.get_inventory import get_inventory_by_goods +from warehouse_info.models import PurchaseStorageModel, PurchaseStorageItemModel + +class StorageItemSerializer(serializers.ModelSerializer): + """ + 入库单的 项项目,序列化 + """ + # 注意:既然只有 库存数据没有,那么可以增加一个属性 + cur_inventory = serializers.SerializerMethodField(read_only=True, label='在当前仓库中库存') + + class Meta: + model = PurchaseStorageItemModel + fields = '__all__' + + def get_cur_inventory(self, obj): # 当前的obj为:PurchaseStorageItemModel对象 + result = get_inventory_by_goods(obj.goods.id, obj.warehouse.id) + return result if result else 0 + +class StorageSerializer(serializers.ModelSerializer): + """ + 入库单——反序列化器(用于新增和修改) 和 查询列表的序列化器 + """ + item_list = StorageItemSerializer(many=True) + # 采购订单列表中需要展示:商品信息。 + goods_info = serializers.SerializerMethodField(read_only=True, label='列表中展示的商品信息') + + class Meta: + model = PurchaseStorageModel + fields = '__all__' + + def get_goods_info(self, obj): + """ + 商品信息是由: 商品1名称 商品1规格 , 商品2名称 商品2规格 ,.... + """ + if obj.item_list.all(): + result = [] + for item in obj.item_list.all(): + result.append(item.name + (item.specification if item.specification else '')) + return ', '.join(result) + return "" + + # 同时插入采购单和采购单中货品项。必须重写create + def create(self, validated_data): + item_list = validated_data.pop('item_list') + with transaction.atomic(): + purchase_storage = PurchaseStorageModel.objects.create(**validated_data) + for item in item_list: + psi = PurchaseStorageItemModel.objects.create(purchase_storage=purchase_storage, **item) + # 把一些冗余属性也加进去 + goods = item.get('goods') + psi.specification = goods.specification + psi.model_number = goods.model_number + psi.number_code = goods.number_code + psi.color = goods.color + psi.category = goods.category + psi.category_name = goods.category.name + psi.units = goods.units + psi.units_name = goods.units.basic_name + psi.save() + return purchase_storage + + # 同时插入采购单和采购单中货品项。必须重写update + def update(self, instance, validated_data): + if instance.status == '1': + raise ValidationError("入库单已经生效,不能修改") + item_list = validated_data.pop('item_list') + old_list = instance.item_list.all() + with transaction.atomic(): + if old_list.exists(): + # 然后把旧数据删除,因为在validated_data拿不到货品库存数据的ID + instance.item_list.all().delete() + + for item in item_list: # 重新插入采购项 数据 + psi = PurchaseStorageItemModel.objects.create(purchase_storage=instance, **item) + # 把一些冗余属性也加进去 + goods = item.get('goods') + psi.specification = goods.specification + psi.model_number = goods.model_number + psi.number_code = goods.number_code + psi.color = goods.color + psi.category = goods.category + psi.category_name = goods.category.name + psi.units = goods.units + psi.units_name = goods.units.basic_name + psi.save() + result = super(StorageSerializer, self).update(instance=instance, validated_data=validated_data) + return result \ No newline at end of file diff --git a/ERP_5/apps/warehouse_info/views/__init__.py b/ERP_5/apps/warehouse_info/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ERP_5/apps/warehouse_info/views/in_storage_views.py b/ERP_5/apps/warehouse_info/views/in_storage_views.py new file mode 100644 index 0000000..894142a --- /dev/null +++ b/ERP_5/apps/warehouse_info/views/in_storage_views.py @@ -0,0 +1,180 @@ +from django.db import transaction +from django.db.models import Q, F, Sum +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema +from rest_framework import viewsets, status +from rest_framework.decorators import action +from rest_framework.response import Response + +from ERP_5.utils.base_views import MultipleDestroyMixin, MultipleAuditMixin +from ERP_5.utils.paginations import GlobalPagination +from basic_info.models import SupplierModel +from erp_system.models import UserModel +from goods_info.models import GoodsInventoryModel +from warehouse_info.models import PurchaseStorageModel +from warehouse_info.serializer.in_storage_serializer import StorageSerializer + + +class InStorageViewSet(viewsets.ModelViewSet, MultipleDestroyMixin, MultipleAuditMixin): + """ + create: + 仓库(采购)入库单--新增,注意:其中images_list="1,2,3,4";里面是附件的ID + + 仓库(采购)入库单新增, status: 201(成功), return: 新增仓库(采购)入库单信息 + + destroy: + 仓库(采购)入库单--删除 + + 仓库(采购)入库单删除, status: 204(成功), return: None + + multiple_delete: + 仓库(采购)入库单--批量删除,必传参数:ids=[1,2,3,4...] + + 仓库(采购)入库单批量删除, status: 204(成功), return: None + + update: + 仓库(采购)入库单--修改,注意:其中images_list="1,2,3,4";里面是附件的ID + + 仓库(采购)入库单修改, status: 200(成功), return: 修改后的仓库(采购)入库单信息 + + partial_update: + 仓库(采购)入库单--局部修改,可以传参任意属性的值,服务器会修改指定的属性值 + + 仓库(采购)入库单局部修改, status: 200(成功), return: 修改后的仓库(采购)入库单信息 + + list: + 仓库(采购)入库单--该接口可以弃用 + + 仓库(采购)入库单列表信息, status: 200(成功), return: 仓库(采购)入库单信息列表 + + retrieve: + 查询某一个仓库(采购)入库单 + + 查询指定ID的仓库(采购)入库单, status: 200(成功), return: 用户仓库(采购)入库单 + """ + + queryset = PurchaseStorageModel.objects.all() + serializer_class = StorageSerializer + pagination_class = GlobalPagination + + def get_queryset(self): + if self.action == 'find': # 过滤查询 + # 获取请求参数(在json中): + number_code = self.request.data.get('number_code', None) + keyword = self.request.data.get('keyword', None) + start_date = self.request.data.get('start_date', None) + end_date = self.request.data.get('start_date', None) + supplier = self.request.data.get('supplier', 0) + operator_user = self.request.data.get('operator_user', 0) + status = self.request.data.get('status', None) + + warehouse = self.request.data.get('warehouse', 0) + account = self.request.data.get('account', 0) + purchase_number_code = self.request.data.get('purchase_number_code', None) + query = Q() + if keyword: + child_query = Q() + child_query.add(Q(item_list__name__contains=keyword), 'OR') + child_query.add(Q(item_list__specification=keyword), 'OR') + query.add(child_query, 'AND') + if warehouse: + query.add(Q(item_list__warehouse_id=warehouse), 'AND') + if account: + query.add(Q(account__id=account), 'AND') + if purchase_number_code: + query.add(Q(purchase__number_code__contains=purchase_number_code), 'AND') + + if start_date: + query.add(Q(invoices_date__gt=start_date), 'AND') + if end_date: + query.add(Q(invoices_date__lt=end_date), 'AND') + + if supplier: + query.add(Q(supplier__id=supplier), 'AND') + if number_code: + query.add(Q(number_code__contains=number_code), 'AND') + if operator_user: + query.add(Q(operator_user__id=operator_user), 'AND') + if status: + query.add(Q(status=status), 'AND') + + return PurchaseStorageModel.objects.filter(query).distinct().all() + else: + return PurchaseStorageModel.objects.all() + + params = openapi.Schema(type=openapi.TYPE_OBJECT, properties={ + 'keyword': openapi.Schema(type=openapi.TYPE_STRING, description="名称的关键字或者型号"), + 'start_date': openapi.Schema(type=openapi.TYPE_STRING, description="起始日期2020-10-01"), + 'number_code': openapi.Schema(type=openapi.TYPE_STRING, description="编号(序列号)"), + 'end_date': openapi.Schema(type=openapi.TYPE_STRING, description="结束日期2020-10-01"), + 'status': openapi.Schema(type=openapi.TYPE_STRING, description="状态0或者1,2,3.."), + 'supplier': openapi.Schema(type=openapi.TYPE_INTEGER, description="供应商的ID"), + 'operator_user': openapi.Schema(type=openapi.TYPE_INTEGER, description="操作用户的ID"), + + 'warehouse': openapi.Schema(type=openapi.TYPE_INTEGER, description="仓库的ID"), + 'account': openapi.Schema(type=openapi.TYPE_INTEGER, description="结算账户的ID"), + 'purchase_number_code': openapi.Schema(type=openapi.TYPE_STRING, description="采购订单编号关键字"), + }) + # 分页参数必须是query_param(看源码) + page_param = openapi.Parameter(name='page', in_=openapi.IN_QUERY, description="页号", type=openapi.TYPE_INTEGER) + size_param = openapi.Parameter(name='size', in_=openapi.IN_QUERY, description="每页显示数量", type=openapi.TYPE_INTEGER) + + @swagger_auto_schema(method='POST', request_body=params, manual_parameters=[page_param, size_param], + operation_description="入库单的搜索过滤") + @action(methods=['POST'], detail=False) + def find(self, request, *args, **kwargs): + return super(InStorageViewSet, self).list(request=request, *args, **kwargs) + + body_param = openapi.Schema(type=openapi.TYPE_OBJECT, required=['ids', 'user_id'], properties={ + 'ids': openapi.Schema(type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_INTEGER), + description="选择哪些需要批量的ID(主键)列表"), + 'user_id': openapi.Schema(type=openapi.TYPE_INTEGER, description="审核人的用户ID"), + # 'is_audit': openapi.Schema(type=openapi.TYPE_STRING, description="是否审核,审核:1") + }) + + @swagger_auto_schema(method='put', request_body=body_param, operation_description="批量审核") + @action(methods=['put'], detail=False) + @transaction.atomic # 自动 数据库事务 + def multiple_audit(self, request, *args, **kwargs): + audit_ids = request.data.get('ids') + user_id = request.data.get('user_id') # 用户ID + # is_audit = request.data.get('is_audit', '1') # 是否审核,审核:1 + # 为了减少代码量,参数的验证就不写了 + queryset = self.get_queryset().filter(id__in=audit_ids).all() + check_user = UserModel.objects.get(id=int(user_id)) + for item in queryset: + # 条件是否成立 + if item.status != '0': # 0以后的状态是不能审核的 + return Response(data={'detail': '不能审核,因为订单已生效'}, status=status.HTTP_400_BAD_REQUEST) + if item.status == '0' and item.purchase: + if item.purchase.status != '1' and item.purchase.status != '2' and item.purchase.status != '5': + return Response(data={'detail': '不能审核,因为关联的采购订单未生效或者已经入库完成'}, status=status.HTTP_400_BAD_REQUEST) + # 处理审核的业务 + if item.this_debt: # 如果有欠款,需要修改供应商的期末应付 + SupplierModel.objects.filter(id=item.supplier_id).update(current_pay=F('current_pay') + item.this_debt) + if item.purchase: # 如果关联到采购单,需要修改采购单的状态为:部分入库或者全部入库 + # 先去查询已经入库的数量 + in_count_dict = PurchaseStorageModel.objects \ + .filter(purchase_id=item.purchase_id) \ + .exclude(status=0) \ + .aggregate(my_sum=Sum('number_count')) + in_count = 0 + if in_count_dict: + in_count = in_count_dict['my_sum'] + # 第一种情况: 已经入库数量 + 当前正在入库的数量 == 采购总数量。 + if (in_count + item.number_count) == item.purchase.number_count: + item.purchase.status = '3' # 全部入库 + else: # 第二种情况: 小于采购总数量 + item.purchase.status = '2' # 部分入库 + item.purchase.save() + + # 每一个入库单中货品的库存需要增加 + for storage_item in item.item_list.all(): + # 需要增加:原来的库存 += 当前入库数量 + GoodsInventoryModel.objects \ + .filter(goods_id=storage_item.goods_id, warehouse_id=storage_item.warehouse_id) \ + .update(cur_inventory=F('cur_inventory') + storage_item.purchase_count) + + # 修改审核状态 + self.get_queryset().filter(id__in=audit_ids).update(status='1', check_user_name=check_user.real_name, check_user_id=check_user.id) + return Response(status=status.HTTP_200_OK) \ No newline at end of file diff --git a/ERP_5/settings/dev.py b/ERP_5/settings/dev.py index 0dcc8ac..a479f68 100644 --- a/ERP_5/settings/dev.py +++ b/ERP_5/settings/dev.py @@ -45,6 +45,7 @@ INSTALLED_APPS = [ 'basic_info', 'goods_info', 'purchase_info', + 'warehouse_info', # 'django.contrib.staticfiles', # required for serving swagger ui's css/js files 'drf_yasg',