From 16e4d85f5d260a60be8559c7da67730769b56673 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Mon, 18 Sep 2023 14:06:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0sam=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/pum/filters.py | 4 +- apps/sam/__init__.py | 0 apps/sam/admin.py | 3 + apps/sam/apps.py | 6 ++ apps/sam/migrations/0001_initial.py | 117 ++++++++++++++++++++++++++++ apps/sam/migrations/__init__.py | 0 apps/sam/models.py | 78 +++++++++++++++++++ apps/sam/serializers.py | 65 ++++++++++++++++ apps/sam/tests.py | 3 + apps/sam/urls.py | 15 ++++ apps/sam/views.py | 92 ++++++++++++++++++++++ 11 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 apps/sam/__init__.py create mode 100644 apps/sam/admin.py create mode 100644 apps/sam/apps.py create mode 100644 apps/sam/migrations/0001_initial.py create mode 100644 apps/sam/migrations/__init__.py create mode 100644 apps/sam/models.py create mode 100644 apps/sam/serializers.py create mode 100644 apps/sam/tests.py create mode 100644 apps/sam/urls.py create mode 100644 apps/sam/views.py diff --git a/apps/pum/filters.py b/apps/pum/filters.py index 8f1c346e..0f9372a7 100644 --- a/apps/pum/filters.py +++ b/apps/pum/filters.py @@ -5,7 +5,7 @@ class PuPlanFilter(filters.FilterSet): class Meta: model = PuPlan fields = { - "state": ["exact", "__in"] + "state": ["exact", "in"] } class PuPlanItemFilter(filters.FilterSet): @@ -14,6 +14,6 @@ class PuPlanItemFilter(filters.FilterSet): fields = { "material": ["exact"], "pu_plan": ["exact"], - "pu_order": ["exact", '__isnull'], + "pu_order": ["exact", 'isnull'], "pu_order__state": ["exact"] } \ No newline at end of file diff --git a/apps/sam/__init__.py b/apps/sam/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/sam/admin.py b/apps/sam/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/apps/sam/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/sam/apps.py b/apps/sam/apps.py new file mode 100644 index 00000000..5eb8b889 --- /dev/null +++ b/apps/sam/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SamConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.sam' diff --git a/apps/sam/migrations/0001_initial.py b/apps/sam/migrations/0001_initial.py new file mode 100644 index 00000000..73bc9401 --- /dev/null +++ b/apps/sam/migrations/0001_initial.py @@ -0,0 +1,117 @@ +# Generated by Django 3.2.12 on 2023-09-13 06:04 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('system', '0002_myschedule'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mtm', '0009_process'), + ] + + operations = [ + migrations.CreateModel( + name='Contract', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=100, verbose_name='合同名称')), + ('number', models.CharField(max_length=100, unique=True, verbose_name='合同编号')), + ('amount', models.IntegerField(default=0, verbose_name='合同金额')), + ('sign_date', models.DateField(verbose_name='签订日期')), + ('description', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')), + ('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contract_belong_dept', to='system.dept', verbose_name='所属部门')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contract_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'verbose_name': '合同信息', + 'verbose_name_plural': '合同信息', + }, + ), + migrations.CreateModel( + name='Customer', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('name', models.CharField(max_length=50, unique=True, verbose_name='客户名称')), + ('address', models.CharField(blank=True, max_length=20, null=True, verbose_name='详细地址')), + ('contact', models.CharField(max_length=20, verbose_name='联系人')), + ('contact_phone', models.CharField(max_length=11, null=True, unique=True, verbose_name='联系电话')), + ('description', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')), + ('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_belong_dept', to='system.dept', verbose_name='所属部门')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'verbose_name': '客户信息', + 'verbose_name_plural': '客户信息', + }, + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('state', models.PositiveSmallIntegerField(choices=[(10, '创建中'), (20, '已排产'), (30, '已交付')], default=10, help_text="((10, '创建中'), (20, '已排产'), (30, '已交付'))", verbose_name='订单状态')), + ('number', models.CharField(max_length=100, unique=True, verbose_name='订单编号')), + ('delivery_date', models.DateField(verbose_name='截止交货日期')), + ('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_belong_dept', to='system.dept', verbose_name='所属部门')), + ('contract', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='sam.contract', verbose_name='所属合同')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sam.customer', verbose_name='客户')), + ], + options={ + 'verbose_name': '订单信息', + 'verbose_name_plural': '订单信息', + }, + ), + migrations.CreateModel( + name='OrderItem', + fields=[ + ('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('count', models.PositiveIntegerField(default=1, verbose_name='所需数量')), + ('count_deliverd', models.PositiveIntegerField(default=0, verbose_name='已交货数量')), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sam.order', verbose_name='关联订单')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='所需产品')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='order', + name='items', + field=models.ManyToManyField(blank=True, through='sam.OrderItem', to='mtm.Material', verbose_name='订单明细'), + ), + migrations.AddField( + model_name='order', + name='update_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'), + ), + migrations.AddField( + model_name='contract', + name='customer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contract_customer', to='sam.customer', verbose_name='关联客户'), + ), + migrations.AddField( + model_name='contract', + name='update_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='contract_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'), + ), + ] diff --git a/apps/sam/migrations/__init__.py b/apps/sam/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/sam/models.py b/apps/sam/models.py new file mode 100644 index 00000000..9aeda384 --- /dev/null +++ b/apps/sam/models.py @@ -0,0 +1,78 @@ +from django.db import models +from apps.utils.models import CommonBModel, BaseModel, CommonBDModel +from apps.mtm.models import Material + +# Create your models here. +class Customer(CommonBModel): + """ + 客户信息 + """ + name = models.CharField('客户名称', max_length=50, unique=True) + address = models.CharField('详细地址', max_length=20, blank=True, null=True) + contact = models.CharField('联系人', max_length=20) + contact_phone = models.CharField('联系电话', max_length=11, unique=True,null=True) + description = models.CharField('描述', max_length=200, blank=True, null=True) + + class Meta: + verbose_name = '客户信息' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class Contract(CommonBModel): + """ + 合同信息 + """ + name = models.CharField('合同名称', max_length=100) + number = models.CharField('合同编号', max_length=100, unique=True) + amount = models.IntegerField('合同金额', default=0) + customer = models.ForeignKey(Customer, verbose_name='关联客户', on_delete=models.CASCADE, related_name='contract_customer') + sign_date = models.DateField('签订日期') + description = models.CharField('描述', max_length=200, blank=True, null=True) + class Meta: + verbose_name = '合同信息' + verbose_name_plural = verbose_name + + def __str__(self): + return self.name + + +class Order(CommonBModel): + """ + 订单信息 + """ + ORDER_CREATE = 10 + ORDER_SUBMITED = 20 + ORDER_PLANED = 30 + ORDER_DELIVERED = 40 + ORDER_STATES = ( + (10, '创建中'), + (20, '已提交') + (30, '已排产'), + (40, '已交付') + ) + state = models.PositiveSmallIntegerField('订单状态', default=ORDER_CREATE, choices=ORDER_STATES, help_text=str(ORDER_STATES)) + number = models.CharField('订单编号', max_length=100, unique=True) + customer = models.ForeignKey(Customer, verbose_name='客户', on_delete=models.CASCADE) + contract = models.ForeignKey(Contract, verbose_name='所属合同', null=True, blank=True, on_delete=models.SET_NULL) + delivery_date = models.DateField('截止交货日期') + items = models.ManyToManyField(Material, verbose_name='订单明细', through='sam.orderitem', blank=True) + submit_time = models.DateTimeField('提交时间', null=True, blank=True) + + class Meta: + verbose_name = '订单信息' + verbose_name_plural = verbose_name + + +class OrderItem(BaseModel): + """ + 订单明细 + """ + order = models.ForeignKey(Order, verbose_name='关联订单', on_delete=models.CASCADE) + product = models.ForeignKey(Material, verbose_name='所需产品', on_delete=models.CASCADE) + count = models.PositiveIntegerField('所需数量', default=1) + count_deliverd = models.PositiveIntegerField('已交货数量', default=0) + + \ No newline at end of file diff --git a/apps/sam/serializers.py b/apps/sam/serializers.py new file mode 100644 index 00000000..30953f51 --- /dev/null +++ b/apps/sam/serializers.py @@ -0,0 +1,65 @@ +from rest_framework import serializers +from apps.utils.serializers import CustomModelSerializer +from apps.sam.models import Customer, Contract, Order, OrderItem +from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE +from rest_framework.exceptions import ValidationError +from apps.mtm.serializers import MaterialSerializer + +class CustomerSerializer(CustomModelSerializer): + class Meta: + model = Customer + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS + ['belong_dept'] + + +class ContractSerializer(CustomModelSerializer): + class Meta: + model = Contract + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS + ['belong_dept', 'invoice'] + + +class OrderSerializer(CustomModelSerializer): + class Meta: + model = Order + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS + ['belong_dept', 'state'] + + def validate(self, attrs): + contract = attrs.get('contract', None) + if contract: + attrs['customer'] = contract.customer + if attrs.get('customer', None) is None: + raise ValidationError('未选择客户') + return attrs + + def update(self, instance, validated_data): + if instance.state != Order.ORDER_CREATE: + raise ValidationError('订单信息不可编辑') + return super().update(instance, validated_data) + +class OrderItemSerializer(CustomModelSerializer): + product_ = MaterialSerializer(source='product', read_only=True) + order_ = OrderSerializer(source='order', read_only=True) + class Meta: + model = OrderItem + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS_BASE + ['count_deliverd'] + + def validate(self, attrs): + order = attrs['order'] + if order.state != Order.ORDER_CREATE: + raise ValidationError('该订单状态下不可创建') + return attrs + + def create(self, validated_data): + order = validated_data['order'] + product = validated_data['product'] + if OrderItem.objects.filter(order=order, product=product).exists(): + raise ValidationError('该产品已选择!') + return super().create(validated_data) + + def update(self, instance, validated_data): + validated_data.pop('product', None) + validated_data.pop('order', None) + return super().update(instance, validated_data) \ No newline at end of file diff --git a/apps/sam/tests.py b/apps/sam/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/sam/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/sam/urls.py b/apps/sam/urls.py new file mode 100644 index 00000000..0ea17b4e --- /dev/null +++ b/apps/sam/urls.py @@ -0,0 +1,15 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from apps.sam.views import (CustomerViewSet, ContractViewSet, OrderViewSet, OrderItemViewSet) + +API_BASE_URL = 'api/sam/' +HTML_BASE_URL = 'sam/' + +router = DefaultRouter() +router.register('customer', CustomerViewSet, basename='customer') +router.register('contract', ContractViewSet, basename='contract') +router.register('order', OrderViewSet, basename='order') +router.register('orderitem', OrderItemViewSet, basename='orderitem') +urlpatterns = [ + path(API_BASE_URL, include(router.urls)), +] \ No newline at end of file diff --git a/apps/sam/views.py b/apps/sam/views.py new file mode 100644 index 00000000..bbfb8090 --- /dev/null +++ b/apps/sam/views.py @@ -0,0 +1,92 @@ +from django.shortcuts import render +from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet +from apps.sam.models import Customer, Contract, Order, OrderItem +from apps.sam.serializers import CustomerSerializer, ContractSerializer, OrderSerializer, OrderItemSerializer +from rest_framework.exceptions import ParseError +from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin +from apps.utils.mixins import BulkCreateModelMixin +from rest_framework.decorators import action +from django.db import transaction +from rest_framework import serializers +from rest_framework.exceptions import PermissionDenied +from django.utils import timezone +from rest_framework.response import Response + + +# Create your views here. +class CustomerViewSet(CustomModelViewSet): + """ + list: 客户信息 + + 客户信息 + """ + queryset = Customer.objects.all() + serializer_class = CustomerSerializer + search_fields = ['name', 'contact'] + + def perform_destroy(self, instance): + if Contract.objects.filter(customer=instance).exists(): + raise ParseError('该客户存在合同不可删除') + instance.delete() + + +class ContractViewSet(CustomModelViewSet): + """ + list: 合同信息 + + 合同信息 + """ + querset = Contract.objects.all() + serializer_class = ContractSerializer + select_related_fields = ['customer'] + search_fields = ['name', 'number'] + filterset_fields = ['customer'] + + def perform_destroy(self, instance): + if Order.objects.filter(contract=instance).exists(): + raise ParseError('该合同存在订单不可删除') + + +class OrderViewSet(CustomModelViewSet): + """ + list: 订单信息 + + 订单信息 + """ + queryset = Order.objects.all() + serializer_class = OrderSerializer + select_related_fields = ['contract', 'customer'] + search_fields = ['number'] + filter_fields = ['contract', 'customer'] + + @action(methods=['post'], detail=True, perms_map={'post': 'order.update'}, serializer_class=serializers.Serializer) + @transaction.atomic + def submit(self, request, *args, **kwargs): + """提交订单 + + 提交订单 + """ + order = self.get_object() + user = request.user + if order.create_by != user: + raise PermissionDenied('非创建人不可提交') + if order.state != Order.ORDER_CREATE: + raise ParseError('订单非创建中') + order.submit_time = timezone.now() + order.state = Order.ORDER_SUBMITED + order.save() + return Response() + +class OrderItemViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, CustomGenericViewSet): + """ + list: 订单明细 + + 订单明细 + """ + queryset = OrderItem.objects.all() + serializer_class = OrderItemSerializer + select_related_fields = ['order', 'product'] + filterset_fields = ['order', 'product'] + ordering = ['create_time'] + +