diff --git a/apps/mtm/migrations/0009_process.py b/apps/mtm/migrations/0009_process.py new file mode 100644 index 00000000..8c77d544 --- /dev/null +++ b/apps/mtm/migrations/0009_process.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.12 on 2023-09-13 06:03 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mtm', '0008_mgroup_is_runing'), + ] + + operations = [ + migrations.CreateModel( + name='Process', + 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='工序名称')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='process_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='process_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/apps/mtm/models.py b/apps/mtm/models.py index 30db4497..339b76a2 100644 --- a/apps/mtm/models.py +++ b/apps/mtm/models.py @@ -87,4 +87,13 @@ class Goal(CommonADModel): goal_val_12 = models.FloatField('12月份目标值') class Meta: - unique_together = ("mgroup", "year", "goal_cate") \ No newline at end of file + unique_together = ("mgroup", "year", "goal_cate") + + + +class Process(CommonAModel): + """ + 工序 + """ + name = models.CharField('工序名称', max_length=100) + diff --git a/apps/pum/__init__.py b/apps/pum/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/pum/admin.py b/apps/pum/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/apps/pum/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/pum/apps.py b/apps/pum/apps.py new file mode 100644 index 00000000..f070232b --- /dev/null +++ b/apps/pum/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PumConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'apps.pum' diff --git a/apps/pum/filters.py b/apps/pum/filters.py new file mode 100644 index 00000000..8f1c346e --- /dev/null +++ b/apps/pum/filters.py @@ -0,0 +1,19 @@ +from django_filters import rest_framework as filters +from apps.pum.models import PuPlanItem, PuPlan + +class PuPlanFilter(filters.FilterSet): + class Meta: + model = PuPlan + fields = { + "state": ["exact", "__in"] + } + +class PuPlanItemFilter(filters.FilterSet): + class Meta: + model = PuPlanItem + fields = { + "material": ["exact"], + "pu_plan": ["exact"], + "pu_order": ["exact", '__isnull'], + "pu_order__state": ["exact"] + } \ No newline at end of file diff --git a/apps/pum/migrations/0001_initial.py b/apps/pum/migrations/0001_initial.py new file mode 100644 index 00000000..076b2a61 --- /dev/null +++ b/apps/pum/migrations/0001_initial.py @@ -0,0 +1,123 @@ +# Generated by Django 3.2.12 on 2023-09-18 05:42 + +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='PuOrder', + 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, '到货中'), (40, '已完成')], default=10, help_text="((10, '创建中'), (20, '已提交'), (30, '到货中'), (40, '已完成'))", verbose_name='状态')), + ('number', models.CharField(blank=True, max_length=20, null=True, verbose_name='订单编号')), + ('delivery_date', models.DateField(blank=True, null=True, verbose_name='截止到货日期')), + ('submit_time', models.DateTimeField(blank=True, null=True, verbose_name='提交时间')), + ('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='puorder_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='puorder_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='PuPlan', + 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, '下单中'), (40, '下单完成'), (50, '已完成')], default=10, help_text="((10, '创建中'), (20, '已提交'), (30, '下单中'), (40, '下单完成'), (50, '已完成'))", verbose_name='状态')), + ('number', models.CharField(max_length=20, verbose_name='编号')), + ('name', models.CharField(blank=True, max_length=50, null=True, verbose_name='名称')), + ('submit_time', models.DateTimeField(blank=True, null=True, verbose_name='提交时间')), + ('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='puplan_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='puplan_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='puplan_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Supplier', + 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, verbose_name='供应商名称')), + ('contact', models.CharField(max_length=20, verbose_name='联系人')), + ('contact_phone', models.CharField(max_length=11, verbose_name='联系电话')), + ('address', models.CharField(default='', max_length=200, verbose_name='地址')), + ('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_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='supplier_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='supplier_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='PuPlanItem', + 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='删除标记')), + ('need_count', models.PositiveIntegerField(verbose_name='所属数量')), + ('need_date', models.DateField(verbose_name='需求日期')), + ('note', models.TextField(default='', verbose_name='备注')), + ('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='puplanitem_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='puplanitem_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='所需物料')), + ('pu_order', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='puplan_item_puorder', to='pum.puorder', verbose_name='关联采购订单')), + ('pu_plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='item_puplan', to='pum.puplan', verbose_name='采购计划')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='puplanitem_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='PuOrderItem', + 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=0, verbose_name='所需数量')), + ('delivered_count', models.PositiveIntegerField(default=0, verbose_name='已到货数量')), + ('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='物料')), + ('pu_order', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='item_puorder', to='pum.puorder', verbose_name='关联采购订单')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='puorder', + name='supplier', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pum.supplier', verbose_name='供应商'), + ), + migrations.AddField( + model_name='puorder', + name='update_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='puorder_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'), + ), + ] diff --git a/apps/pum/migrations/__init__.py b/apps/pum/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/pum/models.py b/apps/pum/models.py new file mode 100644 index 00000000..618816d0 --- /dev/null +++ b/apps/pum/models.py @@ -0,0 +1,81 @@ +from django.db import models +from apps.utils.models import CommonBModel, BaseModel, CommonBDModel +from apps.mtm.models import Material + + +# Create your models here. +class Supplier(CommonBModel): + """ + 供应商 + """ + name = models.CharField('供应商名称', max_length=50) + contact = models.CharField('联系人', max_length=20) + contact_phone = models.CharField('联系电话', max_length=11) + address = models.CharField('地址', max_length=200, default='') + + +class PuPlan(CommonBModel): + """ + 采购计划 + """ + PUPLAN_CREATE = 10 + PUPLAN_SUBMITED = 20 + PUPLAN_ORDERING = 30 + PUPLAN_ORDERED = 40 + PUPLAN_DONE = 50 + PUPLAN_STATES = ( + (PUPLAN_CREATE, '创建中'), + (PUPLAN_SUBMITED, '已提交'), + (PUPLAN_ORDERING, '下单中'), + (PUPLAN_ORDERED, '下单完成'), + (PUPLAN_DONE, '已完成'), + ) + state = models.PositiveSmallIntegerField('状态', choices=PUPLAN_STATES, default=10, help_text=str(PUPLAN_STATES)) + number = models.CharField('编号', max_length=20) + name = models.CharField('名称', max_length=50, null=True, blank=True) + submit_time = models.DateTimeField('提交时间', null=True, blank=True) + + + +class PuOrder(CommonBModel): + """ + 采购订单 + """ + PUORDER_CREATE = 10 + PUORDER_SUBMITED = 20 + PUORDER_SHIP = 30 + PUORDER_DONE = 40 + PUORDER_STATES = ( + (PUORDER_CREATE, '创建中'), + (PUORDER_SUBMITED, '已提交'), + (PUORDER_SHIP, '到货中'), + (PUORDER_DONE, '已完成'), + ) + state = models.PositiveSmallIntegerField('状态', choices=PUORDER_STATES, default=10, help_text=str(PUORDER_STATES)) + number = models.CharField('订单编号', max_length=20, null=True, blank=True) + supplier = models.ForeignKey(Supplier, verbose_name='供应商', on_delete=models.CASCADE) + delivery_date = models.DateField('截止到货日期', null=True, blank=True) + submit_time = models.DateTimeField('提交时间', null=True, blank=True) + + +class PuOrderItem(BaseModel): + material = models.ForeignKey(Material, verbose_name='物料', on_delete=models.CASCADE) + count = models.PositiveIntegerField('所需数量', default=0) + delivered_count = models.PositiveIntegerField('已到货数量', default=0) + pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单', + on_delete=models.CASCADE, null=True, blank=True, related_name='item_puorder') + + + +class PuPlanItem(CommonBDModel): + """ + 采购计划明细(因为各部门填写所以需要belong_dept字段) + """ + material = models.ForeignKey(Material, verbose_name='所需物料', on_delete=models.CASCADE) + need_count = models.PositiveIntegerField('所属数量') + need_date = models.DateField('需求日期') + note = models.TextField('备注', default='') + pu_plan = models.ForeignKey(PuPlan, verbose_name='采购计划', on_delete=models.CASCADE, + null=True, blank=True, related_name='item_puplan') + pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单', + on_delete=models.SET_NULL, null=True, blank=True, related_name='puplan_item_puorder') \ No newline at end of file diff --git a/apps/pum/serializers.py b/apps/pum/serializers.py new file mode 100644 index 00000000..b8d8a4cd --- /dev/null +++ b/apps/pum/serializers.py @@ -0,0 +1,100 @@ +from rest_framework import serializers +from apps.utils.serializers import CustomModelSerializer +from apps.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS_BASE +from rest_framework.exceptions import ValidationError + +from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem +from apps.mtm.serializers import MaterialSerializer + +class SupplierSerializer(CustomModelSerializer): + class Meta: + model = Supplier + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS_DEPT + + +class PuPlanSerializer(CustomModelSerializer): + class Meta: + model = PuPlan + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS_DEPT + ['state'] + + def update(self, instance, validated_data): + if instance.state != PuPlan.PUPLAN_CREATE: + raise ValidationError('该状态下不可编辑') + return super().update(instance, validated_data) + + +class PuPlanItemSerializer(CustomModelSerializer): + material_ = MaterialSerializer(source='material', read_only=True) + belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True) + class Meata: + model = PuPlanItem + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS_DEPT + ['pu_order'] + + def create(self, validated_data): + pu_plan = validated_data['pu_plan'] + if pu_plan.state != PuPlan.PUPLAN_CREATE: + raise ValidationError('该状态下不可添加需求') + material = validated_data['material'] + user = self.context['request'].user + if PuPlanItem.objects.filter(material=material, belong_dept=user.belong_dept).exists(): + raise ValidationError('同部门已提交该物料需求,请确认!') + return super().create(validated_data) + + def update(self, instance, validated_data): + validated_data.pop('pu_plan') + pu_plan = instance.pu_plan + if pu_plan.state != PuPlan.PUPLAN_CREATE: + raise ValidationError('该状态下不可编辑需求') + material = validated_data['material'] + user = self.context['request'].user + if instance.create_by != user: + raise ValidationError('非创建人不可编辑') + if PuPlanItem.objects.exclude(id=instance.id).filter(material=material, belong_dept=user.belong_dept).exists(): + raise ValidationError('同部门已提交该物料需求,请确认!') + return super().update(instance, validated_data) + + +class PuOrderSerializer(CustomModelSerializer): + class Meta: + model = PuOrder + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS_DEPT + ['state'] + + def update(self, instance, validated_data): + validated_data.pop('supplier') + if instance.steate != PuOrder.PUORDER_CREATE: + raise ValidationError('该状态下不可编辑') + return super().update(instance, validated_data) + + +class PuOrderItemSerializer(CustomModelSerializer): + material_ = MaterialSerializer(source='material', read_only=True) + class Meta: + model = PuOrderItem + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS_BASE + ['delivered_count'] + + def create(self, validated_data): + pu_order = validated_data['pu_order'] + material = validated_data['material'] + if pu_order.state != PuOrder.PUORDER_CREATE: + raise ValidationError('采购订单该状态下不可添加明细') + if PuOrder.objects.filter(pu_order=pu_order, material=material).exists(): + raise ValidationError('该物料已添加') + return super().create(validated_data) + + def update(self, instance, validated_data): + validated_data.pop('material') + validated_data.pop('pu_order') + pu_order = instance.pu_order + if pu_order.state != PuOrder.PUORDER_CREATE: + raise ValidationError('采购订单该状态下不可编辑') + return super().update(instance, validated_data) + + +class AddSerializer(serializers.Serializer): + pu_order = serializers.PrimaryKeyRelatedField(label='采购订单ID', queryset=PuOrder.objects.all()) + pu_planitems = serializers.PrimaryKeyRelatedField(label='计划明细ID', queryset=PuPlanItem.objects.all(), many=True) diff --git a/apps/pum/services.py b/apps/pum/services.py new file mode 100644 index 00000000..7e402429 --- /dev/null +++ b/apps/pum/services.py @@ -0,0 +1,19 @@ +from apps.pum.models import PuOrderItem, PuPlan, PuPlanItem, PuOrder + +class PumService: + + def change_puplan_state_when_puorder_sumbit(puorder: PuOrder): + puplanIds = PuPlanItem.objects.filter(pu_order=puorder).values_list('pu_plan', flat=True) + for id in puplanIds: + puplan = PuPlan.objects.get(id=id) + state = puplan.state + if PuPlanItem.objects.filter(pu_plan=puplan).count() == PuPlan.objects.filter(pu_plan=puplan, pu_order__state__gte=PuOrder.PUORDER_SUBMITED).count(): + state = PuPlan.PUPLAN_ORDERED + else: + state = PuPlan.PUPLAN_ORDERING + puplan.state = state + puplan.save() + + + + diff --git a/apps/pum/tests.py b/apps/pum/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/pum/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/pum/urls.py b/apps/pum/urls.py new file mode 100644 index 00000000..80f613aa --- /dev/null +++ b/apps/pum/urls.py @@ -0,0 +1,16 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet) + +API_BASE_URL = 'api/pum/' +HTML_BASE_URL = 'pum/' + +router = DefaultRouter() +router.register('supplier', SupplierViewSet, basename='supplier') +router.register('pu_plan', PuPlanViewSet, basename='pu_plan') +router.register('pu_planitem', PuPlanItemViewSet, basename='pu_planitem') +router.register('pu_order', PuOrderViewSet, basename='pu_order') +router.register('pu_orderitem', PuOrderItemViewSet, basename='pu_orderitem') +urlpatterns = [ + path(API_BASE_URL, include(router.urls)), +] \ No newline at end of file diff --git a/apps/pum/views.py b/apps/pum/views.py new file mode 100644 index 00000000..09a4720a --- /dev/null +++ b/apps/pum/views.py @@ -0,0 +1,155 @@ +from django.shortcuts import render +from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem +from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet +from apps.pum.serializers import (SupplierSerializer, PuPlanSerializer, PuPlanItemSerializer, + PuOrderSerializer, PuOrderItemSerializer, AddSerializer) +from rest_framework.exceptions import ParseError, PermissionDenied +from rest_framework.decorators import action +from rest_framework import serializers +from apps.pum.filters import PuPlanItemFilter +from django.db import transaction +from rest_framework.response import Response +from django.utils import timezone +from apps.pum.services import PumService +# Create your views here. + + +class SupplierViewSet(CustomModelViewSet): + queryset = Supplier.objects.all() + serializer_class = SupplierSerializer + search_fields = ['name', 'contact'] + + def perform_destroy(self, instance): + if PuOrder.objects.filter(supplier=instance).exists(): + raise ParseError('该供应商存在采购订单不可删除') + instance.delete() + + +class PuPlanViewSet(CustomModelViewSet): + queryset = PuPlan.objects.all() + serializer_class = PuPlanSerializer + search_fields = ['name', 'number'] + filterset_fields = ['state'] + + def perform_destroy(self, instance): + if PuPlan.objects.filter(pu_plan=instance).exists(): + raise ParseError('该计划存在明细不可删除') + return super().perform_destroy(instance) + + @action(methods=['post'], detail=True, perms_map={'post': 'pu_plan.update'}, serializer_class=serializers.Serializer) + def submit(self, request, *args, **kwargs): + """提交采购计划 + + 提交采购计划 + """ + puplan = self.get_object() + user = request.user + if puplan.create_by != user: + raise PermissionDenied('非创建人不可提交') + if puplan.state != PuPlan.PUPLAN_CREATE: + raise ParseError('采购计划状态异常') + puplan.submit_time = timezone.now() + puplan.state = PuPlan.PUPLAN_SUBMITED + puplan.save() + return Response() + +class PuPlanItemViewSet(CustomModelViewSet): + queryset = PuPlanItem.objects.all() + serializer_class = PuPlanItemSerializer + filterset_class = PuPlanItemFilter + ordering_fields = ['create_time', 'material', 'need_date', 'need_count'] + ordering = ['create_time'] + + def perform_destroy(self, instance): + user = self.request.user + pu_plan = instance.pu_plan + if pu_plan.state == PuPlan.PUPLAN_CREATE and user != instance.create_by: + raise ParseError('非创建人不可删除') + elif instance.pu_order is not None: + raise ParseError('存在采购订单不可删除') + return super().perform_destroy(instance) + + +class PuOrderViewSet(CustomModelViewSet): + queryset = PuOrder.objects.all() + serializer_class = PuOrderSerializer + filterset_fields = ['state', 'supplier'] + search_fields = ['number', 'supplier__name'] + + @transaction.atomic + def perform_destroy(self, instance): + if instance.state != PuOrder.PUORDER_CREATE: + raise ParseError('采购订单非创建中不可删除') + instance.delete(soft=False) + + @action(methods=['post'], detail=True, perms_map={'post': 'pu_order.update'}, serializer_class=serializers.Serializer) + @transaction.atomic + def submit(self, request, *args, **kwargs): + """提交采购订单 + + 提交采购订单 + """ + puorder = self.get_object() + user = request.user + if puorder.delivery_date is None or puorder.number is None: + raise ParseError('订单信息不全') + if puorder.create_by != user: + raise PermissionDenied('非创建人不可提交') + if puorder.state != PuOrder.PUPLAN_CREATE: + raise ParseError('采购计划状态异常') + puorder.submit_time = timezone.now() + puorder.state = PuPlan.PUPLAN_SUBMITED + puorder.save() + PumService.change_puplan_state_when_puorder_sumbit(puorder) + return Response() + + +class PuOrderItemViewSet(CustomModelViewSet): + queryset = PuOrderItem.objects.all() + serializer_class = PuOrderItemSerializer + filterset_fields = ['material', 'pu_order'] + ordering = ['create_time'] + + @transaction.atomic + def perform_destroy(self, instance): + pu_order = instance.pu_order + if pu_order.state != PuOrder.PUORDER_CREATE: + raise ParseError('采购订单非创建中不可删除') + instance.delete() + + @action(methods=['post'], detail=False, perms_map={'post': 'pu_orderitem.create'}, serializer_class=AddSerializer) + @transaction.atomic + def add_from_planitems(self, request, *args, **kwargs): + """从计划明细创建/变更采购订单明细 + + 从计划明细创建/变更采购订单明细 + """ + data = request.data + sr = AddSerializer(data=data) + sr.is_valid(raise_exception=True) + vdata = sr.validated_data + puorder = vdata['pu_order'] + if puorder.state != PuOrder.PUORDER_CREATE: + raise ParseError('该采购订单不可用') + puplanitems = PuPlanItem.objects.filter(id__in=vdata['pu_planitems']) + for item in puplanitems: + if item.pu_plan.state == PuPlan.PUPLAN_CREATE: + raise ParseError('存在不可选择的计划明细') + if item.pu_order and item.pu_order.state != PuOrder.PUORDER_CREATE: + raise ParseError('存在计划明细已指定进行中的采购订单') + puorderitem, is_created = PuOrderItem.objects.get_or_create( + pu_order = puorder, material=item.material, + defaults={'pu_order': puorder, 'material': item.material, 'count': item.need_count} + ) + if not is_created: + puorderitem.count = puorderitem.count + item.need_count + puorderitem.save() + if puorder.delivery_date is None: + puorder.delivery_date = item.need_date + elif item.need_date < puorder.delivery_data: + puorder.delivery_date = item.need_date + puorder.save() + item.pu_order = puorder + item.save() + return Response() + diff --git a/server/settings.py b/server/settings.py index bc2aa858..bd89dbeb 100755 --- a/server/settings.py +++ b/server/settings.py @@ -105,7 +105,8 @@ INSTALLED_APPS = [ 'apps.qm', 'apps.enm', 'apps.fim', - 'apps.inm' + 'apps.inm', + 'apps.pum' ] MIDDLEWARE = [ diff --git a/server/urls.py b/server/urls.py index b283d558..63611b25 100755 --- a/server/urls.py +++ b/server/urls.py @@ -62,6 +62,7 @@ urlpatterns = [ path('', include('apps.enm.urls')), path('', include('apps.fim.urls')), path('', include('apps.inm.urls')), + path('', include('apps.pum.urls')),