From b7447c7e9c0d719c665327d880d57af6cc21f62a Mon Sep 17 00:00:00 2001 From: Czw <459749926@qq.com> Date: Thu, 11 Nov 2021 23:58:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=87=E8=B4=AD=E5=8D=95=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/finance/models.py | 14 ++- apps/finance/urls.py | 2 + apps/purchase/filters.py | 13 ++- apps/purchase/models.py | 13 ++- apps/purchase/permissions.py | 10 +- apps/purchase/serializers.py | 179 ++++++++++++++++++++++++++++++++++- apps/purchase/views.py | 22 ++++- apps/system/models.py | 2 + extensions/models.py | 3 + 9 files changed, 251 insertions(+), 7 deletions(-) diff --git a/apps/finance/models.py b/apps/finance/models.py index 88eb374..a71b98f 100644 --- a/apps/finance/models.py +++ b/apps/finance/models.py @@ -11,7 +11,6 @@ class PaymentOrder(Model): remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注') total_amount = AmountField(null=True, verbose_name='总金额') discount_amount = AmountField(default=0, verbose_name='优惠金额') - payment_amount = AmountField(null=True, verbose_name='实付金额') is_void = BooleanField(default=False, verbose_name='作废状态') creator = ForeignKey('system.User', on_delete=PROTECT, related_name='created_payment_orders', verbose_name='创建人') @@ -21,6 +20,19 @@ class PaymentOrder(Model): class Meta: unique_together = [('number', 'team')] + @classmethod + def get_number(cls, team): + start_date, end_date = pendulum.now(), pendulum.tomorrow() + instance = cls.objects.filter(team=team, create_time__gte=start_date, create_time__lt=end_date).last() + + try: + result = re.match('^(.*?)([1-9]+)$', instance.number) + number = result.group(1) + str(int(result.group(2)) + 1) + except AttributeError: + number = 'FK' + start_date.format('YYYYMMDD') + '0001' + + return number + class PaymentAccount(Model): """付款账户""" diff --git a/apps/finance/urls.py b/apps/finance/urls.py index 60018cb..ef8429f 100644 --- a/apps/finance/urls.py +++ b/apps/finance/urls.py @@ -1,5 +1,7 @@ from extensions.routers import * +from apps.purchase.views import * router = BaseRouter() +router.register('purchase_orders', PurchaseOrderViewSet, 'purchase_order') urlpatterns = router.urls diff --git a/apps/purchase/filters.py b/apps/purchase/filters.py index a505264..a424348 100644 --- a/apps/purchase/filters.py +++ b/apps/purchase/filters.py @@ -1,7 +1,18 @@ from django_filters.rest_framework import FilterSet from django_filters.filters import * +from apps.purchase.models import * + + +class PurchaseOrderFilter(FilterSet): + start_date = DateFilter(field_name='create_time', lookup_expr='gte', label='开始日期') + end_date = DateFilter(field_name='create_time', lookup_expr='lt', label='结束日期') + + class Meta: + model = PurchaseOrder + fields = ['number', 'warehouse', 'supplier', 'handler', 'is_void', 'creator', + 'start_date', 'end_date'] __all__ = [ - + 'PurchaseOrderFilter', ] diff --git a/apps/purchase/models.py b/apps/purchase/models.py index b58996a..32fd2de 100644 --- a/apps/purchase/models.py +++ b/apps/purchase/models.py @@ -10,8 +10,13 @@ class PurchaseOrder(Model): handler = ForeignKey('system.User', on_delete=PROTECT, related_name='purchase_orders', verbose_name='经手人') handle_time = DateTimeField(verbose_name='处理时间') remark = CharField(max_length=256, null=True, blank=True, verbose_name='备注') - total_amount = AmountField(verbose_name='总金额') - total_quantity = FloatField(verbose_name='总数量') + total_quantity = FloatField(verbose_name='采购总数量') + other_amount = AmountField(default=0, verbose_name='其他费用') + total_amount = AmountField(verbose_name='采购总金额') + payment_amount = AmountField(default=0, verbose_name='付款金额') + arrears_amount = AmountField(default=0, verbose_name='欠款金额') + payment_order = OneToOneField('finance.PaymentOrder', on_delete=PROTECT, null=True, + related_name='purchase_order', verbose_name='付款单据') is_void = BooleanField(default=False, verbose_name='作废状态') enable_auto_stock_in = BooleanField(default=False, verbose_name='启用自动入库') creator = ForeignKey('system.User', on_delete=PROTECT, @@ -22,6 +27,10 @@ class PurchaseOrder(Model): class Meta: unique_together = [('number', 'team')] + @classmethod + def get_number(cls, team): + return + class PurchaseGoods(Model): """采购商品""" diff --git a/apps/purchase/permissions.py b/apps/purchase/permissions.py index b4f0898..ca289c3 100644 --- a/apps/purchase/permissions.py +++ b/apps/purchase/permissions.py @@ -1,6 +1,14 @@ from extensions.permissions import InterfacePermission -__all__ = [ +class PurchaseOrderPermission(InterfacePermission): + code = 'purchase_order' + +class PurchaseReturnOrderPermission(InterfacePermission): + code = 'purchase_return_order' + + +__all__ = [ + 'PurchaseOrderPermission', 'PurchaseReturnOrderPermission', ] diff --git a/apps/purchase/serializers.py b/apps/purchase/serializers.py index e56ad7f..add84b3 100644 --- a/apps/purchase/serializers.py +++ b/apps/purchase/serializers.py @@ -1,7 +1,184 @@ from extensions.serializers import * from extensions.exceptions import * +from apps.purchase.models import * +from apps.finance.models import * +from apps.data.models import * +from apps.goods.models import * +from apps.system.models import * + + +class PurchaseOrderSerializer(BaseSerializer): + """采购单据""" + + class PurchaseGoodsSerializer(BaseSerializer): + """采购商品""" + + goods_number = CharField(source='goods.number', read_only=True, label='商品编号') + goods_name = CharField(source='goods.name', read_only=True, label='商品名称') + goods_barcode = CharField(source='goods.barcode', read_only=True, label='商品条码') + unit_name = CharField(source='goods.unit.name', read_only=True, label='单位名称') + + class Meta: + model = PurchaseGoods + read_only_fields = ['id', 'goods_number', 'goods_name', 'goods_barcode', 'total_amount', + 'return_quantity', 'unit_name'] + fields = ['goods', 'purchase_quantity', 'purchase_price', *read_only_fields] + + def validate_goods(self, instance): + instance = self.validate_foreign_key(Goods, instance) + if not instance.is_active: + raise ValidationError(f'商品[{instance.name}]未激活') + return instance + + def validate_purchase_quantity(self, value): + if value <= 0: + raise ValidationError('采购数量小于或等于零') + return value + + def validate_purchase_price(self, value): + if value <= 0: + raise ValidationError('采购单价小于或等于零') + return value + + class PaymentAccountSerializer(BaseSerializer): + """付款账户""" + + account_number = CharField(source='account.number', read_only=True, label='账户编号') + account_name = CharField(source='account.name', read_only=True, label='账户名称') + + class Meta: + model = PaymentAccount + read_only_fields = ['id', 'account_number', 'account_name'] + fields = ['account', 'payment_amount', *read_only_fields] + + def validate_account(self, instance): + instance = self.validate_foreign_key(Account, instance) + if not instance.is_active: + raise ValidationError(f'结算账户[{instance.name}]未激活') + return instance + + def validate_payment_amount(self, value): + if value <= 0: + raise ValidationError('付款金额小于或等于零') + return value + + warehouse_number = CharField(source='warehouse.number', read_only=True, label='仓库编号') + warehouse_name = CharField(source='warehouse.name', read_only=True, label='仓库名称') + supplier_number = CharField(source='supplier.number', read_only=True, label='供应商编号') + supplier_name = CharField(source='supplier.name', read_only=True, label='供应商名称') + handler_name = CharField(source='handler.name', read_only=True, label='经手人名称') + creator_name = CharField(source='creator.name', read_only=True, label='创建人名称') + purchase_goods_items = PurchaseGoodsSerializer(source='purchase_goods_set', many=True, label='采购商品') + payment_account_items = PaymentAccountSerializer(source='payment_order.payment_accounts', + required=False, many=True, label='付款账户') + + class Meta: + model = PurchaseOrder + read_only_fields = ['id', 'warehouse_number', 'warehouse_name', 'supplier_number', 'supplier_name', + 'handler_name', 'total_amount', 'total_quantity', 'payment_amount', 'arrears_amount', + 'is_void', 'enable_auto_stock_in', 'creator_name', 'create_time'] + fields = ['number', 'warehouse', 'supplier', 'handler', 'handle_time', 'other_amount', 'remark', + 'purchase_goods_items', 'payment_account_items', *read_only_fields] + + def validate_number(self, value): + self.validate_unique({'number': value}, message=f'编号[{value}]已存在') + return value + + def validate_warehouse(self, instance): + instance = self.validate_foreign_key(Warehouse, instance) + if not instance.is_active: + raise ValidationError(f'仓库[{instance.name}]未激活') + + if not instance.is_locked: + raise ValidationError(f'仓库[{instance.name}]已锁定') + return instance + + def validate_supplier(self, instance): + instance = self.validate_foreign_key(Supplier, instance) + if not instance.is_active: + raise ValidationError(f'供应商[{instance.name}]未激活') + return instance + + def validate_handler(self, instance): + instance = self.validate_foreign_key(User, instance) + if not instance.is_active: + raise ValidationError(f'经手人[{instance.name}]未激活') + return instance + + def validate_other_amount(self, value): + if value <= 0: + raise ValidationError('其他费用小于或等于零') + return value + + @transaction.atomic + def create(self, validated_data): + purchase_goods_items = validated_data.pop('purchase_goods_items') + payment_account_items = validated_data.pop('payment_account_items', None) + print(purchase_goods_items) + print(payment_account_items) + + validated_data['enable_auto_stock_in'] = self.team.enable_auto_stock_in + validated_data['creator'] = self.user + purchase_order = super().create(validated_data) + + total_purchase_quantity = 0 + total_purchase_amount = 0 + + # 创建采购商品 + purchase_goods_set = [] + for purchase_goods_item in purchase_goods_items: + purchase_quantity = purchase_goods_item['purchase_quantity'] + purchase_price = purchase_goods_item['purchase_price'] + total_amount = NP.times(purchase_quantity, purchase_price) + purchase_goods_set.append(PurchaseGoods( + purchase_order=purchase_order, goods=purchase_goods_item['goods'], + purchase_quantity=purchase_quantity, purchase_price=purchase_price, + total_amount=total_amount, team=self.team + )) + + total_purchase_quantity = NP.plus(total_purchase_quantity, purchase_quantity) + total_purchase_amount = NP.plus(total_purchase_amount, total_amount) + else: + PurchaseGoods.objects.bulk_create(purchase_goods_set) + total_purchase_amount = NP.plus(total_purchase_amount, purchase_order.other_amount) + purchase_order.total_quantity = total_purchase_quantity + purchase_order.total_amount = total_purchase_amount + + # 创建付款单据 + if payment_account_items: + payment_order_number = PaymentOrder.get_number(team=self.team) + payment_order_remark = f'采购单据: {purchase_order.number}' + payment_order = PaymentOrder.objects.create( + number=payment_order_number, supplier=purchase_order.supplier, + handler=purchase_order.handler, handle_time=purchase_order.handle_time, + remark=payment_order_remark, creator=self.user, team=self.team + ) + + total_payment_amount = 0 + + # 创建付款账户 + payment_accounts = [] + for payment_account_item in payment_account_items: + payment_amount = payment_account_item['payment_amount'] + payment_accounts.append(PaymentAccount( + payment_order=payment_order, account=payment_account_item['account'], + payment_amount=payment_amount, team=self.team + )) + + total_payment_amount = NP.plus(total_payment_amount, payment_amount) + else: + PaymentAccount.objects.bulk_create(payment_accounts) + payment_order.total_amount = total_payment_amount + payment_order.save(update_fields=['total_amount']) + purchase_order.payment_amount = total_payment_amount + purchase_order.arrears_amount = NP.minus(total_purchase_amount, total_payment_amount) + purchase_order.payment_order = payment_order + + purchase_order = purchase_order.save(update_fields=['total_quantity', 'total_amount', 'payment_amount', + 'arrears_amount', 'payment_order']) + return purchase_order __all__ = [ - + 'PurchaseOrderSerializer', ] diff --git a/apps/purchase/views.py b/apps/purchase/views.py index 02bf587..f08b3b8 100644 --- a/apps/purchase/views.py +++ b/apps/purchase/views.py @@ -1,8 +1,28 @@ from extensions.permissions import * from extensions.exceptions import * from extensions.viewsets import * +from apps.purchase.serializers import * +from apps.purchase.permissions import * +from apps.purchase.filters import * +from apps.purchase.schemas import * +from apps.purchase.models import * + + +class PurchaseOrderViewSet(BaseViewSet, ListModelMixin, RetrieveModelMixin, CreateModelMixin): + """采购单据""" + + serializer_class = PurchaseOrderSerializer + permission_classes = [IsAuthenticated, PurchaseOrderPermission] + filterset_class = PurchaseOrderFilter + search_fields = ['number', 'supplier__number', 'supplier__name', 'remark'] + ordering_fields = ['id', 'number', 'total_quantity', 'total_amount', 'create_time'] + ordering = ['-number', 'id'] + select_related_fields = ['warehouse', 'supplier', 'handler', 'creator', + 'purchase_goods_set__goods__unit'] + prefetch_related_fields = ['purchase_goods_set', 'payment_order__payment_accounts'] + queryset = PurchaseOrder.objects.all() __all__ = [ - + 'PurchaseOrderViewSet', ] diff --git a/apps/system/models.py b/apps/system/models.py index 7bccc5f..b51721f 100644 --- a/apps/system/models.py +++ b/apps/system/models.py @@ -7,6 +7,8 @@ class Team(Model): number = CharField(max_length=32, unique=True, verbose_name='编号') expiry_time = DateTimeField(verbose_name='到期时间') create_time = DateTimeField(auto_now_add=True, verbose_name='创建时间') + enable_auto_stock_in = BooleanField(default=False, verbose_name='启用自动入库') + enable_auto_stock_out = BooleanField(default=False, verbose_name='启用自动出库') class PermissionType(Model): diff --git a/extensions/models.py b/extensions/models.py index 595cfb8..b0fb14b 100644 --- a/extensions/models.py +++ b/extensions/models.py @@ -6,6 +6,8 @@ from django.db.models import Model, IntegerChoices, TextChoices from django.db.models import Sum, Count, Value, F, Q, Prefetch from django.db.models.functions import Coalesce from django.db import transaction, connection +import pendulum +import re class AmountField(DecimalField): @@ -23,4 +25,5 @@ __all__ = [ 'BooleanField', 'IntegerField', 'FloatField', 'DecimalField', 'AmountField', 'CharField', 'DateField', 'DateTimeField', 'JSONField', 'FileField', 'ImageField', 'Sum', 'Count', 'Value', 'F', 'Q', 'Prefetch', 'Coalesce', 'transaction', 'connection', + 'pendulum', 're', ]