From 5062f48c8819a60a4c8eb3b7695589ec63f3a347 Mon Sep 17 00:00:00 2001 From: laoxiao Date: Sat, 22 Apr 2023 22:29:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=86=99=E5=AE=8C=E4=BA=86=E9=A2=86=E6=96=99?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ERP_5/apps/financial_info/models.py | 69 ++++++ .../serializer/receipt_serializer.py | 74 +++++++ ERP_5/apps/financial_info/urls.py | 3 +- .../financial_info/views/receipt_views.py | 197 ++++++++++++++++++ .../production_info/serializer/__init__.py | 0 .../serializer/bom_serializer.py | 44 ++++ .../serializer/product_task_serializer.py | 153 ++++++++++++++ ERP_5/apps/production_info/urls.py | 35 ++++ ERP_5/apps/production_info/views/__init__.py | 0 ERP_5/apps/production_info/views/bom_views.py | 84 ++++++++ .../views/product_task_views.py | 110 ++++++++++ ERP_5/apps/warehouse_info/models.py | 4 + .../serializer/out_storage_serializer.py | 20 ++ ERP_5/settings/dev.py | 1 + ERP_5/urls.py | 1 + ERP_5/utils/cont.py | 4 + 16 files changed, 798 insertions(+), 1 deletion(-) create mode 100644 ERP_5/apps/financial_info/serializer/receipt_serializer.py create mode 100644 ERP_5/apps/financial_info/views/receipt_views.py create mode 100644 ERP_5/apps/production_info/serializer/__init__.py create mode 100644 ERP_5/apps/production_info/serializer/bom_serializer.py create mode 100644 ERP_5/apps/production_info/serializer/product_task_serializer.py create mode 100644 ERP_5/apps/production_info/urls.py create mode 100644 ERP_5/apps/production_info/views/__init__.py create mode 100644 ERP_5/apps/production_info/views/bom_views.py create mode 100644 ERP_5/apps/production_info/views/product_task_views.py diff --git a/ERP_5/apps/financial_info/models.py b/ERP_5/apps/financial_info/models.py index e0fdd10..b82283a 100644 --- a/ERP_5/apps/financial_info/models.py +++ b/ERP_5/apps/financial_info/models.py @@ -77,3 +77,72 @@ class PaymentItemModel(BaseModel): verbose_name = '付款单中的 付款项目表' verbose_name_plural = verbose_name ordering = ['id'] + + +# 收款类型 +receipt_choices = ( + ('1', '销售定金'), + ('2', '销售出库货款'), + ('3', '客户还款的收款'), + ('4', '其他收款') +) + +# 收款单的模型类 +class ReceiptModel(BaseModel): + pay_date = models.DateTimeField('收款日期') + number_code = models.CharField('单据编号,不让用户填写', max_length=28) + discount_money = models.DecimalField('优惠金额(收款优惠),最多精确到小数点后两位', max_digits=10, default=0, decimal_places=2, + blank=True, null=True) + receipt_money = models.DecimalField('合计 收款金额最多精确到小数点后两位', max_digits=10, decimal_places=2, default=0) + this_money = models.DecimalField('实际 收款金额最多精确到小数点后两位', max_digits=10, decimal_places=2, default=0) + remark = models.CharField('备注', max_length=512, blank=True, null=True) + currency = models.CharField('货币种类', max_length=20, null=True, choices=currency_choices, default='CNY') + receipt_category = models.CharField('收款类型', max_length=2, null=True, choices=receipt_choices, default='1') + status = models.CharField('状态,0:未审核,1:已审核', max_length=1, default='0') + + account = models.ForeignKey('basic_info.SettlementAccountModel', null=True, blank=True, on_delete=models.SET_NULL, + verbose_name='结算账户,审核之后不能改') + operator_user = models.ForeignKey('erp_system.UserModel', related_name='operator2_pay_list', null=True, + blank=True, on_delete=models.SET_NULL, + verbose_name='财务操作人员,不能修改') + # 增加一个冗余字段 + operator_user_name = models.CharField('财务人员的真实姓名', max_length=20, null=True, blank=True) + check_user = models.ForeignKey('erp_system.UserModel', related_name='operator4_pay_list', null=True, blank=True, on_delete=models.SET_NULL, + verbose_name='审核人员,不能修改') + # 增加一个冗余字段 + check_user_name = models.CharField('审核人员的真实姓名', max_length=20, null=True, blank=True) + + customer = models.ForeignKey('basic_info.CustomerModel', null=True, blank=True, on_delete=models.SET_NULL, + verbose_name='客户,审核之后不能改') + # 增加一个冗余字段 + customer_name = models.CharField('客户名称', max_length=30, null=True, blank=True) + sale = models.ForeignKey('sale_info.SaleModel', null=True, blank=True, on_delete=models.SET_NULL, + verbose_name='销售订单,审核之后不能改') + attachment_list = models.CharField('附件的id列表,字段的值为: 1,2,3,4', max_length=20, null=True, blank=True) + + class Meta: + db_table = 't_receipt' + verbose_name = '收款单表' + verbose_name_plural = verbose_name + ordering = ['id'] + + +# 收款单中 收款项目的模型类 +class ReceiptItemModel(BaseModel): + # 冗余字段 + storage_code = models.CharField('销售出库单编号,不让用户填写', max_length=28) + deliver_storage = models.ForeignKey('warehouse_info.SaleDeliverModel', related_name='pay_item_list', null=True, blank=True, + on_delete=models.SET_NULL, verbose_name='采购入库单') + + receipt = models.ForeignKey('ReceiptModel', null=True, blank=True, related_name='item_list', on_delete=models.SET_NULL, + verbose_name='收款单,不能改') + should_money = models.DecimalField('应该 收款金额(就是采购入库单中需要支收的金额),最多精确到小数点后两位', max_digits=10, decimal_places=2, default=0) + this_money = models.DecimalField('本次 收款金额,最多精确到小数点后两位', max_digits=10, decimal_places=2, default=0) + + remark = models.CharField('备注', max_length=512, blank=True, null=True) + + class Meta: + db_table = 't_receipt_item' + verbose_name = '收款单中的 收款项目表' + verbose_name_plural = verbose_name + ordering = ['id'] diff --git a/ERP_5/apps/financial_info/serializer/receipt_serializer.py b/ERP_5/apps/financial_info/serializer/receipt_serializer.py new file mode 100644 index 0000000..3ce4260 --- /dev/null +++ b/ERP_5/apps/financial_info/serializer/receipt_serializer.py @@ -0,0 +1,74 @@ +from django.db import transaction +from django.db.models import Sum +from rest_framework import serializers +from rest_framework.exceptions import ValidationError + +from financial_info.models import ReceiptModel, ReceiptItemModel + + +class ReceiptItemSerializer(serializers.ModelSerializer): + """ + 收款单中 收款项目 的序列化器 + """ + # 查询当前销售 出库单中已经 收金额 + already_receipt = serializers.SerializerMethodField(read_only=True, help_text='已经收款的金额') + + class Meta: + model = ReceiptItemModel + fields = '__all__' + + def get_already_receipt(self, obj: ReceiptItemModel): + """ + 查询当前 销售出库单中已经 收的货款金额 + :param obj: ReceiptItemModel + :return: 0获取其他的数值 + """ + if obj.deliver_storage: + sum_dict = ReceiptItemModel.objects.filter(deliver_storage_id=obj.deliver_storage.id)\ + .exclude(receipt__status='0')\ + .aggregate(sum=Sum('this_money')) + if sum_dict: + return sum_dict['sum'] if sum_dict['sum'] else 0 + return 0 + return 0 + + +class ReceiptSerializer(serializers.ModelSerializer): + """ + 收款单——序列化器 + """ + item_list = ReceiptItemSerializer(many=True, required=False) + + class Meta: + model = ReceiptModel + fields = '__all__' + + def create(self, validated_data): + if 'item_list' not in validated_data: + return super(ReceiptSerializer, self).create(validated_data) + item_list = validated_data.pop('item_list') + with transaction.atomic(): + receipt = ReceiptModel.objects.create(**validated_data) + for item in item_list: + # 插入收款项目 + ReceiptItemModel.objects.create(receipt=receipt, **item) + return receipt + + # 必须重写update + def update(self, instance, validated_data): + if instance.status != '0': + raise ValidationError("收款单已经生效,不能修改!") + with transaction.atomic(): + + if 'item_list' in validated_data: + item_list = validated_data.pop('item_list') + old_list = instance.item_list.all() + + if old_list.exists(): + # 然后把旧数据删除,因为在validated_data拿不到货品库存数据的ID + instance.item_list.all().delete() + + for item in item_list: # 重新插入采购项 数据 + ReceiptItemModel.objects.create(receipt=instance, **item) + result = super(ReceiptSerializer, self).update(instance=instance, validated_data=validated_data) + return result diff --git a/ERP_5/apps/financial_info/urls.py b/ERP_5/apps/financial_info/urls.py index 67e36b3..9f8ac24 100644 --- a/ERP_5/apps/financial_info/urls.py +++ b/ERP_5/apps/financial_info/urls.py @@ -16,7 +16,7 @@ Including another URLconf from django.contrib import admin from django.urls import path, re_path from rest_framework.routers import DefaultRouter -from .views import payment_views +from .views import payment_views, receipt_views from rest_framework_jwt.views import obtain_jwt_token @@ -28,5 +28,6 @@ urlpatterns = [ router = DefaultRouter() router.register('payment', payment_views.PaymentViewSet) +router.register('receipt', receipt_views.ReceiptViewSet) urlpatterns += router.urls diff --git a/ERP_5/apps/financial_info/views/receipt_views.py b/ERP_5/apps/financial_info/views/receipt_views.py new file mode 100644 index 0000000..bf5d44a --- /dev/null +++ b/ERP_5/apps/financial_info/views/receipt_views.py @@ -0,0 +1,197 @@ +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 SettlementAccountModel, CustomerModel +from erp_system.models import UserModel +from financial_info.models import ReceiptModel, ReceiptItemModel +from financial_info.serializer.receipt_serializer import ReceiptSerializer +from warehouse_info.models import SaleDeliverModel + + +class ReceiptViewSet(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: 修改后的收款单信息 + + list: + 收款单--该接口可以弃用 + + 收款单列表信息, status: 200(成功), return: 收款单信息列表 + + retrieve: + 查询某一个收款单 + + 查询指定ID的收款单, status: 200(成功), return: 用户收款单 + """ + + queryset = ReceiptModel.objects.all() + serializer_class = ReceiptSerializer + pagination_class = GlobalPagination + + def get_queryset(self): + if self.action == 'find': # 过滤查询 + # 获取请求参数(在json中): + number_code = self.request.data.get('number_code', None) + check_user = self.request.data.get('check_user', 0) + start_date = self.request.data.get('start_date', None) + end_date = self.request.data.get('start_date', None) + customer = self.request.data.get('customer', 0) + operator_user = self.request.data.get('operator_user', 0) + status = self.request.data.get('status', None) + + account = self.request.data.get('account', 0) + sale_number_code = self.request.data.get('sale_number_code', None) + query = Q() + + if check_user: + query.add(Q(check_user__id=check_user), 'AND') + if account: + query.add(Q(account__id=account), 'AND') + if sale_number_code: + query.add(Q(sale__number_code__contains=sale_number_code), 'AND') + + if start_date: + query.add(Q(pay_date__gt=start_date), 'AND') + if end_date: + query.add(Q(pay_date__lt=end_date), 'AND') + + if customer: + query.add(Q(customer__id=customer), '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 ReceiptModel.objects.filter(query).distinct().all() + else: + return ReceiptModel.objects.all() + + params = openapi.Schema(type=openapi.TYPE_OBJECT, properties={ + '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"), + 'customer': openapi.Schema(type=openapi.TYPE_INTEGER, description="客户的ID"), + 'operator_user': openapi.Schema(type=openapi.TYPE_INTEGER, description="财务人员的ID"), + 'check_user': openapi.Schema(type=openapi.TYPE_INTEGER, description="审核人员的ID"), + + 'account': openapi.Schema(type=openapi.TYPE_INTEGER, description="结算账户的ID"), + 'sale_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(ReceiptViewSet, 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"), + }) + + @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 receipt in queryset: # 期中receipt是收款单 + # 判断1:关联的销售 单状态是否为“未审核” + if receipt.sale and receipt.sale.status == '0': + return Response(data={'detail': '销售订单未审核,不能收款'}, status=status.HTTP_400_BAD_REQUEST) + # 判断2 收款单的状态是否为:“已审核” + if receipt.status == '1': + return Response(data={'detail': '收款单已经审核,不需要再次审核'}, status=status.HTTP_400_BAD_REQUEST) + + # 处理关联业务逻辑 + # 业务一: 销售定金收款 + if receipt.receipt_category == '1' and receipt.sale and not receipt.item_list.exists(): + receipt.sale.status = '5' # 已收 销售定金 + receipt.sale.save() + # 业务二: 客户还款 的收款 + if receipt.receipt_category == '3' and not receipt.item_list.exists(): + CustomerModel.objects.filter(id=receipt.customer.id).update( + current_receivable=F('current_receivable') - receipt.this_money) + # 业务三: 出库货款 的收入 + if receipt.receipt_category == '2' and receipt.sale: + if receipt.item_list.exists(): # 这种判断不回从数据库中返回数据 + for item in receipt.item_list.all(): # 期中item是收款项目 + # 判断4: 出库单必须是已经审核状态 + if item.deliver_storage and item.deliver_storage.status != '1': + return Response(data={'detail': '出库单未审核或者已经收款,不能收款'}, + status=status.HTTP_400_BAD_REQUEST) + # 业务三——第1种情况 对应的出库单,是否应该:收款完成 ,首先需要查询该出库单的已经收款金额之和 + sum_dict = ReceiptItemModel.objects \ + .filter(deliver_storage_id=item.deliver_storage.id, receipt__status='1') \ + .aggregate(sum=Sum('this_money')) + in_sum = sum_dict['sum'] if sum_dict['sum'] else 0 + if item.should_money == (in_sum + item.this_money): + item.deliver_storage.status = '2' # 把出库单的状态修改为:收款完成 + item.deliver_storage.save() + # 业务三——第2种情况 对应的销售单状态是否要改为:销售完成 + # 销售完成的条件: 1、该销售单必须是:全部出库状态;2、该销售单中 所有的出库单状态也都必须是:收款完成 + sale = item.receipt.sale + if sale.status == '3': + # 查询 该销售单中 所有出库单状态不是:'收款完成' 的有多少个? + number = SaleDeliverModel.objects \ + .filter(sale_id=sale.id) \ + .exclude(status='2').count() + if number == 0: # 则 所有的出库单状态都为:收款完成 + sale.status = '4' # 修改销售单状态为: 销售完成 + sale.save() + + else: + return Response(data={'detail': '没有选择收款项目,支收货款的时候必须选择'}, + status=status.HTTP_400_BAD_REQUEST) + # 至此: 三种业务都完成 + # 业务四: 修改 结算账户的余额 + SettlementAccountModel.objects.filter(id=receipt.account.id) \ + .update(balance=F('balance') + receipt.this_money) + + # 至此: 四种业务都完成,开始修改收款单的状态以及审核信息 + # 审核 + 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) diff --git a/ERP_5/apps/production_info/serializer/__init__.py b/ERP_5/apps/production_info/serializer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ERP_5/apps/production_info/serializer/bom_serializer.py b/ERP_5/apps/production_info/serializer/bom_serializer.py new file mode 100644 index 0000000..a31e89b --- /dev/null +++ b/ERP_5/apps/production_info/serializer/bom_serializer.py @@ -0,0 +1,44 @@ +from rest_framework import serializers + +from ERP_5.utils.get_inventory import get_inventory_by_goods +from goods_info.models import GoodsModel +from production_info.models import BomProcessModel, BomMaterialModel + + +class ProcessSerializer(serializers.ModelSerializer): + """ + 生产工序的 序列化器 + """ + + class Meta: + model = BomProcessModel + fields = '__all__' + + +class MaterialSerializer(serializers.ModelSerializer): + """ + 生产物料的 序列化器 + """ + cur_inventory = serializers.SerializerMethodField(read_only=True, help_text='在仓库中库存') + + class Meta: + model = BomMaterialModel + fields = '__all__' + + def get_cur_inventory(self, obj: BomMaterialModel): # 当前的obj为:BomMaterialModel + result = get_inventory_by_goods(obj.goods.id) + return result if result else 0 + +class GoodsBomSerializer(serializers.ModelSerializer): + """ + 某一个货品的BOM表数据 序列化器 + """ + # 当前商品BOM表中所有的: 生产工序列表 + process_list = ProcessSerializer(many=True, read_only=True) + + # 当前商品BOM表中所有的: 生产物料列表 + material_list = MaterialSerializer(many=True, read_only=True) + + class Meta: + model = GoodsModel + fields = ['number_code', 'name', 'material_list', 'process_list'] \ No newline at end of file diff --git a/ERP_5/apps/production_info/serializer/product_task_serializer.py b/ERP_5/apps/production_info/serializer/product_task_serializer.py new file mode 100644 index 0000000..1db8008 --- /dev/null +++ b/ERP_5/apps/production_info/serializer/product_task_serializer.py @@ -0,0 +1,153 @@ +from django.db import transaction +from django.db.models import F +from django.utils import timezone +from rest_framework import serializers +from rest_framework.exceptions import ValidationError + +from ERP_5.utils.cont import NumberPrefix +from ERP_5.utils.generate_code import generate_code +from ERP_5.utils.get_inventory import get_inventory_by_goods +from basic_info.models import WarehouseModel +from goods_info.models import GoodsModel, GoodsInventoryModel +from production_info.models import ProductProcessModel, ProductMaterialModel, ProductTaskModel +from warehouse_info.models import SaleDeliverModel, SaleDeliverItemModel + + +class ProductProcessSerializer(serializers.ModelSerializer): + """ + 生产计划中的:生产工序 序列化器 + """ + + class Meta: + model = ProductProcessModel + fields = '__all__' + + +class ProductMaterialSerializer(serializers.ModelSerializer): + """ + 生产计划中的:生产物料 序列化器 + """ + cur_inventory = serializers.SerializerMethodField(read_only=True, help_text='在仓库中库存') + + class Meta: + model = ProductMaterialModel + fields = '__all__' + + def get_cur_inventory(self, obj: ProductMaterialModel): # 当前的obj为:ProductMaterialModel + result = get_inventory_by_goods(obj.goods.id) + return result if result else 0 + + +class ProductTaskSerializer(serializers.ModelSerializer): + """ + 生产任务的 序列化器 + """ + + # 当前生产任务中所有的: 生产工序列表 + process_list = ProductProcessSerializer(many=True) + + # 当前生产任务中所有的: 生产物料列表 + material_list = ProductMaterialSerializer(many=True) + + class Meta: + model = ProductTaskModel + fields = '__all__' + + def create(self, validated_data): + process_list = validated_data.pop('process_list') + material_list = validated_data.pop('material_list') + with transaction.atomic(): + pt = ProductTaskModel.objects.create(**validated_data) + for item in process_list: + ProductProcessModel.objects.create(product_task=pt, **item) + for item in material_list: + ProductMaterialModel.objects.create(product_task=pt, **item) + return pt + + def update(self, instance, validated_data): + if instance.status != '0': + raise ValidationError("生产任务已经生效,不能修改!") + process_list = validated_data.pop('process_list') + material_list = validated_data.pop('material_list') + old_list1 = instance.process_list + old_list2 = instance.material_list + with transaction.atomic(): + if old_list1.exists(): + # 然后把旧数据删除,因为在validated_data拿不到货品库存数据的ID + instance.process_list.all().delete() + if old_list2.exists(): + # 然后把旧数据删除,因为在validated_data拿不到货品库存数据的ID + instance.material_list.all().delete() + for item in process_list: + ProductProcessModel.objects.create(product_task=instance, **item) + for item in material_list: + ProductMaterialModel.objects.create(product_task=instance, **item) + result = super(ProductTaskSerializer, self).update(instance=instance, validated_data=validated_data) + return result + + +class GetMaterialSerializer(serializers.Serializer): + """ + 领料的序列化器 + """ + + def update(self, instance, validated_data): + pass + + def create(self, validated_data): + # 需要完成4件事情 + with transaction.atomic(): + # 1、插入出库单 + deliver = SaleDeliverModel.objects.create(invoices_date=timezone.now(), + number_code=generate_code(NumberPrefix['deliver'].value), + last_amount=validated_data['money'], + number_count=validated_data['count'], + product_task_id=validated_data['pt_id'], + status='1', + is_other='1') + # 2、插入出库项目 + goods = GoodsModel.objects.get(pk=validated_data['good_id']) + wh = WarehouseModel.objects.get(pk=validated_data['warehouse_id']) + psi = SaleDeliverItemModel() + psi.goods_id = goods.id + 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.name = goods.name + + psi.sale_count = validated_data['count'] + psi.sale_price = validated_data['price'] + psi.sale_money = validated_data['money'] + psi.warehouse_name = wh.name + psi.warehouse_id = wh.id + + psi.sale_deliver_id = deliver.id + psi.save() + + # 3、 修改库存数量 + GoodsInventoryModel.objects \ + .filter(goods_id=goods.id, warehouse_id=wh.id) \ + .update(cur_inventory=F('cur_inventory') - psi.sale_count) + # 4、 修改生产物料中领料数量 + ProductMaterialModel.objects.filter(id=validated_data['material_id']) \ + .update(get_count=F('get_count') + psi.sale_count) + + return deliver + + goods_number_code = serializers.CharField(help_text='物料的条码或者编号', required=False) + warehouse_name = serializers.CharField(help_text='领取仓库的名字') + warehouse_id = serializers.IntegerField(help_text='领取仓库的主键') + good_id = serializers.IntegerField(help_text='领取的货品主键') + + count = serializers.DecimalField(help_text='领取数量,最多精确到小数点后两位', max_digits=10, decimal_places=2, default=0) + price = serializers.DecimalField(help_text='领取的成本单价,最多精确到小数点后两位', max_digits=10, decimal_places=2, default=0) + money = serializers.DecimalField(help_text='领取的成本总价,最多精确到小数点后两位', max_digits=10, decimal_places=2, default=0) + + material_id = serializers.IntegerField(help_text='所属生产物料的主键') + pt_id = serializers.IntegerField(help_text='所属生产任务的主键') + diff --git a/ERP_5/apps/production_info/urls.py b/ERP_5/apps/production_info/urls.py new file mode 100644 index 0000000..54abf78 --- /dev/null +++ b/ERP_5/apps/production_info/urls.py @@ -0,0 +1,35 @@ +"""ERP_5 URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, re_path +from rest_framework.routers import DefaultRouter +from .views import bom_views, product_task_views + +from rest_framework_jwt.views import obtain_jwt_token + +urlpatterns = [ + # path('admin/', admin.site.urls), + + +] + +router = DefaultRouter() +router.register('bom_material', bom_views.MaterialViewSets) +router.register('bom_process', bom_views.ProcessViewSets) +router.register('bom', bom_views.GoodsBomViewSet) +router.register('products', product_task_views.ProductTaskViewSets) + +urlpatterns += router.urls diff --git a/ERP_5/apps/production_info/views/__init__.py b/ERP_5/apps/production_info/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ERP_5/apps/production_info/views/bom_views.py b/ERP_5/apps/production_info/views/bom_views.py new file mode 100644 index 0000000..45c2c17 --- /dev/null +++ b/ERP_5/apps/production_info/views/bom_views.py @@ -0,0 +1,84 @@ +from rest_framework import mixins, viewsets + +from ERP_5.utils.base_views import MultipleDestroyMixin +from goods_info.models import GoodsModel +from production_info.models import BomMaterialModel, BomProcessModel +from production_info.serializer.bom_serializer import MaterialSerializer, ProcessSerializer, GoodsBomSerializer + + +class MaterialViewSets(mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, + mixins.RetrieveModelMixin, viewsets.GenericViewSet, MultipleDestroyMixin): + """ + create: + 生产物料--新增, + + 生产物料新增, status: 201(成功), return: 新增生产物料信息 + + destroy: + 生产物料--删除 + + 生产物料删除, status: 204(成功), return: None + + multiple_delete: + 生产物料--批量删除,必传参数:ids=[1,2,3,4...] + + 生产物料批量删除, status: 204(成功), return: None + + update: + 生产物料--修改, + + 生产物料修改, status: 200(成功), return: 修改后的生产物料信息 + + retrieve: + 查询某一个生产物料 + + 查询指定ID的生产物料, status: 200(成功), return: 生产物料 + """ + + queryset = BomMaterialModel.objects.all() + serializer_class = MaterialSerializer + + +class ProcessViewSets(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, + mixins.DestroyModelMixin, viewsets.GenericViewSet, MultipleDestroyMixin): + """ + create: + 生产工序--新增, + + 生产工序新增, status: 201(成功), return: 新增生产工序信息 + + destroy: + 生产工序--删除 + + 生产工序删除, status: 204(成功), return: None + + multiple_delete: + 生产工序--批量删除,必传参数:ids=[1,2,3,4...] + + 生产工序批量删除, status: 204(成功), return: None + + update: + 生产工序--修改, + + 生产工序修改, status: 200(成功), return: 修改后的生产物料信息 + + retrieve: + 查询某一个生产工序 + + 查询指定ID的生产工序, status: 200(成功), return: 生产工序 + """ + + queryset = BomProcessModel.objects.all() + serializer_class = ProcessSerializer + + +class GoodsBomViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): + """ + retrieve: + 查询某一个货品的BOM表数据(包括:BOM表中物料清单和工序清单) + + 包括BOM中的物料清单和生产工序列表, status: 200(成功), return: BOM表 + """ + + serializer_class = GoodsBomSerializer + queryset = GoodsModel.objects.all() diff --git a/ERP_5/apps/production_info/views/product_task_views.py b/ERP_5/apps/production_info/views/product_task_views.py new file mode 100644 index 0000000..971a88d --- /dev/null +++ b/ERP_5/apps/production_info/views/product_task_views.py @@ -0,0 +1,110 @@ +from django.db.models import Q +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema +from rest_framework import viewsets +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 production_info.models import ProductTaskModel +from production_info.serializer.product_task_serializer import ProductTaskSerializer, GetMaterialSerializer + + +class ProductTaskViewSets(viewsets.ModelViewSet, MultipleDestroyMixin, MultipleAuditMixin): + """ + create: + 生产任务--新增,注意:如果有BOM表,则直接读取BOM。如果没有需要自己添加生产物料和工序 + + 生产任务新增, 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 = ProductTaskModel.objects.all() + serializer_class = ProductTaskSerializer + 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) + query = Q() + if keyword: + child_query = Q() + child_query.add(Q(name__contains=keyword), 'OR') + child_query.add(Q(specification=keyword), 'OR') + query.add(child_query, '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 number_code: + query.add(Q(number_code__contains=number_code), 'AND') + + return ProductTaskModel.objects.filter(query).distinct().all() + else: + return ProductTaskModel.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") + }) + # 分页参数必须是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(ProductTaskViewSets, self).list(request=request, *args, **kwargs) + + @swagger_auto_schema(method='POST', request_body=GetMaterialSerializer, + operation_description="生产任务中领取物料") + @action(methods=['POST'], detail=False) + def get_material(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + result = serializer.save() + return Response({'msg': 'ok'}) + + def get_serializer_class(self): + if self.action == 'get_material': + return GetMaterialSerializer + else: + return ProductTaskSerializer diff --git a/ERP_5/apps/warehouse_info/models.py b/ERP_5/apps/warehouse_info/models.py index 10db44d..8d27dce 100644 --- a/ERP_5/apps/warehouse_info/models.py +++ b/ERP_5/apps/warehouse_info/models.py @@ -144,6 +144,10 @@ class SaleDeliverModel(BaseModel): is_other = models.CharField('是否是其他出库,0:销售出库,1:其他出库', max_length=1, default='0') + product_task = models.ForeignKey('production_info.ProductTaskModel', null=True, blank=True, + on_delete=models.SET_NULL, + verbose_name='所属的生产任务') + class Meta: db_table = 't_sale_deliver' verbose_name = '出库单表' diff --git a/ERP_5/apps/warehouse_info/serializer/out_storage_serializer.py b/ERP_5/apps/warehouse_info/serializer/out_storage_serializer.py index 3b43404..18e3c17 100644 --- a/ERP_5/apps/warehouse_info/serializer/out_storage_serializer.py +++ b/ERP_5/apps/warehouse_info/serializer/out_storage_serializer.py @@ -1,8 +1,10 @@ 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 financial_info.models import ReceiptItemModel from warehouse_info.models import SaleDeliverItemModel, SaleDeliverModel @@ -30,10 +32,28 @@ class DeliverySerializer(serializers.ModelSerializer): # 出库单列表中需要展示:商品信息。 goods_info = serializers.SerializerMethodField(read_only=True, label='列表中展示的商品信息') + # 查询当前销售 出库单中已经 收金额 + already_receipt = serializers.SerializerMethodField(read_only=True, help_text='已经收款的金额') + class Meta: model = SaleDeliverModel fields = '__all__' + def get_already_receipt(self, obj: SaleDeliverModel): + """ + 查询当前 销售出库单中已经 收的货款金额 + :param obj: SaleDeliverModel + :return: 0获取其他的数值 + """ + sum_dict = ReceiptItemModel.objects.filter(deliver_storage_id=obj.id)\ + .exclude(receipt__status='0')\ + .aggregate(sum=Sum('this_money')) + if sum_dict: + return sum_dict['sum'] if sum_dict['sum'] else 0 + return 0 + + + def get_goods_info(self, obj): """ 商品信息是由: 商品1名称 商品1规格 , 商品2名称 商品2规格 ,.... diff --git a/ERP_5/settings/dev.py b/ERP_5/settings/dev.py index 343aabb..92b3fbc 100644 --- a/ERP_5/settings/dev.py +++ b/ERP_5/settings/dev.py @@ -48,6 +48,7 @@ INSTALLED_APPS = [ 'warehouse_info', 'financial_info', 'sale_info', + 'production_info', # 'django.contrib.staticfiles', # required for serving swagger ui's css/js files 'drf_yasg', diff --git a/ERP_5/urls.py b/ERP_5/urls.py index 9695f8c..6198c4c 100644 --- a/ERP_5/urls.py +++ b/ERP_5/urls.py @@ -42,6 +42,7 @@ urlpatterns = [ path('api/', include('warehouse_info.urls')), path('api/', include('financial_info.urls')), path('api/', include('sale_info.urls')), + path('api/', include('production_info.urls')), re_path(r'^api/generate_code/$', GenerateCode.as_view()), # 媒体资源文件的访问路由 diff --git a/ERP_5/utils/cont.py b/ERP_5/utils/cont.py index fb12176..5ba7e9c 100644 --- a/ERP_5/utils/cont.py +++ b/ERP_5/utils/cont.py @@ -15,3 +15,7 @@ class NumberPrefix(Enum): # 定义编号的前缀 cat = 'CAT' # 商品信息编号的前缀 goo = 'GOO' + # 出库单编号的前缀 + deliver = 'OUT' + # 入库单编号的前缀 + storage = 'INS'