diff --git a/.gitignore b/.gitignore index fa116e5..2f02e50 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ deploy.sh package-lock.json .idea/ .vscode/ +.idea/ server/static/ \ No newline at end of file diff --git a/hb_server/apps/develop/urls.py b/hb_server/apps/develop/urls.py index b284243..79f8f04 100644 --- a/hb_server/apps/develop/urls.py +++ b/hb_server/apps/develop/urls.py @@ -2,13 +2,14 @@ from django.db.models import base from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateFIFOItem, UpdateLastTestResult +from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateFIFOItem, UpdateLastTestResult, UpdateSpg urlpatterns = [ path('cleandata/', CleanDataView.as_view()), path('update_cutting/', UpdateCuttingView.as_view()), path('update_last_result/', UpdateLastTestResult.as_view()), path('update_last_result/', UpdateLastTestResult.as_view()), - path('update_fifoitem/', UpdateFIFOItem.as_view()) + path('update_fifoitem/', UpdateFIFOItem.as_view()), + path('update_spg/', UpdateSpg.as_view()) ] diff --git a/hb_server/apps/develop/views.py b/hb_server/apps/develop/views.py index f02f55f..f6a52fc 100644 --- a/hb_server/apps/develop/views.py +++ b/hb_server/apps/develop/views.py @@ -6,7 +6,7 @@ from rest_framework.permissions import IsAdminUser from rest_framework.response import Response from apps.inm.models import FIFO, FIFOItem, Inventory, MaterialBatch from apps.mtm.models import Material -from apps.pm.models import ProductionPlan +from apps.pm.models import ProductionPlan, SubProductionPlan from apps.sam.models import Order from apps.wf.models import Ticket from apps.wpm.models import Operation, OperationMaterial, WProduct, WproductFlow @@ -80,3 +80,14 @@ class UpdateFIFOItem(APIView): i.is_testok = None i.save() return Response() + +class UpdateSpg(APIView): + permission_classes = [IsAdminUser] + @transaction.atomic + def post(self, request, format=None): + """ + 冷加工重新计算合格率 + """ + for i in SubProductionPlan.objects.filter(subproduction__process__id=1): + WpmServies.update_subproduction_progress_main(sp=i) + return Response() diff --git a/hb_server/apps/em/migrations/0010_alter_equipment_state.py b/hb_server/apps/em/migrations/0010_alter_equipment_state.py new file mode 100644 index 0000000..4f8de3c --- /dev/null +++ b/hb_server/apps/em/migrations/0010_alter_equipment_state.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-18 00:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('em', '0009_auto_20210916_1108'), + ] + + operations = [ + migrations.AlterField( + model_name='equipment', + name='state', + field=models.PositiveIntegerField(choices=[(0, '完好'), (1, '限用'), (2, '在修'), (3, '禁用')], default=0, verbose_name='设备状态'), + ), + ] diff --git a/hb_server/apps/em/migrations/0011_auto_20220120_0956.py b/hb_server/apps/em/migrations/0011_auto_20220120_0956.py new file mode 100644 index 0000000..845ec26 --- /dev/null +++ b/hb_server/apps/em/migrations/0011_auto_20220120_0956.py @@ -0,0 +1,63 @@ +# Generated by Django 3.2.9 on 2022-01-20 01:56 + +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), + ('em', '0010_alter_equipment_state'), + ] + + operations = [ + migrations.CreateModel( + name='ECheckRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, 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='删除标记')), + ('check_date', models.DateField(blank=True, null=True, verbose_name='校准检查日期')), + ('description', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='echeckrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.RemoveField( + model_name='equipment', + name='belong_dept', + ), + migrations.RemoveField( + model_name='equipment', + name='statedm', + ), + migrations.AddField( + model_name='equipment', + name='next_check_date', + field=models.DateField(blank=True, null=True, verbose_name='下次校准检查日期'), + ), + migrations.AlterField( + model_name='equipment', + name='state', + field=models.PositiveIntegerField(choices=[(10, '完好'), (20, '限用'), (30, '在修'), (40, '禁用')], default=0, verbose_name='设备状态'), + ), + migrations.DeleteModel( + name='Equipmentrecord', + ), + migrations.AddField( + model_name='echeckrecord', + name='equipment', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='em.equipment', verbose_name='校准检定设备'), + ), + migrations.AddField( + model_name='echeckrecord', + name='update_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='echeckrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'), + ), + ] diff --git a/hb_server/apps/em/migrations/0012_auto_20220120_1048.py b/hb_server/apps/em/migrations/0012_auto_20220120_1048.py new file mode 100644 index 0000000..a0aff45 --- /dev/null +++ b/hb_server/apps/em/migrations/0012_auto_20220120_1048.py @@ -0,0 +1,43 @@ +# Generated by Django 3.2.9 on 2022-01-20 02:48 + +import datetime +from django.db import migrations, models +import django.db.models.deletion +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('em', '0011_auto_20220120_0956'), + ] + + operations = [ + migrations.AddField( + model_name='equipment', + name='check_date', + field=models.DateField(blank=True, null=True, verbose_name='最近校准检查日期'), + ), + migrations.AlterField( + model_name='echeckrecord', + name='check_date', + field=models.DateField(default=datetime.datetime(2022, 1, 20, 2, 48, 20, 706844, tzinfo=utc), verbose_name='校准检查日期'), + preserve_default=False, + ), + migrations.AlterField( + model_name='echeckrecord', + name='equipment', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='em.equipment', verbose_name='校准检定设备'), + preserve_default=False, + ), + migrations.AlterField( + model_name='equipment', + name='cycle', + field=models.IntegerField(blank=True, null=True, verbose_name='校准或检定周期(月)'), + ), + migrations.AlterField( + model_name='equipment', + name='next_check_date', + field=models.DateField(blank=True, null=True, verbose_name='预计下次校准检查日期'), + ), + ] diff --git a/hb_server/apps/em/models.py b/hb_server/apps/em/models.py index f2cb67c..194474f 100644 --- a/hb_server/apps/em/models.py +++ b/hb_server/apps/em/models.py @@ -3,32 +3,34 @@ from django.contrib.auth.models import AbstractUser from django.db.models.base import Model import django.utils.timezone as timezone from django.db.models.query import QuerySet -from apps.system.models import CommonAModel, CommonBModel, Organization, User, Dict, File -#from apps.mtm.models import Process +from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File from utils.model import SoftModel, BaseModel from simple_history.models import HistoricalRecords -class Equipment(CommonBModel): +class Equipment(CommonAModel): """ 设备台账信息 """ + EQUIP_STATE_OK = 10 + EQUIP_STATE_LIMIT = 20 + EQUIP_STATE_FIX = 30 + EQUIP_STATE_DISABLE = 40 state_choices = ( - (0, '完好'), - (1, '限用'), - (2, '在修'), - (3, '禁用') + (EQUIP_STATE_OK, '完好'), + (EQUIP_STATE_LIMIT, '限用'), + (EQUIP_STATE_FIX, '在修'), + (EQUIP_STATE_DISABLE, '禁用') + ) - statedm_choices = ( - (0, '合格'), - (1, '准用'), - (2, '限用'), - (3, '禁用'), - (4, '停用'), - (5, '封存') + state2_choices = ( + (EQUIP_STATE_OK, '合格'), + (EQUIP_STATE_DISABLE, '禁用') + ) - + EQUIP_TYPE_PRO = 1 + EQUIP_TYPE_TEST = 2 type_choices = ( (1, '生产设备'), (2, '检验工具') @@ -54,20 +56,21 @@ class Equipment(CommonBModel): factory = models.CharField('生产厂', max_length=50, null=True, blank=True) production_date = models.DateField('生产日期', null=True, blank=True) buy_date = models.DateField('购置日期', null=True, blank=True) - state = models.CharField('设备状态', max_length=11, choices=state_choices, default=0) + state = models.PositiveIntegerField('设备状态', choices=state_choices, default=0) parameter = models.TextField('技术参数', null=True, blank=True) place = models.CharField('存放位置', max_length=50, null=True, blank=True) count = models.IntegerField('数量', default=0) keeper = models.ForeignKey(User, verbose_name='保管人', on_delete=models.CASCADE, null=True, blank=True) description = models.CharField('描述', max_length=200, blank=True, null=True) - #process = models.ForeignKey(Process, verbose_name='工序', on_delete=models.CASCADE, null=True, blank=True) - mgmtype = models.IntegerField('管理类别', choices=mgmtype_choices, default=1)#监视,测量设备 - way = models.IntegerField('校准或检定方式', choices=way_choices, default=1)#监视,测量设备 - standard = models.CharField('溯源标准或依据', max_length=200, blank=True, null=True)#监视,测量设备 - cycle = models.IntegerField('校准或检定周期', default=0)#监视,测量设备 - usetype = models.IntegerField('使用类别', choices=usetype_choices, default=1)#监视,测量设备 - statedm = models.IntegerField('设备状态', choices=statedm_choices, default=0)#监视,测量设备 + # 以下是监视测量设备单独字段 + mgmtype = models.IntegerField('管理类别', choices=mgmtype_choices, default=1) + way = models.IntegerField('校准或检定方式', choices=way_choices, default=1) + standard = models.CharField('溯源标准或依据', max_length=200, blank=True, null=True) + cycle = models.IntegerField('校准或检定周期(月)', null=True, blank=True) + usetype = models.IntegerField('使用类别', choices=usetype_choices, default=1) + check_date = models.DateField('最近校准检查日期', blank=True, null=True) + next_check_date = models.DateField('预计下次校准检查日期',blank=True, null=True) class Meta: verbose_name = '设备信息' verbose_name_plural = verbose_name @@ -75,8 +78,10 @@ class Equipment(CommonBModel): def __str__(self): return self.number + '-' + self.name -class Equipmentrecord(CommonBModel): - equipment = models.ForeignKey(Equipment, verbose_name='校准检定设备', on_delete=models.CASCADE, null=True, blank=True) - recentlydate = models.DateField('最近一次校准/检定日期',blank=True, null=True) - nextdate = models.DateField('下次应校准或检定日期',blank=True, null=True) +class ECheckRecord(CommonADModel): + """ + 校准鉴定记录 + """ + equipment = models.ForeignKey(Equipment, verbose_name='校准检定设备', on_delete=models.CASCADE) + check_date = models.DateField('校准检查日期') description = models.CharField('描述', max_length=200, blank=True, null=True) \ No newline at end of file diff --git a/hb_server/apps/em/serializers.py b/hb_server/apps/em/serializers.py index be34bfc..c22238d 100644 --- a/hb_server/apps/em/serializers.py +++ b/hb_server/apps/em/serializers.py @@ -1,13 +1,12 @@ from apps.mtm.models import Step from rest_framework import serializers from rest_framework.serializers import ModelSerializer - -from .models import Equipment,Equipmentrecord +from rest_framework import exceptions +from .models import Equipment, ECheckRecord from apps.system.serializers import OrganizationSimpleSerializer, UserSimpleSerializer -class EquipmentSerializer(ModelSerializer): - belong_dept_ = OrganizationSimpleSerializer(source='belong_dept', read_only=True) +class EquipmentListSerializer(ModelSerializer): keeper_ = UserSimpleSerializer(source='keeper', read_only=True) step_ = serializers.SerializerMethodField() class Meta: @@ -17,24 +16,35 @@ class EquipmentSerializer(ModelSerializer): @staticmethod def setup_eager_loading(queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.select_related('belong_dept','keeper') + queryset = queryset.select_related('keeper') return queryset def get_step_(self, obj): return Step.objects.filter(equipments=obj).values('id', 'name', 'number') +class EquipmentCreateUpdateSerializer(ModelSerializer): + class Meta: + model = Equipment + exclude = ['create_by', 'update_by', 'create_time', 'update_time', 'check_date', 'next_check_date'] + + def validate(self, attrs): + if attrs['type'] == Equipment.EQUIP_TYPE_TEST: + if attrs['state'] not in [Equipment.EQUIP_STATE_OK, Equipment.EQUIP_STATE_DISABLE]: + raise exceptions.APIException('设备状态错误') + return super().validate(attrs) + class EquipmentSimpleSerializer(ModelSerializer): class Meta: model = Equipment - fields = ['id', 'number', 'name', 'state'] + fields = ['id', 'number', 'name', 'state', 'model'] -class EquipmentrecordSerializer(ModelSerializer): - equipment_ = EquipmentSerializer(source='equipment', read_only=True) +class ECheckRecordListSerializer(ModelSerializer): + equipment_ = EquipmentSimpleSerializer(source='equipment', read_only=True) class Meta: - model = Equipmentrecord + model = ECheckRecord fields = '__all__' @staticmethod @@ -43,6 +53,11 @@ class EquipmentrecordSerializer(ModelSerializer): queryset = queryset.select_related('equipment') return queryset +class EChcekRecordCreateSerializer(ModelSerializer): + class Meta: + model = ECheckRecord + fields = ['equipment', 'check_date', 'description'] + class DaqCreateSerializer(serializers.Serializer): number = serializers.CharField() file = serializers.FileField() \ No newline at end of file diff --git a/hb_server/apps/em/services.py b/hb_server/apps/em/services.py new file mode 100644 index 0000000..5e1781c --- /dev/null +++ b/hb_server/apps/em/services.py @@ -0,0 +1,20 @@ +from tabnanny import check +from apps.em.models import ECheckRecord, Equipment +from dateutil.relativedelta import relativedelta +from django.utils import timezone +class EmService: + + @classmethod + def update_check_date(cls, equip:Equipment): + # 根据校准检定记录变更下次日期 + check = ECheckRecord.objects.filter(equipment=equip).order_by('id').last() + if check: + equip.check_date = check.check_date + if equip.cycle: + equip.next_check_date = equip.check_date + relativedelta(months=equip.cycle) + else: + equip.next_check_date = None + else: + equip.check_date = None + equip.next_check_date = None + equip.save() \ No newline at end of file diff --git a/hb_server/apps/em/urls.py b/hb_server/apps/em/urls.py index e2f7e91..2fc4bb8 100644 --- a/hb_server/apps/em/urls.py +++ b/hb_server/apps/em/urls.py @@ -1,12 +1,12 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.em.views import DaqView, EquipmentViewSet,EquipmentrecordViewSet +from apps.em.views import DaqView, EquipmentViewSet, EChcekRecordViewSet from django.urls import path, include from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('equipment', EquipmentViewSet, basename='equipment') -router.register('equipmentrecord', EquipmentrecordViewSet, basename='equipmentrecord') +router.register('echeck_record', EChcekRecordViewSet, basename='echeck_record') urlpatterns = [ path('daq/', DaqView.as_view()), path('', include(router.urls)), diff --git a/hb_server/apps/em/views.py b/hb_server/apps/em/views.py index 00ae4b9..aec05f7 100644 --- a/hb_server/apps/em/views.py +++ b/hb_server/apps/em/views.py @@ -1,14 +1,20 @@ +from datetime import timedelta from django.shortcuts import render +from numpy import delete from rest_framework.exceptions import APIException from rest_framework.views import APIView -from rest_framework.viewsets import ModelViewSet - +from rest_framework.viewsets import ModelViewSet, GenericViewSet +from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, ListModelMixin, DestroyModelMixin from rest_framework import serializers, status from rest_framework.response import Response -from apps.em.models import Equipment,Equipmentrecord -from apps.em.serializers import DaqCreateSerializer, EquipmentSerializer,EquipmentrecordSerializer +from apps.em.models import Equipment, ECheckRecord +from apps.em.serializers import DaqCreateSerializer, EChcekRecordCreateSerializer, ECheckRecordListSerializer, \ + EquipmentCreateUpdateSerializer, EquipmentListSerializer +from apps.em.services import EmService from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin +from django.utils import timezone +from django.db import transaction # Create your views here. class EquipmentViewSet(CreateUpdateModelAMixin, OptimizationMixin, ModelViewSet): @@ -18,48 +24,48 @@ class EquipmentViewSet(CreateUpdateModelAMixin, OptimizationMixin, ModelViewSet) perms_map = {'get': '*', 'post': 'equipment_create', 'put': 'equipment_update', 'delete': 'equipment_delete'} queryset = Equipment.objects.all() - serializer_class = EquipmentSerializer + serializer_class = EquipmentListSerializer search_fields = ['number', 'name','description'] - filterset_fields = ['belong_dept', 'keeper', 'type'] + filterset_fields = ['keeper', 'type'] ordering_fields = ['create_time'] ordering = ['-create_time'] + def get_serializer_class(self): + if self.action in ['create', 'update']: + return EquipmentCreateUpdateSerializer + return super().get_serializer_class() # Create your views here. -class EquipmentrecordViewSet(CreateUpdateModelAMixin, OptimizationMixin, ModelViewSet): +class EChcekRecordViewSet(CreateUpdateModelAMixin, OptimizationMixin, + CreateModelMixin, RetrieveModelMixin, ListModelMixin, DestroyModelMixin, GenericViewSet): """ 设备校准-增删改查 """ - perms_map = {'get': '*', 'post': 'equipmentrecord_create', - 'put': 'equipmentrecord_update', 'delete': 'equipmentrecord_delete'} - queryset = Equipmentrecord.objects.all() - serializer_class = EquipmentrecordSerializer + perms_map = {'get': '*', 'post': 'echeckrecord_create', 'delete': 'echeckrecord_delete'} + queryset = ECheckRecord.objects.all() + serializer_class = ECheckRecordListSerializer filterset_fields = ['equipment'] - ordering_fields = ['create_time'] - ordering = ['-create_time'] + ordering = ['-id'] + def get_serializer_class(self): + if self.action in ['create']: + return EChcekRecordCreateSerializer + return super().get_serializer_class() + + @transaction.atomic def create(self, request, *args, **kwargs): - - data = request.data - if data.get('equipment', None): - equipment = Equipment.objects.get(pk=data['equipment']) - equipment.statedm = data['state'] - equipment.save() serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save() - return Response(status=status.HTTP_200_OK) - def update(self, request, *args, **kwargs): - data = request.data - if data.get('equipment', None): - equipment = Equipment.objects.get(pk=data['equipment']) - equipment.statedm = data['state'] - equipment.save() - id = self.get_object() - serializer = self.get_serializer(id, data=data) - serializer.is_valid(raise_exception=True) - serializer.save() - return Response(status=status.HTTP_200_OK) + instance = serializer.save(create_by=request.user) + EmService.update_check_date(equip=instance.equipment) + return Response() + + @transaction.atomic + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + instance.delete() + EmService.update_check_date(equip=instance.equipment) + return Response() import uuid import os diff --git a/hb_server/apps/hrm/models.py b/hb_server/apps/hrm/models.py index dc85cf5..5f95387 100644 --- a/hb_server/apps/hrm/models.py +++ b/hb_server/apps/hrm/models.py @@ -36,8 +36,8 @@ class Employee(CommonAModel): def __str__(self): return self.name -class Attendance(CommonADModel): - """ - 出勤记录 - """ +# class Attendance(CommonADModel): +# """ +# 出勤记录 +# """ \ No newline at end of file diff --git a/hb_server/apps/inm/filters.py b/hb_server/apps/inm/filters.py index db876ec..bc97441 100644 --- a/hb_server/apps/inm/filters.py +++ b/hb_server/apps/inm/filters.py @@ -21,7 +21,8 @@ class MbFilterSet(filters.FilterSet): class IProductFilterSet(filters.FilterSet): order = filters.NumberFilter(field_name="wproduct__subproduction_plan__production_plan__order") - + update_time_start = filters.DateFilter(field_name="update_time", lookup_expr='gte') + update_time_end = filters.DateFilter(field_name="update_time", lookup_expr='lte') class Meta: model = IProduct - fields = ['material', 'warehouse', 'batch', 'order', 'material__type'] + fields = ['material', 'warehouse', 'batch', 'order', 'material__type', 'is_saled', 'update_time_start', 'update_time_end'] diff --git a/hb_server/apps/mtm/migrations/0044_subproduction_need_combtest.py b/hb_server/apps/mtm/migrations/0044_subproduction_need_combtest.py new file mode 100644 index 0000000..c570518 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0044_subproduction_need_combtest.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-19 07:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0043_auto_20220106_0942'), + ] + + operations = [ + migrations.AddField( + model_name='subproduction', + name='need_combtest', + field=models.BooleanField(default=False, verbose_name='需要质检'), + ), + ] diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index b7b99c2..4afb8b5 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -210,6 +210,7 @@ class SubProduction(CommonAModel): name = models.CharField('命名', max_length=50, null=True, blank=True) product = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='隶属大工序', on_delete=models.CASCADE, related_name='subproduction_process') + need_combtest = models.BooleanField('需要质检', default=False) sort = models.IntegerField('排序号', default=1) class Meta: diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 8e5eb53..99cdfb6 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -72,6 +72,11 @@ class SubProductionSerializer(serializers.ModelSerializer): model = SubProduction fields = '__all__' +class SubProductionCreateUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = SubProduction + fields = ['name', 'product', 'process', 'need_combtest', 'sort'] + class OtherMaterialSerializer(serializers.ModelSerializer): class Meta: model = SubprodctionMaterial diff --git a/hb_server/apps/mtm/views.py b/hb_server/apps/mtm/views.py index 04f3caa..1bb9e47 100644 --- a/hb_server/apps/mtm/views.py +++ b/hb_server/apps/mtm/views.py @@ -4,7 +4,7 @@ from rest_framework.mixins import CreateModelMixin, ListModelMixin, UpdateModelM from apps.mtm.filters import MaterialFilterSet, TechDocFilterset from apps.mtm.models import Material, Process, RecordForm, RecordFormField, Step, SubprodctionMaterial, TechDoc, UsedStep, SubProduction -from apps.mtm.serializers import InputMaterialSerializer, InputMaterialUpdateSerializer, MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, OtherMaterialSerializer, OutputMaterialSerializer, OutputMaterialUpdateSerializer, ProcessSerializer, RecordFormCreateSerializer, RecordFormDetailSerializer, RecordFormFieldCreateSerializer, RecordFormFieldSerializer, RecordFormFieldUpdateSerializer, RecordFormSerializer, RecordFormUpdateSerializer, StepDetailSerializer, StepSerializer, SubProductionSerializer, SubprodctionMaterialListSerializer, TechDocCreateSerializer, TechDocListSerializer, TechDocUpdateSerializer, UsedStepCreateSerializer, UsedStepListSerializer, UsedStepUpdateSerializer +from apps.mtm.serializers import InputMaterialSerializer, InputMaterialUpdateSerializer, MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, OtherMaterialSerializer, OutputMaterialSerializer, OutputMaterialUpdateSerializer, ProcessSerializer, RecordFormCreateSerializer, RecordFormDetailSerializer, RecordFormFieldCreateSerializer, RecordFormFieldSerializer, RecordFormFieldUpdateSerializer, RecordFormSerializer, RecordFormUpdateSerializer, StepDetailSerializer, StepSerializer, SubProductionCreateUpdateSerializer, SubProductionSerializer, SubprodctionMaterialListSerializer, TechDocCreateSerializer, TechDocListSerializer, TechDocUpdateSerializer, UsedStepCreateSerializer, UsedStepListSerializer, UsedStepUpdateSerializer from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action from rest_framework.response import Response @@ -80,6 +80,10 @@ class SubProductionViewSet(CreateUpdateModelAMixin, ModelViewSet): search_fields = ['name'] serializer_class = SubProductionSerializer ordering = ['sort'] + def get_serializer_class(self): + if self.action in ['create', 'update']: + return SubProductionCreateUpdateSerializer + return super().get_serializer_class() class InputMaterialViewSet(CreateUpdateModelAMixin, ModelViewSet): """ diff --git a/hb_server/apps/pm/migrations/0023_alter_productionplan_order.py b/hb_server/apps/pm/migrations/0023_alter_productionplan_order.py new file mode 100644 index 0000000..4fd00c6 --- /dev/null +++ b/hb_server/apps/pm/migrations/0023_alter_productionplan_order.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.9 on 2022-01-18 00:39 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0010_auto_20211208_1408'), + ('pm', '0022_auto_20211229_1429'), + ] + + operations = [ + migrations.AlterField( + model_name='productionplan', + name='order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plan_order', to='sam.order', verbose_name='关联订单'), + ), + ] diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index 3fe8799..3761ead 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -4,7 +4,7 @@ from django.db import transaction from rest_framework import serializers from rest_framework.views import APIView from apps.em.models import Equipment -from apps.em.serializers import EquipmentSerializer +from apps.em.serializers import EquipmentSimpleSerializer from apps.inm.models import MaterialBatch from apps.inm.serializers import MaterialBatchSerializer from apps.mtm.models import Step, SubProduction, SubprodctionMaterial, UsedStep @@ -248,5 +248,5 @@ class ResourceViewSet(GenericViewSet): subproductions = SubProduction.objects.filter(product__id__in=rdata_l, is_deleted=False) steps = Step.objects.filter(usedstep__is_deleted=False, usedstep__subproduction__in=subproductions) equips = Equipment.objects.filter(step_equips__in=steps, is_deleted=False).distinct() - serializer = EquipmentSerializer(instance=equips, many=True) + serializer = EquipmentSimpleSerializer(instance=equips, many=True) return Response(serializer.data) diff --git a/hb_server/apps/srm/serializers.py b/hb_server/apps/srm/serializers.py index edea221..10e2b94 100644 --- a/hb_server/apps/srm/serializers.py +++ b/hb_server/apps/srm/serializers.py @@ -19,4 +19,8 @@ class PlanGanttSerializer(serializers.ModelSerializer): def get_children(self, obj): subplans = SubProductionPlan.objects.filter(production_plan=obj).order_by('process__number') - return SubplanGanttSerializer(instance=subplans, many=True).data \ No newline at end of file + return SubplanGanttSerializer(instance=subplans, many=True).data + +class ProcessYieldSerializer(serializers.Serializer): + datetime_start = serializers.DateField(label='开始时间', required=False, allow_null=True) + datetime_end = serializers.DateField(label='结束时间', required=False, allow_null=True) \ No newline at end of file diff --git a/hb_server/apps/srm/urls.py b/hb_server/apps/srm/urls.py index cbaf2ed..0d8a992 100644 --- a/hb_server/apps/srm/urls.py +++ b/hb_server/apps/srm/urls.py @@ -3,11 +3,12 @@ from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.srm.views import GanttPlan +from apps.srm.views import GanttPlan, ProcessYieldView router = DefaultRouter() urlpatterns = [ path('gantt/plan/', GanttPlan.as_view()), + path('process/yield/', ProcessYieldView.as_view()), path('', include(router.urls)), ] diff --git a/hb_server/apps/srm/views.py b/hb_server/apps/srm/views.py index 4551f30..ec7a398 100644 --- a/hb_server/apps/srm/views.py +++ b/hb_server/apps/srm/views.py @@ -1,9 +1,14 @@ + from django.shortcuts import render from rest_framework import serializers -from rest_framework.generics import ListAPIView +from rest_framework.generics import ListAPIView, CreateAPIView +from rest_framework.views import APIView from rest_framework.response import Response +from apps.mtm.models import Process, Step from apps.pm.models import ProductionPlan, SubProductionPlan -from apps.srm.serializers import PlanGanttSerializer +from apps.srm.serializers import PlanGanttSerializer, ProcessYieldSerializer +from apps.wpm.models import WProduct, WproductFlow +from django.db.models import Count # Create your views here. class GanttPlan(ListAPIView): @@ -15,4 +20,48 @@ class GanttPlan(ListAPIView): queryset = ProductionPlan.objects.filter(is_deleted=False, is_planed=True).prefetch_related('subplan_plan', 'subplan_plan__process') ordering = ['-id'] +class ProcessYieldView(CreateAPIView): + """ + 工序成品率统计 + """ + perms_map = {'get':'*'} + serializer_class = ProcessYieldSerializer + + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + wpfs = WproductFlow.objects.filter(is_lastlog=True) + if vdata.get('datetime_start', None): + wpfs = wpfs.filter(update_time__gte = vdata.get('datetime_start')) + if vdata.get('datetime_end', None): + wpfs = wpfs.filter(update_time__lte = vdata.get('datetime_end')) + # 根据产品日志记录进行聚合 + count_ok_g = list(wpfs.filter(act_state__in=[WProduct.WPR_ACT_STATE_INM, + WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_SELLED]).values('step__process__id').annotate(count_ok=Count('id'))) + count_notok_g = list( + ( + wpfs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP]).exclude(step__process__id=1) + | wpfs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP], + step__process__id=1).exclude(number=None) + )\ + .values('step__process__id', + ).annotate(count_notok=Count('id'))) + ret = [] + process_l = list(Process.objects.filter(is_deleted=False).order_by('number').values('id', 'name')) + for i in process_l: + ret_item = {'id':i['id'], 'name':i['name'], 'count_ok':0, 'count_notok':0, 'rate':1} + for m in count_ok_g: + if m['step__process__id'] == ret_item['id']: + ret_item['count_ok'] = m['count_ok'] + for n in count_notok_g: + if n['step__process__id'] == ret_item['id']: + ret_item['count_notok'] = n['count_notok'] + rate = (ret_item['count_ok']/(ret_item['count_ok']+ret_item['count_notok'])) \ + if ret_item['count_ok']+ret_item['count_notok']>0 else 1 + ret_item['rate'] = rate + ret.append(ret_item) + return Response(ret) + diff --git a/hb_server/apps/wpm/filters.py b/hb_server/apps/wpm/filters.py index c8392d3..54454f8 100644 --- a/hb_server/apps/wpm/filters.py +++ b/hb_server/apps/wpm/filters.py @@ -41,6 +41,12 @@ class WProductFilterSet(filters.FilterSet): def filter_tag(self, queryset, name, value): if value == 'no_scrap': queryset = queryset.exclude(act_state=WProduct.WPR_ACT_STATE_SCRAP) + elif value == 'notok': + queryset = queryset.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP])\ + .exclude(step__process__id = 1) # 不算冷加工的报废 + elif value == 'ok': + queryset = queryset.filter(act_state__in=[WProduct.WPR_ACT_STATE_INM, + WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_SELLED]) return queryset diff --git a/hb_server/apps/wpm/migrations/0049_operationequip_state.py b/hb_server/apps/wpm/migrations/0049_operationequip_state.py new file mode 100644 index 0000000..5541350 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0049_operationequip_state.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-18 00:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0048_operationwproduct_place'), + ] + + operations = [ + migrations.AddField( + model_name='operationequip', + name='state', + field=models.PositiveSmallIntegerField(choices=[(0, '完好'), (1, '限用'), (2, '在修'), (3, '禁用')], default=0, verbose_name='当前设备状态'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0050_alter_operationequip_state.py b/hb_server/apps/wpm/migrations/0050_alter_operationequip_state.py new file mode 100644 index 0000000..a04569a --- /dev/null +++ b/hb_server/apps/wpm/migrations/0050_alter_operationequip_state.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-20 02:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0049_operationequip_state'), + ] + + operations = [ + migrations.AlterField( + model_name='operationequip', + name='state', + field=models.PositiveSmallIntegerField(choices=[(10, '完好'), (20, '限用'), (30, '在修'), (40, '禁用')], default=10, verbose_name='当前设备状态'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0051_auto_20220120_1541.py b/hb_server/apps/wpm/migrations/0051_auto_20220120_1541.py new file mode 100644 index 0000000..9a0c6fb --- /dev/null +++ b/hb_server/apps/wpm/migrations/0051_auto_20220120_1541.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.9 on 2022-01-20 07:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0010_auto_20211208_1408'), + ('wpm', '0050_alter_operationequip_state'), + ] + + operations = [ + migrations.AddField( + model_name='wproduct', + name='to_order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sam.order', verbose_name='指派的订单'), + ), + migrations.AddField( + model_name='wproductflow', + name='to_order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sam.order', verbose_name='指派的订单'), + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index dd870b4..c52a061 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -118,7 +118,8 @@ class WProduct(CommonAModel): on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_test') ticket = models.ForeignKey('wf.ticket', verbose_name='当前工单', on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_ticket') - + + to_order = models.ForeignKey('sam.order', verbose_name='指派的订单', null=True, blank=True, on_delete = models.CASCADE) is_mtested = models.BooleanField('是否军检', default=False) is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True) remark_mtest = models.TextField('军检备注', null=True, blank=True) @@ -192,7 +193,7 @@ class WproductFlow(CommonAModel): on_delete=models.SET_NULL, null=True, blank=True) ticket = models.ForeignKey('wf.ticket', verbose_name='当前工单', on_delete=models.SET_NULL, null=True, blank=True) - + to_order = models.ForeignKey('sam.order', verbose_name='指派的订单', null=True, blank=True, on_delete = models.CASCADE) is_mtested = models.BooleanField('是否军检', default=False) is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True) remark_mtest = models.TextField('军检备注', null=True, blank=True) @@ -336,4 +337,5 @@ class OperationEquip(BaseModel): Operation, verbose_name='关联操作', on_delete=models.CASCADE, related_name='oe_operation') equip = models.ForeignKey(Equipment, verbose_name='生产设备', on_delete=models.CASCADE, related_name='oe_equip') + state = models.PositiveSmallIntegerField('当前设备状态', choices=Equipment.state_choices, default=Equipment.EQUIP_STATE_OK) remark = models.TextField('备注', null=True, blank=True) diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index 8956eb3..3181088 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -19,6 +19,7 @@ from apps.system.models import User from apps.system.serializers import UserSimpleSerializer from apps.wpm.models import Operation, OperationEquip, OperationMaterial, OperationWproduct, Pick, WMaterial, WProduct, OperationRecord, OperationRecordItem, WprouctTicket from django.db import transaction +from apps.sam.models import Order class PickHalfSerializer(serializers.Serializer): id = serializers.PrimaryKeyRelatedField(queryset=SubProductionProgress.objects.all(), label='子计划进度ID') @@ -155,6 +156,7 @@ class WProductListSerializer(serializers.ModelSerializer): subproduction_plan_ = SubproductionPlanSimpleSerializer(source='subproduction_plan', read_only=True) warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True) children = serializers.SerializerMethodField() + to_order_ = OrderSimpleSerializer(source='to_order', read_only=True) class Meta: model = WProduct fields = '__all__' @@ -560,4 +562,8 @@ class CuttingListSerializer(serializers.ModelSerializer): class WproductMtestSerializer(serializers.ModelSerializer): class Meta: model = WProduct - fields = ['remark_mtest', 'is_mtestok'] \ No newline at end of file + fields = ['remark_mtest', 'is_mtestok'] + +class WproductToOrderSerializer(serializers.Serializer): + wproducts = serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all(), many=True) + order = serializers.PrimaryKeyRelatedField(queryset=Order.objects.all()) \ No newline at end of file diff --git a/hb_server/apps/wpm/services.py b/hb_server/apps/wpm/services.py index 3734106..d880f7f 100644 --- a/hb_server/apps/wpm/services.py +++ b/hb_server/apps/wpm/services.py @@ -54,7 +54,8 @@ class WpmServies(object): elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and wproduct.material.type == Material.MA_TYPE_GOOD: # 成品检验 wproduct.act_state = WProduct.WPR_ACT_STATE_TOFINALTEST - elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and wproduct.step.type == Step.STEP_TYPE_COMB: # 夹层检验 + elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and \ + wproduct.subproduction_plan.subproduction.need_combtest : # 配置中需要质检 wproduct.act_state = WProduct.WPR_ACT_STATE_TOCOMBTEST else: wproduct.act_state = WProduct.WPR_ACT_STATE_OK @@ -113,7 +114,11 @@ class WpmServies(object): objs = WproductFlow.objects.filter(subproduction_plan=sp, is_lastlog=True) count_ok = objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_INM, WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_SELLED]).count() - count_notok = objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP]).count() + count_notok = ( + objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP]).exclude(step__process__id=1) + | objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP], + step__process__id=1).exclude(number=None) + ).count() count_real = objs.exclude(act_state__in=[WProduct.WPR_ACT_STATE_TORETEST, WProduct.WPR_ACT_STATE_DOWAIT, WProduct.WPR_ACT_STATE_DOING]).count() ins = SubProductionProgress.objects.filter(subproduction_plan=sp, diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index 28d8a12..47c49dc 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -16,7 +16,7 @@ from rest_framework.decorators import action from apps.wf.models import Workflow from apps.wpm.filters import CuttingFilterSet, OperationRecordFilterSet, WMaterialFilterSet, WProductFilterSet from apps.wpm.models import OperationEquip, OperationWproduct, Pick, PickWproduct, WMaterial, WProduct, Operation, \ - OperationMaterial, OperationRecord, OperationRecordItem, WprouctTicket + OperationMaterial, OperationRecord, OperationRecordItem, WproductFlow, WprouctTicket from apps.wpm.serializers import CuttingListSerializer, OperationEquipListSerializer, OperationEquipUpdateSerializer, \ OperationMaterialCreate1ListSerailizer, OperationMaterialCreate1Serailizer, OperationMaterialCreate2ListSerailizer, \ @@ -27,7 +27,7 @@ from apps.wpm.serializers import CuttingListSerializer, OperationEquipListSerial PickSerializer, OperationInitSerializer, OperationSubmitSerializer, ScrapSerializer, WMaterialListSerializer, \ WProductCardSerializer, WProductDetailSerializer, WProductListSerializer, \ WpmTestFormInitSerializer, WproductMtestSerializer, WproductPutInSerializer, \ - WproductPutInsSerializer, WproductTicketListSerializer + WproductPutInsSerializer, WproductTicketListSerializer, WproductToOrderSerializer from rest_framework.response import Response from django.db import transaction @@ -148,7 +148,7 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): """ perms_map = {'*': '*'} queryset = WProduct.objects.select_related('step', 'material', - 'subproduction_plan', 'warehouse').prefetch_related('wproduct_child') + 'subproduction_plan', 'warehouse', 'order').prefetch_related('wproduct_child') serializer_class = WProductListSerializer filterset_class = WProductFilterSet search_fields = ['number'] @@ -441,7 +441,31 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): ret.append([str(index + 1), item['step_name'], item['actions']]) return Response(ret) - + @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproductToOrderSerializer) + @transaction.atomic + def to_order(self, request, pk=None): + """ + 指派发货订单 + """ + serializer = WproductToOrderSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + wps = WProduct.objects.filter(id__in = [i.id for i in vdata.get('wproducts')]) + wp = wps.first() + order = vdata['order'] + if wp.material != order.product: + raise exceptions.ValidationError('所选订单与产品不符') + for i in wps: + if i.material != wp.material and i.material.type != Material.MA_TYPE_GOOD and i.act_state \ + not in [WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_INM]: + raise exceptions.ValidationError('所选产品错误') + for i in wps: + i.to_order = order + i.update_by = request.user + i.save() + WpmServies.add_wproduct_flow_log(i,change_str='to_order') + return Response() + class WproductTicketViewSet(ListModelMixin, GenericViewSet): """ 玻璃审批工单 @@ -559,6 +583,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd ope = OperationEquip() ope.operation = op ope.equip = i + ope.state = i.state ope.save() # 查询所需的工具工装 for i in SubprodctionMaterial.objects.filter(type=SubprodctionMaterial.SUB_MA_TYPE_TOOL, diff --git a/hb_server/utils/response.py b/hb_server/utils/response.py index f4379f4..0411547 100644 --- a/hb_server/utils/response.py +++ b/hb_server/utils/response.py @@ -47,17 +47,20 @@ class FitJSONRenderer(JSONRenderer): """ response_body = BaseResponse() response = renderer_context.get("response") - response_body.code = response.status_code - if response_body.code >= 400: # 响应异常 - response_body.data = data # data里是详细异常信息 - prefix = "" - if isinstance(data, dict): - prefix = list(data.keys())[0] - data = data[prefix] - if isinstance(data, list): - data = data[0] - response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert + status_code = response.status_code # Http状态异常码 + if status_code >= 400: # 如果http响应异常 + if isinstance(data, dict) and 'code' in data: # 如果自定义了异常码 + response_body = data + else: + response_body.data = data # data里是详细异常信息 + prefix = "" + if isinstance(data, dict): + prefix = list(data.keys())[0] + data = data[prefix] + elif isinstance(data, list): + data = data[0] + response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert else: response_body.data = data - renderer_context.get("response").status_code = 200 # 统一成200响应,用code区分 + renderer_context.get("response").status_code = 200 # 统一成200响应, 可用body里code区分业务异常 return super(FitJSONRenderer, self).render(response_body.dict, accepted_media_type, renderer_context)