diff --git a/hb_server/apps/pm/migrations/0027_auto_20220221_1027.py b/hb_server/apps/pm/migrations/0027_auto_20220221_1027.py new file mode 100644 index 0000000..a3891ff --- /dev/null +++ b/hb_server/apps/pm/migrations/0027_auto_20220221_1027.py @@ -0,0 +1,40 @@ +# Generated by Django 3.2.9 on 2022-02-21 02:27 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0025_alter_testrecord_type'), + ('pm', '0026_auto_20220218_1636'), + ] + + operations = [ + migrations.RemoveField( + model_name='subproductionplan', + name='form', + ), + migrations.RemoveField( + model_name='subproductionplan', + name='is_testok', + ), + migrations.RemoveField( + model_name='subproductionplan', + name='tester', + ), + migrations.AddField( + model_name='subproductionplan', + name='first_test', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='qm.testrecord'), + ), + migrations.AddField( + model_name='subproductionplan', + name='first_test_state', + field=models.IntegerField(default=10, verbose_name='首件状态'), + ), + migrations.DeleteModel( + name='FirstItem', + ), + ] diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py index 3b4607d..53d5e2f 100644 --- a/hb_server/apps/pm/models.py +++ b/hb_server/apps/pm/models.py @@ -69,6 +69,7 @@ class SubProductionPlan(CommonAModel): (SUBPLAN_STATE_WORKING, '生产中'), (SUBPLAN_STATE_DONE, '已完成'), ) + number = models.CharField('子计划编号', max_length=50, unique=True, null=True, blank=True) production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE, related_name='subplan_plan') subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE, related_name='subplan_subprod') @@ -91,11 +92,8 @@ class SubProductionPlan(CommonAModel): end_date_real = models.DateField('实际完工日期', null=True, blank=True) is_picked = models.BooleanField('是否已领料', default=False) - # wproducts = models.JSONField('半成品表', default=list, blank=True) - is_testok = models.BooleanField('首件是否合格', null=True, blank=True) - form = models.ForeignKey(RecordForm, verbose_name='首件检查表', on_delete=models.CASCADE, null=True, blank=True) - tester = models.ForeignKey(User, on_delete=models.CASCADE, - verbose_name="首件检查员", null=True, blank=True, related_name='first_tester') + first_test_state = models.IntegerField('首件状态', default=SUBPLAN_STATE_PLANING) + first_test = models.ForeignKey('qm.testrecord', on_delete=models.CASCADE, null=True, blank=True) leader_1 = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="工序负责人", null=True, blank=True, related_name='first_leader_1') leader_2 = models.ForeignKey(User, on_delete=models.CASCADE, @@ -109,16 +107,6 @@ class SubProductionPlan(CommonAModel): verbose_name = '子生产计划' verbose_name_plural = verbose_name -class FirstItem(BaseModel): - """ - 首件确认表记录条目 - """ - form_field = models.ForeignKey(RecordFormField, verbose_name='关联自定义表格字段', on_delete=models.CASCADE) - field_value = models.JSONField('录入值', null=True, blank=True) - is_hidden = models.BooleanField('是否隐藏', default=False) - is_testok = models.BooleanField('是否合格', null=True, blank=True) - subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联的子计划', on_delete=models.CASCADE, related_name='item_test_record') - class SubProductionProgress(BaseModel): """ 子计划生产进度统计表/物料消耗 diff --git a/hb_server/apps/pm/serializers.py b/hb_server/apps/pm/serializers.py index d098511..d32669c 100644 --- a/hb_server/apps/pm/serializers.py +++ b/hb_server/apps/pm/serializers.py @@ -33,12 +33,15 @@ class ResourceConvertListSerializer(serializers.ListSerializer): class ResourceCalListSerializer(serializers.ListSerializer): child = ResourceCalSerializer() -class SubProductionPlanListSerializer(serializers.ModelSerializer): +class SubProductionPlanListSerializer(DynamicFieldsSerializerMixin, serializers.ModelSerializer): workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True) process_ = ProcessSimpleSerializer(source='process', read_only=True) subproduction_ = SubProductionSimpleSerializer(source='subproduction', read_only=True) product_ = MaterialSimpleSerializer(source='product', read_only=True) plan_product_ = serializers.SerializerMethodField() + leader_1_ = UserSimpleSerializer(source='leader_1', read_only=True) + leader_2_ = UserSimpleSerializer(source='leader_2', read_only=True) + leader_3_ = UserSimpleSerializer(source='leader_3', read_only=True) class Meta: model=SubProductionPlan fields = '__all__' @@ -81,20 +84,7 @@ class SubProductionProgressSerializer(serializers.ModelSerializer): class FirstTestInitSerializer(serializers.Serializer): form = serializers.PrimaryKeyRelatedField(queryset=RecordForm.objects.all(), required=True) -class FirstTestDetailSerializer(serializers.ModelSerializer): - tester_ = UserSimpleSerializer(source='tester', read_only=True) - leader_1_ = UserSimpleSerializer(source='leader_1', read_only=True) - leader_2_ = UserSimpleSerializer(source='leader_2', read_only=True) - leader_3_ = UserSimpleSerializer(source='leader_3', read_only=True) - form_ = RecordFormSimpleSerializer(source='form', read_only=True) - # record_data = TestRecordItemSerializer(source='item_test_record', read_only=True, many=True) - record_data = serializers.SerializerMethodField() - class Meta: - model = SubProductionPlan - fields = ['id', 'form', 'form_', 'is_testok', 'remark', 'first_sign_time' - 'tester', 'tester_', 'leader_1', 'leader_1_', 'leader_2', - 'leader_2_', 'leader_3', 'leader_3_'] - - def get_record_data(self, obj): - return None \ No newline at end of file +class FirstTestAuditSerializer(serializers.Serializer): + leader = serializers.CharField() + base64 = serializers.CharField() \ No newline at end of file diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index e7bcfd2..e9f541b 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -5,23 +5,27 @@ from rest_framework import serializers from rest_framework.views import APIView from apps.em.models import Equipment from apps.em.serializers import EquipmentSimpleSerializer +from apps.hrm.services import HRMService from apps.inm.models import MaterialBatch from apps.inm.serializers import MaterialBatchSerializer -from apps.mtm.models import Material, Step, SubProduction, SubprodctionMaterial +from apps.mtm.models import Material, RecordFormField, Step, SubProduction, SubprodctionMaterial from apps.pm.filters import PlanFilterSet, SubproductionProgressFilterSet +from apps.qm.models import TestRecord, TestRecordItem from apps.system.mixins import CreateUpdateModelAMixin -from apps.pm.serializers import FirstTestInitSerializer, GenSubPlanSerializer, PickNeedSerializer, PlanDestorySerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, ResourceConvertListSerializer, ResourceConvertSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer +from apps.pm.serializers import FirstTestAuditSerializer, FirstTestInitSerializer, GenSubPlanSerializer, PickNeedSerializer, PlanDestorySerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, ResourceConvertListSerializer, ResourceConvertSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin from apps.pm.models import ProductionPlan, SubProductionProgress, SubProductionPlan from rest_framework.viewsets import GenericViewSet, ModelViewSet from django.shortcuts import render from apps.sam.models import Order -from rest_framework.exceptions import APIException, ParseError +from rest_framework.exceptions import APIException, ParseError, ValidationError from rest_framework.response import Response from rest_framework.decorators import action from django.db.models import F from utils.tools import ranstr from django.db import transaction +from rest_framework import status +from django.utils import timezone # Create your views here. def updateOrderPlanedCount(order): @@ -145,7 +149,9 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo 子生产计划-列表/修改 """ perms_map = {'get': '*', 'put':'subplan_update'} - queryset = SubProductionPlan.objects.select_related('process', 'workshop', 'subproduction', 'product', 'production_plan__product') + queryset = SubProductionPlan.objects.select_related('process', + 'workshop', 'subproduction', 'product', + 'production_plan__product', 'leader_1', 'leader_2', 'leader_3') search_fields = [] serializer_class = SubProductionPlanListSerializer filterset_fields = ['production_plan', 'process', 'state', 'product', 'workshop'] @@ -229,20 +235,64 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo have = MaterialBatchSerializer(instance=objs, many=True).data return Response({'need':need, 'have':have}) - @action(methods=['put'], detail=True, perms_map={'post':'first_test'}, serializer_class=FirstTestInitSerializer) + @action(methods=['post'], detail=True, perms_map={'post':'first_test'}, serializer_class=FirstTestInitSerializer) @transaction.atomic def first_test_init(self, request, pk=None): """ - 获取首件检查表 + 首件检查表初始化 """ obj = self.get_object() - if obj.is_testok is None: + if obj.first_test is None: rdata = request.data serializer = self.get_serializer(data=rdata) serializer.is_valid(raise_exception=True) form = serializer.validated_data.get('form') - - raise APIException('已经过首件确认') + savedict = dict( + create_by=request.user, + subproduction_plan=obj, + type = TestRecord.TEST_FIRST, + form=form) + tr = TestRecord.objects.create(**savedict) + for i in RecordFormField.objects.filter(form=form, is_deleted=False): + tri = TestRecordItem() + tri.test_record = tr + tri.form_field = i + tri.is_hidden = i.is_hidden + tri.create_by = request.user + tri.save() + return Response() + raise APIException('首件检查已存在') + + @action(methods=['post'], detail=True, perms_map={'post':'first_test_audit'}, serializer_class=FirstTestAuditSerializer) + @transaction.atomic + def first_audit(self, request, pk=None): + obj = self.get_object() + if obj.leader_1 and obj.leader_2 and obj.leader_3: + raise ValidationError('首件确认已完成') + if obj.first_test is None: + raise ValidationError('未进行首件检查') + if not obj.first_test.is_submited: + raise ValidationError('首件检查未提交') + if not obj.first_test.is_testok: + raise ValidationError('首件检查不合格') + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + user, msg = HRMService.face_compare_from_base64(vdata.get('base64')) + if user: + le = vdata.get('leader') + if le not in ['leader_1', 'leader_2', 'leader_3']: + return Response('审核人有误', status=status.HTTP_400_BAD_REQUEST) + if vdata.get('leader') == 'leader_1': + obj.leader_1 = user + elif vdata.get('leader') == 'leader_2': + obj.leader_2 = user + else: + obj.leader_3 = user + obj.first_sign_time = timezone.now() + obj.save() + return Response() + return Response(msg, status=status.HTTP_400_BAD_REQUEST) diff --git a/hb_server/apps/qm/migrations/0025_alter_testrecord_type.py b/hb_server/apps/qm/migrations/0025_alter_testrecord_type.py new file mode 100644 index 0000000..5d7404c --- /dev/null +++ b/hb_server/apps/qm/migrations/0025_alter_testrecord_type.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-02-21 02:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('qm', '0024_rename_is_midtesing_testrecord_is_midtesting'), + ] + + operations = [ + migrations.AlterField( + model_name='testrecord', + name='type', + field=models.PositiveSmallIntegerField(choices=[(20, '工序检验'), (30, '工序复检'), (36, '夹层检验'), (40, '成品检验'), (10, '首件检验')], default=20), + ), + ] diff --git a/hb_server/apps/qm/models.py b/hb_server/apps/qm/models.py index db776de..adb040a 100644 --- a/hb_server/apps/qm/models.py +++ b/hb_server/apps/qm/models.py @@ -47,6 +47,7 @@ class TestRecord(CommonADModel): """ 检验记录 """ + TEST_FIRST = 10 TEST_PROCESS = 20 TEST_PROCESS_RE = 30 TEST_COMB = 36 @@ -55,7 +56,8 @@ class TestRecord(CommonADModel): (TEST_PROCESS, '工序检验'), (TEST_PROCESS_RE, '工序复检'), (TEST_COMB, '夹层检验'), - (TEST_FINAL, '成品检验') + (TEST_FINAL, '成品检验'), + (TEST_FIRST, '首件检验') ) form = models.ForeignKey('mtm.recordform', verbose_name='所用表格', on_delete=models.CASCADE) type = models.PositiveSmallIntegerField(choices=type_choice, default=TEST_PROCESS) diff --git a/hb_server/apps/qm/views.py b/hb_server/apps/qm/views.py index 1151491..d46d6ce 100644 --- a/hb_server/apps/qm/views.py +++ b/hb_server/apps/qm/views.py @@ -93,7 +93,8 @@ class TestRecordViewSet(ListModelMixin, UpdateModelMixin, RetrieveModelMixin, De with transaction.atomic(): obj.is_submited=True obj.save() - WpmService.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算 + if obj.wproduct: + WpmService.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算 return Response() # def create(self, request, *args, **kwargs): diff --git a/hb_server/apps/wf/views.py b/hb_server/apps/wf/views.py index 3040daa..f223264 100644 --- a/hb_server/apps/wf/views.py +++ b/hb_server/apps/wf/views.py @@ -90,7 +90,7 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet): class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'get':'*', 'post':'workflow_update', - 'put':'workflow_update', 'delete':'workflow_delete'} + 'put':'workflow_update', 'delete':'workflow_update'} queryset = State.objects.all() serializer_class = StateSerializer search_fields = ['name'] @@ -99,7 +99,7 @@ class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, Destr class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'get':'*', 'post':'workflow_update', - 'put':'workflow_update', 'delete':'workflow_delete'} + 'put':'workflow_update', 'delete':'workflow_update'} queryset = Transition.objects.all() serializer_class = TransitionSerializer search_fields = ['name'] @@ -108,7 +108,7 @@ class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): perms_map = {'get':'*', 'post':'workflow_update', - 'put':'workflow_update', 'delete':'workflow_delete'} + 'put':'workflow_update', 'delete':'workflow_update'} queryset = CustomField.objects.all() serializer_class = CustomFieldSerializer search_fields = ['field_name'] diff --git a/hb_server/utils/response.py b/hb_server/utils/response.py index 776757f..2f60656 100644 --- a/hb_server/utils/response.py +++ b/hb_server/utils/response.py @@ -62,8 +62,8 @@ class FitJSONRenderer(JSONRenderer): if isinstance(data, list): data = data[0] - response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert + response_body.msg = prefix + str(data) # 取一部分放入msg,方便前端alert else: response_body.data = data renderer_context.get("response").status_code = 200 # 统一成200响应, 可用body里code区分业务异常 - return super(FitJSONRenderer, self).render(response_body.dict, accepted_media_type, renderer_context) + return super(FitJSONRenderer, self).render(response_body.dict, accepted_media_type, renderer_context) \ No newline at end of file