diff --git a/ERP_5/apps/financial_info/models.py b/ERP_5/apps/financial_info/models.py index e0fdd10..7771d42 100644 --- a/ERP_5/apps/financial_info/models.py +++ b/ERP_5/apps/financial_info/models.py @@ -77,3 +77,64 @@ class PaymentItemModel(BaseModel): verbose_name = '付款单中的 付款项目表' verbose_name_plural = verbose_name ordering = ['id'] + + +# 收款单的模型类 +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') + pay_category = models.CharField('收款类型', max_length=2, null=True, choices=pay_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='operator_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='operator3_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('purchase_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'] \ No newline at end of file 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..428c81d --- /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 payment + + # 必须重写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/views/receipt_views.py b/ERP_5/apps/financial_info/views/receipt_views.py new file mode 100644 index 0000000..578da95 --- /dev/null +++ b/ERP_5/apps/financial_info/views/receipt_views.py @@ -0,0 +1,196 @@ +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, SettlementAccountModel +from erp_system.models import UserModel +from financial_info.models import ReceiptModel, ReceiptItemModel +from financial_info.serializer.payment_serializer import PaymentSerializer +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) + supplier = self.request.data.get('supplier', 0) + operator_user = self.request.data.get('operator_user', 0) + status = self.request.data.get('status', None) + + account = self.request.data.get('account', 0) + purchase_number_code = self.request.data.get('purchase_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 purchase_number_code: + query.add(Q(purchase__number_code__contains=purchase_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 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 PaymentModel.objects.filter(query).distinct().all() + else: + return PaymentModel.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"), + 'supplier': 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"), + '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(PaymentViewSet, 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 pm in queryset: # 期中pm是收款单 + # 判断1:关联的采购单状态是否为“未审核” + if pm.purchase and pm.purchase.status == '0': + return Response(data={'detail': '采购订单未审核,不能收款'}, status=status.HTTP_400_BAD_REQUEST) + # 判断2 收款单的状态是否为:“已审核” + if pm.status == '1': + return Response(data={'detail': '收款单已经审核,不需要再次审核'}, status=status.HTTP_400_BAD_REQUEST) + # 判断3 结算账户的余额是否够用 + if pm.this_money > pm.account.balance: + return Response(data={'detail': '选择的结算账户中的余额不够,不能收款'}, status=status.HTTP_400_BAD_REQUEST) + + # 处理关联业务逻辑 + # 业务一: 采购定金收款 + if pm.pay_category == '1' and pm.purchase and not pm.item_list.exists(): + pm.purchase.status = '5' # 已收定金 + pm.purchase.save() + # 业务二: 供应商欠款 支收 + if pm.pay_category == '3' and not pm.item_list.exists(): + SupplierModel.objects.filter(id=pm.supplier.id).update(current_pay=F('current_pay') - pm.pay_money) + # 业务三: 入库货款 支收 + if pm.pay_category == '2' and pm.purchase: + if pm.item_list.exists(): # 这种判断不回从数据库中返回数据 + for item in pm.item_list.all(): # 期中item是收款项目 + # 判断4: 入库单必须是已经审核状态 + if item.purchase_storage and item.purchase_storage.status != '1': + return Response(data={'detail': '入库单未审核或者已经收款,不能收款'}, status=status.HTTP_400_BAD_REQUEST) + # 业务三——第1种情况 对应的入库单,是否应该:收款完成 ,首先需要查询该入库单的已经收款金额之和 + sum_dict = PaymentItemModel.objects\ + .filter(purchase_storage_id=item.purchase_storage.id, payment__status='1')\ + .aggregate(sum=Sum('this_money')) + pay_sum = sum_dict['sum'] if sum_dict['sum'] else 0 + if item.should_money == (pay_sum + item.this_money): + item.purchase_storage.status = '2' # 把入库单的状态修改为:收款完成 + item.purchase_storage.save() + # 业务三——第2种情况 对应的采购单状态是否要改为:采购完成 + # 采购完成的条件: 1、该采购单必须是:全部入库状态;2、该采购单中 所有的入库单状态也都必须是:收款完成 + purchase = item.payment.purchase + if purchase.status == '3': + # 查询 该采购单中 所有入库单状态不是:'收款完成' 的有多少个? + number = PurchaseStorageModel.objects\ + .filter(purchase_id=purchase.id)\ + .exclude(status='2').count() + if number == 0: # 则 所有的入库单状态都为:收款完成 + purchase.status = '4' # 修改采购单状态为: 采购完成 + purchase.save() + + else: + return Response(data={'detail': '没有选择收款项目,支收货款的时候必须选择'}, status=status.HTTP_400_BAD_REQUEST) + # 至此: 三种业务都完成 + # 业务四: 修改 结算账户的余额 + SettlementAccountModel.objects.filter(id=pm.account.id)\ + .update(balance=F('balance') - pm.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) \ No newline at end of file