From 095eaabbb9d8442b8724ec572bc8201610566f8a Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 21 Sep 2023 16:26:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0process=E5=92=8Croute?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/mtm/filters.py | 17 +++- .../mtm/migrations/0010_auto_20230921_1626.py | 92 +++++++++++++++++++ apps/mtm/models.py | 29 +++++- apps/mtm/serializers.py | 27 +++++- apps/mtm/urls.py | 5 +- apps/mtm/views.py | 48 +++++++--- 6 files changed, 195 insertions(+), 23 deletions(-) create mode 100644 apps/mtm/migrations/0010_auto_20230921_1626.py diff --git a/apps/mtm/filters.py b/apps/mtm/filters.py index eadc80c5..7b39092a 100644 --- a/apps/mtm/filters.py +++ b/apps/mtm/filters.py @@ -1,5 +1,20 @@ from django_filters import rest_framework as filters -from apps.mtm.models import Goal +from apps.mtm.models import Goal, Material +from django.db.models.expressions import F + +class MaterialFilter(filters.FilterSet): + tag = filters.CharFilter(method='filter_tag') + class Meta: + model = Material + fields = { + "type": ["exact", "in"], + } + + def filter_tag(self, queryset, name, value): + if value == 'low_inm': + queryset = queryset.exclude(count_safe=None).filter(count__lte = F('count_safe')) + return queryset + class GoalFilter(filters.FilterSet): class Meta: diff --git a/apps/mtm/migrations/0010_auto_20230921_1626.py b/apps/mtm/migrations/0010_auto_20230921_1626.py new file mode 100644 index 00000000..f2a73127 --- /dev/null +++ b/apps/mtm/migrations/0010_auto_20230921_1626.py @@ -0,0 +1,92 @@ +# Generated by Django 3.2.12 on 2023-09-21 08:26 + +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 = [ + ('system', '0002_myschedule'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mtm', '0009_process'), + ] + + operations = [ + migrations.AddField( + model_name='material', + name='count', + field=models.PositiveIntegerField(default=0, verbose_name='物料库存总数'), + ), + migrations.AddField( + model_name='material', + name='count_safe', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='安全库存总数'), + ), + migrations.AddField( + model_name='material', + name='number', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='编号'), + ), + migrations.AddField( + model_name='material', + name='specification', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='型号'), + ), + migrations.AddField( + model_name='material', + name='unit', + field=models.CharField(default='个', max_length=10, verbose_name='基准计量单位'), + ), + migrations.AddField( + model_name='process', + name='belong_dept', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='process_belong_dept', to='system.dept', verbose_name='所属部门'), + ), + migrations.AddField( + model_name='process', + name='cate', + field=models.CharField(default='', max_length=10, verbose_name='大类'), + ), + migrations.AddField( + model_name='process', + name='instruction', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.file', verbose_name='指导书'), + ), + migrations.AddField( + model_name='process', + name='instruction_content', + field=models.TextField(blank=True, null=True, verbose_name='指导书内容'), + ), + migrations.AddField( + model_name='process', + name='sort', + field=models.PositiveSmallIntegerField(default=1, verbose_name='排序'), + ), + migrations.AlterField( + model_name='material', + name='type', + field=models.PositiveSmallIntegerField(choices=[(0, '电/水/气'), (10, '成品'), (20, '半成品'), (30, '主要原料'), (40, '辅助材料'), (50, '加工工具'), (60, '辅助工装'), (70, '办公用品')], default=1, help_text="((0, '电/水/气'), (10, '成品'), (20, '半成品'), (30, '主要原料'), (40, '辅助材料'), (50, '加工工具'), (60, '辅助工装'), (70, '办公用品'))", verbose_name='物料类型'), + ), + migrations.CreateModel( + name='Route', + 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='删除标记')), + ('sort', models.PositiveSmallIntegerField(default=1, verbose_name='顺序')), + ('is_autotask', models.BooleanField(default=False, verbose_name='是否自动排产')), + ('out_rate', models.FloatField(default=100, verbose_name='出材率')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='route_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('material', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='关联产品')), + ('process', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.process', verbose_name='工序')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='route_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 339b76a2..4df90019 100644 --- a/apps/mtm/models.py +++ b/apps/mtm/models.py @@ -1,5 +1,5 @@ from django.db import models -from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel +from apps.system.models import CommonAModel, Dictionary, CommonBModel, CommonADModel, File, BaseModel # Create your models here. class Material(CommonAModel): @@ -10,6 +10,7 @@ class Material(CommonAModel): MA_TYPE_HELPSO = 40 MA_TYPE_TOOL = 50 MA_TYPE_HELPTOOL = 60 + MA_TYPE_OFFICE = 70 type_choices=( (MA_TYPE_BASE, '电/水/气'), @@ -18,13 +19,19 @@ class Material(CommonAModel): (MA_TYPE_MAINSO, '主要原料'), (MA_TYPE_HELPSO, '辅助材料'), (MA_TYPE_TOOL, '加工工具'), - (MA_TYPE_HELPTOOL, '辅助工装') + (MA_TYPE_HELPTOOL, '辅助工装'), + (MA_TYPE_OFFICE, '办公用品') ) name = models.CharField('名称', max_length=50) + number = models.CharField('编号', max_length=100, null=True, blank=True) + specification = models.CharField('型号', max_length=100, null=True, blank=True) code = models.CharField('标识', max_length=50, null=True, blank=True) type = models.PositiveSmallIntegerField('物料类型', choices= type_choices, default=1, help_text=str(type_choices)) testitems = models.JSONField('检测项目', default=list, blank=True) sort = models.PositiveSmallIntegerField('排序', default=1) + unit = models.CharField('基准计量单位', default='个', max_length=10) + count = models.PositiveIntegerField('物料库存总数', default=0) + count_safe = models.PositiveIntegerField('安全库存总数', null=True, blank=True) class Meta: verbose_name = '物料表' @@ -91,9 +98,23 @@ class Goal(CommonADModel): -class Process(CommonAModel): +class Process(CommonBModel): """ - 工序 + 工序 belong_dept为所在车间 """ name = models.CharField('工序名称', max_length=100) + cate = models.CharField('大类', max_length=10, default='') + sort = models.PositiveSmallIntegerField('排序', default=1) + instruction = models.ForeignKey(File, verbose_name='指导书', on_delete=models.SET_NULL, null=True, blank=True) + instruction_content = models.TextField('指导书内容', null=True, blank=True) + +class Route(CommonAModel): + """ + 工艺路线 + """ + material = models.ForeignKey(Material, verbose_name='关联产品', on_delete=models.CASCADE, null=True, blank=True) + process = models.ForeignKey(Process, verbose_name='工序', on_delete=models.CASCADE, null=True, blank=True) + sort = models.PositiveSmallIntegerField('顺序', default=1) + is_autotask = models.BooleanField('是否自动排产', default=False) + out_rate = models.FloatField('出材率', default=100) \ No newline at end of file diff --git a/apps/mtm/serializers.py b/apps/mtm/serializers.py index 09d1e189..b170c24d 100644 --- a/apps/mtm/serializers.py +++ b/apps/mtm/serializers.py @@ -1,5 +1,5 @@ from apps.utils.serializers import CustomModelSerializer -from apps.mtm.models import Shift, Material, Mgroup, Team, Goal +from apps.mtm.models import Shift, Material, Mgroup, Team, Goal, Process, Route from apps.utils.constants import EXCLUDE_FIELDS from rest_framework import serializers from apps.system.models import Dept @@ -44,4 +44,27 @@ class GoalSerializer(CustomModelSerializer): class MgroupGoalYearSerializer(serializers.Serializer): mgroup = serializers.CharField(label='ID或名称') - year = serializers.IntegerField(label='年') \ No newline at end of file + year = serializers.IntegerField(label='年') + + +class ProcessSerializer(CustomModelSerializer): + belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True) + class Meta: + model = Process + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS + +class RouteSerializer(CustomModelSerializer): + material_ = MaterialSerializer(source='material', read_only=True) + process_name = serializers.CharField(source='process.name', read_only=True) + process_cate = serializers.CharField(source='process.cate', read_only=True) + class Meta: + model = Route + fields = '__all__' + read_only_fields = EXCLUDE_FIELDS + + def update(self, instance, validated_data): + validated_data.pop('material', None) + validated_data.pop('process', None) + return super().update(instance, validated_data) + diff --git a/apps/mtm/urls.py b/apps/mtm/urls.py index 32ef07ec..e6e2402c 100644 --- a/apps/mtm/urls.py +++ b/apps/mtm/urls.py @@ -1,7 +1,7 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.mtm.views import MgroupViewSet, ShiftViewSet, TeamViewSet, MaterialViewSet, GoalViewSet +from apps.mtm.views import (MgroupViewSet, ShiftViewSet, TeamViewSet, MaterialViewSet, GoalViewSet, ProcessViewSet, RouteViewSet) API_BASE_URL = 'api/mtm/' HTML_BASE_URL = 'mtm/' @@ -12,7 +12,8 @@ router.register('team', TeamViewSet, basename='team') router.register('material', MaterialViewSet, basename='material') router.register('shift', ShiftViewSet, basename='shift') router.register('goal', GoalViewSet, basename='goal') - +router.register('process', ProcessViewSet, basename='process') +router.register('route', RouteViewSet, basename='route') urlpatterns = [ path(API_BASE_URL, include(router.urls)), ] \ No newline at end of file diff --git a/apps/mtm/views.py b/apps/mtm/views.py index 765b4d1e..844020ef 100644 --- a/apps/mtm/views.py +++ b/apps/mtm/views.py @@ -1,17 +1,17 @@ from django.shortcuts import render -from rest_framework.mixins import ListModelMixin - -from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet -from apps.mtm.models import Material, Mgroup, Shift, Team, Goal -from apps.mtm.serializers import MaterialSerializer, MgroupSerializer, ShiftSerializer, TeamSerializer, GoalSerializer -from apps.mtm.filters import GoalFilter from rest_framework.decorators import action +from rest_framework.mixins import ListModelMixin from rest_framework.response import Response -from drf_yasg import openapi -from drf_yasg.utils import swagger_auto_schema -from django.db.models import Q +from rest_framework.exceptions import ParseError + +from apps.mtm.filters import GoalFilter, MaterialFilter +from apps.mtm.models import Goal, Material, Mgroup, Shift, Team, Process, Route +from apps.mtm.serializers import (GoalSerializer, MaterialSerializer, + MgroupGoalYearSerializer, MgroupSerializer, + ShiftSerializer, TeamSerializer, ProcessSerializer, RouteSerializer) from apps.mtm.services import get_mgroup_goals -from apps.mtm.serializers import MgroupGoalYearSerializer +from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet + # Create your views here. class MaterialViewSet(CustomModelViewSet): @@ -22,9 +22,9 @@ class MaterialViewSet(CustomModelViewSet): """ queryset = Material.objects.all() serializer_class = MaterialSerializer - filterset_fields = ['code'] - search_fields = ['name', 'code'] - ordering = ['id'] + filterset_class = MaterialFilter + search_fields = ['name', 'code', 'number', 'specification'] + ordering = ['sort', 'id'] class ShiftViewSet(ListModelMixin, CustomGenericViewSet): @@ -90,4 +90,24 @@ class GoalViewSet(CustomModelViewSet): sr.is_valid(raise_exception=True) vdata = sr.validated_data res = get_mgroup_goals(vdata['mgroup'], vdata['year'], True) - return Response(res) \ No newline at end of file + return Response(res) + + +class ProcessViewSet(CustomModelViewSet): + queryset = Process.objects.all() + serializer_class = ProcessSerializer + select_related_fields = ['belong_dept'] + search_fields = ['name', 'cate'] + filterset_fields = ['cate'] + ordering = ['sort', 'create_time'] + + def perform_destroy(self, instance): + if Route.objects.filter(process=instance).exists(): + raise ParseError('存在使用的工艺路线!') + return super().perform_destroy(instance) + +class RouteViewSet(CustomModelViewSet): + queryset = Route.objects.all() + serializer_class = RouteSerializer + filterset_fields = ['material', 'process', 'is_autotask'] + ordering = ['sort', 'create_time'] \ No newline at end of file