diff --git a/hb_server/apps/inm/serializers.py b/hb_server/apps/inm/serializers.py index d404ecc..5bca091 100644 --- a/hb_server/apps/inm/serializers.py +++ b/hb_server/apps/inm/serializers.py @@ -1,6 +1,7 @@ from rest_framework import serializers from apps.inm.models import FIFO, FIFOItem, IProduct, MaterialBatch, WareHouse,Inventory +from apps.qm.models import TestRecord, TestRecordItem from apps.system.serializers import UserSimpleSerializer from apps.mtm.serializers import MaterialSimpleSerializer @@ -119,3 +120,16 @@ class FIFOInPurSerializer(serializers.ModelSerializer): FIFOItem.objects.create(**i) return obj + + +class InmTestRecordItemCreateSerializer(serializers.ModelSerializer): + class Meta: + model = TestRecordItem + fields = ['form_field', 'field_value', 'is_testok'] + +class InmTestRecordCreateSerializer(serializers.ModelSerializer): + record_data = InmTestRecordItemCreateSerializer(many=True) + fifo_item = serializers.PrimaryKeyRelatedField(queryset=FIFOItem.objects.all(), required=True) + class Meta: + model = TestRecord + fields = ['form', 'record_data', 'is_testok', 'fifo_item'] \ No newline at end of file diff --git a/hb_server/apps/inm/urls.py b/hb_server/apps/inm/urls.py index f5d68de..908d9b6 100644 --- a/hb_server/apps/inm/urls.py +++ b/hb_server/apps/inm/urls.py @@ -9,7 +9,7 @@ router.register('warehouse', WarehouseViewSet, basename='warehouse') router.register('inventory', InventoryViewSet, basename='inventory') router.register('materialbatch', MaterialBatchViewSet, basename='materialbatch') router.register('fifo', FIFOViewSet, basename='fifo'), -router.register('fifodetail', FIFOItemViewSet, basename='fifodetail') +router.register('fifoitem', FIFOItemViewSet, basename='fifoitem') urlpatterns = [ path('', include(router.urls)), ] diff --git a/hb_server/apps/inm/views.py b/hb_server/apps/inm/views.py index cfd511e..244118e 100644 --- a/hb_server/apps/inm/views.py +++ b/hb_server/apps/inm/views.py @@ -6,8 +6,9 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet from apps.inm.filters import MbFilterSet from apps.inm.models import FIFO, FIFOItem, MaterialBatch, WareHouse,Inventory -from apps.inm.serializers import FIFOItemSerializer, FIFOInPurSerializer, FIFOListSerializer, MaterialBatchQuerySerializer, MaterialBatchSerializer, WareHouseSerializer, WareHouseCreateUpdateSerializer,InventorySerializer +from apps.inm.serializers import FIFOItemSerializer, FIFOInPurSerializer, FIFOListSerializer, InmTestRecordCreateSerializer, MaterialBatchQuerySerializer, MaterialBatchSerializer, WareHouseSerializer, WareHouseCreateUpdateSerializer,InventorySerializer from apps.inm.signals import update_inm +from apps.qm.models import TestRecordItem from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action from rest_framework.response import Response @@ -81,6 +82,40 @@ class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): raise APIException('该出入库记录已通过审核, 无法删除') return super().perform_destroy(instance) + @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=InmTestRecordCreateSerializer) + def test(self, request, pk=None): + """ + 检测 + """ + serializer = InmTestRecordCreateSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + record_data = vdata.pop('record_data') + if 'is_testok' not in vdata: + raise APIException('未填写检测结论') + with transaction.atomic(): + obj = serializer.save(create_by = self.request.user) + tris = [] + for m in record_data: # 保存记录详情 + form_field = m['form_field'] + m['field_name'] = form_field.field_name + m['field_key'] = form_field.field_key + m['field_type'] = form_field.field_type + m['field_value'] = m['field_value'] + m['sort'] = form_field.sort + m['need_judge'] = form_field.need_judge + m['is_testok'] = m['is_testok'] if 'is_testok' in m else True + m['test_record'] = obj + tris.append(TestRecordItem(**m)) + TestRecordItem.objects.bulk_create(tris) + + # 如果检测合格 + if obj.fifo_item: + obj.fifo_item.is_testok = True if obj.is_testok else False + obj.fifo_item.is_tested = True + obj.fifo_item.save() + return Response() + class FIFOViewSet(ListModelMixin, GenericViewSet): """ 出入库记录 diff --git a/hb_server/apps/qm/migrations/0008_auto_20211111_1405.py b/hb_server/apps/qm/migrations/0008_auto_20211111_1405.py new file mode 100644 index 0000000..d5b3f92 --- /dev/null +++ b/hb_server/apps/qm/migrations/0008_auto_20211111_1405.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.6 on 2021-11-11 06:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0029_step_type'), + ('wpm', '0012_auto_20211111_1056'), + ('qm', '0007_alter_testrecorditem_field_type'), + ] + + operations = [ + migrations.AddField( + model_name='testrecord', + name='m_state', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='关联的物料状态'), + ), + migrations.AddField( + model_name='testrecord', + name='remark', + field=models.TextField(default='', verbose_name='备注'), + ), + migrations.AddField( + model_name='testrecord', + name='wproduct', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.wproduct', verbose_name='关联的动态产品'), + ), + ] diff --git a/hb_server/apps/qm/models.py b/hb_server/apps/qm/models.py index 0174d40..35d77d8 100644 --- a/hb_server/apps/qm/models.py +++ b/hb_server/apps/qm/models.py @@ -49,7 +49,10 @@ class TestRecord(CommonAModel): """ form = models.ForeignKey('mtm.recordform', verbose_name='所用表格', on_delete=models.CASCADE) is_testok = models.BooleanField('是否合格', default=True) + wproduct = models.ForeignKey('wpm.wproduct', verbose_name='关联的动态产品', on_delete=models.CASCADE, null=True, blank=True) + m_state = models.ForeignKey('mtm.material', verbose_name='关联的物料状态', on_delete=models.CASCADE, null=True, blank=True) fifo_item = models.ForeignKey('inm.fifoitem', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True) + remark = models.TextField('备注', default='') class TestRecordItem(BaseModel): diff --git a/hb_server/apps/qm/views.py b/hb_server/apps/qm/views.py index cc59ab8..bfecdc1 100644 --- a/hb_server/apps/qm/views.py +++ b/hb_server/apps/qm/views.py @@ -1,7 +1,8 @@ +from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from apps.qm.serializers import StandardCreateUpdateSerializer, StandardSerializer, TestItemCreateUpdateSerializer, TestItemSerializer, TestRecordCreateSerializer, TestRecordDetailSerializer, TestRecordListSerializer from apps.qm.models import Standard, TestItem, TestRecord, TestRecordItem from django.shortcuts import render -from rest_framework.viewsets import ModelViewSet +from rest_framework.viewsets import GenericViewSet, ModelViewSet from apps.system.mixins import CreateUpdateModelAMixin from rest_framework.exceptions import APIException from rest_framework.response import Response @@ -42,7 +43,7 @@ class TestItemViewSet(CreateUpdateModelAMixin, ModelViewSet): return TestItemCreateUpdateSerializer return TestItemSerializer -class TestRecordViewSet(ModelViewSet): +class TestRecordViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): """ 检测记录 """ @@ -52,41 +53,39 @@ class TestRecordViewSet(ModelViewSet): ordering = ['-id'] def get_serializer_class(self): - if self.action == 'create': - return TestRecordCreateSerializer - elif self.action == 'list': + if self.action == 'list': return TestRecordListSerializer elif self.action == 'retrieve': return TestRecordDetailSerializer return super().get_serializer_class() - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) - vdata = serializer.validated_data - record_data = vdata.pop('record_data') - if 'is_testok' not in vdata: - raise APIException('未填写检测结论') - with transaction.atomic(): - obj = serializer.save(create_by = self.request.user) - tris = [] - for m in record_data: # 保存记录详情 - form_field = m['form_field'] - m['field_name'] = form_field.field_name - m['field_key'] = form_field.field_key - m['field_type'] = form_field.field_type - m['field_value'] = m['field_value'] - m['sort'] = form_field.sort - m['need_judge'] = form_field.need_judge - m['is_testok'] = m['is_testok'] if 'is_testok' in m else True - m['test_record'] = obj - tris.append(TestRecordItem(**m)) - TestRecordItem.objects.bulk_create(tris) + # def create(self, request, *args, **kwargs): + # serializer = self.get_serializer(data=request.data) + # serializer.is_valid(raise_exception=True) + # vdata = serializer.validated_data + # record_data = vdata.pop('record_data') + # if 'is_testok' not in vdata: + # raise APIException('未填写检测结论') + # with transaction.atomic(): + # obj = serializer.save(create_by = self.request.user) + # tris = [] + # for m in record_data: # 保存记录详情 + # form_field = m['form_field'] + # m['field_name'] = form_field.field_name + # m['field_key'] = form_field.field_key + # m['field_type'] = form_field.field_type + # m['field_value'] = m['field_value'] + # m['sort'] = form_field.sort + # m['need_judge'] = form_field.need_judge + # m['is_testok'] = m['is_testok'] if 'is_testok' in m else True + # m['test_record'] = obj + # tris.append(TestRecordItem(**m)) + # TestRecordItem.objects.bulk_create(tris) - # 如果检测合格 - if obj.fifo_item: - obj.fifo_item.is_testok = True if obj.is_testok else False - obj.fifo_item.is_tested = True - obj.fifo_item.save() + # # 如果检测合格 + # if obj.fifo_item: + # obj.fifo_item.is_testok = True if obj.is_testok else False + # obj.fifo_item.is_tested = True + # obj.fifo_item.save() - return Response(status=status.HTTP_201_CREATED) \ No newline at end of file + # return Response(status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/hb_server/apps/wpm/migrations/0012_auto_20211111_1056.py b/hb_server/apps/wpm/migrations/0012_auto_20211111_1056.py new file mode 100644 index 0000000..d6dc0e0 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0012_auto_20211111_1056.py @@ -0,0 +1,41 @@ +# Generated by Django 3.2.6 on 2021-11-11 02:56 + +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0029_step_type'), + ('wpm', '0011_alter_operationrecorditem_operation_record'), + ] + + operations = [ + migrations.AddField( + model_name='operation', + name='use_scrap', + field=models.BooleanField(default=False, verbose_name='是否使用的边角料'), + ), + migrations.AddField( + model_name='wproduct', + name='pre_pstate', + field=models.ForeignKey(blank=True, help_text='已执行完的步骤', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='w_pre_pstate', to='mtm.step', verbose_name='已执行到'), + ), + migrations.AlterField( + model_name='operationmaterial', + name='count', + field=models.IntegerField(validators=[django.core.validators.MinValueValidator(0)], verbose_name='消耗或产出数量'), + ), + migrations.AlterField( + model_name='wmaterial', + name='count', + field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)], verbose_name='当前数量'), + ), + migrations.AlterField( + model_name='wproduct', + name='p_state', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='w_ptate', to='mtm.step', verbose_name='所在步骤'), + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index 1b3c8c3..0d8d4b8 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -7,7 +7,7 @@ from apps.system.models import CommonAModel, CommonBModel, Organization, User, D from utils.model import SoftModel, BaseModel from simple_history.models import HistoricalRecords from apps.mtm.models import Material, Process, RecordFormField, Step, RecordForm - +from django.core.validators import MinValueValidator class WMaterial(BaseModel): """ 车间生产物料 @@ -15,7 +15,7 @@ class WMaterial(BaseModel): subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子计划', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='关联物料', on_delete=models.CASCADE) batch = models.CharField('批次号', max_length=100, null=True, blank=True) - count = models.IntegerField('当前数量', default=0) + count = models.IntegerField('当前数量', default=0, validators=[MinValueValidator(0)]) class WProduct(CommonAModel): """ @@ -31,7 +31,8 @@ class WProduct(CommonAModel): ) number = models.CharField('物品编号', unique=True, null=True, blank=True, max_length=50) m_state = models.ForeignKey(Material, verbose_name='所属物料状态', on_delete=models.CASCADE) - p_state = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True) + pre_pstate = models.ForeignKey(Step, verbose_name='已执行到', help_text='已执行完的步骤', null=True, blank=True, on_delete=models.CASCADE, related_name='w_pre_pstate') + p_state = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True, related_name='w_ptate') act_state = models.IntegerField('进行状态', default=0, choices=act_state_choices) is_executed = models.BooleanField('子工序是否已执行', default=False) is_hidden = models.BooleanField('是否隐藏', default=False) @@ -48,11 +49,12 @@ class Operation(CommonAModel): wproducts = models.JSONField('关联产品ID列表', default=list, blank=True) m_state = models.ForeignKey(Material, verbose_name='操作时的物料状态', on_delete=models.CASCADE, null=True, blank=True) p_state = models.ForeignKey(Step, verbose_name='操作步骤', on_delete=models.CASCADE, null=True, blank=True) + use_scrap = models.BooleanField('是否使用的边角料', default=False) remark = models.CharField('操作备注', max_length=200, null=True, blank=True) class OperationMaterial(BaseModel): """ - 车间生产物料消耗产出表 + 生产操作物料消耗产出表 """ type_choices=( (1, '消耗'), @@ -62,7 +64,7 @@ class OperationMaterial(BaseModel): operation = models.ForeignKey(Operation, verbose_name='关联的生产操作', on_delete=models.CASCADE) wmaterial = models.ForeignKey(WMaterial, verbose_name='关联的车间物料', on_delete=models.CASCADE, null=True, blank=True) material = models.ForeignKey(Material, verbose_name='可能产出的副产品', on_delete=models.CASCADE, null=True, blank=True) - count = models.IntegerField('消耗或产出数量') + count = models.IntegerField('消耗或产出数量', validators=[MinValueValidator(0)]) class OperationRecord(CommonAModel): """ diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index 2e88f72..d9862b6 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -1,4 +1,4 @@ -from rest_framework import serializers +from rest_framework import serializers, exceptions from rest_framework.serializers import ModelSerializer from apps.inm.models import FIFO, FIFOItem, MaterialBatch, WareHouse from apps.inm.signals import update_inm @@ -8,6 +8,7 @@ from apps.mtm.serializers import MaterialSimpleSerializer, StepSimpleSerializer from apps.pm.models import SubProductionPlan, SubProductionProgress from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from apps.qm.models import TestRecord, TestRecordItem from apps.system.serializers import UserSimpleSerializer from apps.wpm.models import Operation, WMaterial, WProduct, OperationRecord, OperationRecordItem from django.db import transaction @@ -26,16 +27,16 @@ class PickSerializer(serializers.Serializer): picks = validated_data.pop('picks') sp = validated_data['subproduction_plan'] if sp.state not in [1,2]: - raise serializers.ValidationError('该子计划状态错误') + raise exceptions.ValidationError('该子计划状态错误') if sp.is_picked: - raise serializers.ValidationError('该子计划已领料') - for i in picks: - try: - instance = MaterialBatch.objects.get(material=i['material'], batch=i['batch']) - if instance.count < i['pick_count']: - raise serializers.ValidationError('物料不足') - except: - raise serializers.ValidationError('物料不存在') + raise exceptions.ValidationError('该子计划已领料') + # for i in picks: + # try: + # instance = MaterialBatch.objects.get(material=i['material'], batch=i['batch']) + # if instance.count < i['pick_count']: + # raise exceptions.ValidationError('物料不足') + # except: + # raise exceptions.ValidationError('物料不存在') # 创建出库记录 with transaction.atomic(): validated_data['create_by'] = self.context['request'].user @@ -124,21 +125,21 @@ class OperationInitSerializer(serializers.Serializer): stepIds=[i['id'] for i in subproduction_plan.steps] if step.id not in stepIds: - raise serializers.ValidationError('请选择正确的子工序操作') + raise exceptions.ValidationError('请选择正确的子工序操作') if 'wproducts' in data and data['wproducts']: if step.type == Step.STEP_TYPE_DIV: - raise serializers.ValidationError(_('不可进行此操作')) + raise exceptions.ValidationError(_('不可进行此操作')) for i in data['wproducts']: if i.is_executed: - raise serializers.ValidationError('不可进行操作') + raise exceptions.ValidationError('不可进行操作') if i.subproduction_plan != subproduction_plan: - raise serializers.ValidationError('半成品所属子计划不一致') + raise exceptions.ValidationError('半成品所属子计划不一致') if i.p_state != step: - raise serializers.ValidationError('半成品所属子工序不一致') + raise exceptions.ValidationError('半成品所属子工序不一致') else: if step.type != Step.STEP_TYPE_DIV: - raise serializers.ValidationError(_('请选择半成品进行操作')) + raise exceptions.ValidationError(_('请选择半成品进行操作')) return data @@ -172,6 +173,19 @@ class OperationSubmitSerializer(serializers.Serializer): output = DoOutputSerializer(many=True, required=False) forms = OperationRecordSerializer(many=True, required=False) remark = serializers.CharField(required=False, label='操作备注', allow_blank=True, allow_null=True) + use_scrap = serializers.BooleanField(required=False, default=False) + +class WpmTestRecordItemCreateSerializer(serializers.ModelSerializer): + class Meta: + model = TestRecordItem + fields = ['form_field', 'field_value', 'is_testok'] + +class WpmTestRecordCreateSerializer(serializers.ModelSerializer): + record_data = WpmTestRecordItemCreateSerializer(many=True) + wproduct = serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all(), required=True) + class Meta: + model = TestRecord + fields = ['form', 'record_data', 'is_testok', 'wproduct'] \ No newline at end of file diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index 2cae722..c49e9ba 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -9,14 +9,16 @@ from apps.mtm.models import Material, RecordForm, Step, SubprodctionMaterial from apps.mtm.serializers import RecordFormDetailSerializer from apps.pm.models import SubProductionPlan, SubProductionProgress from apps.pm.serializers import SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer +from apps.qm.models import TestRecordItem from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action from apps.wpm.models import WMaterial, WProduct, Operation, OperationMaterial, OperationRecord, OperationRecordItem -from apps.wpm.serializers import OperationDetailSerializer, OperationListSerializer, PickSerializer, OperationInitSerializer, OperationSubmitSerializer, WMaterialListSerializer, WProductListSerializer +from apps.wpm.serializers import OperationDetailSerializer, OperationListSerializer, PickSerializer, OperationInitSerializer, OperationSubmitSerializer, WMaterialListSerializer, WProductListSerializer, WpmTestRecordCreateSerializer from rest_framework.response import Response from django.db import transaction +from rest_framework import exceptions # Create your views here. class WPlanViewSet(ListModelMixin, GenericViewSet): """ @@ -63,9 +65,40 @@ class WProductViewSet(ListModelMixin, GenericViewSet): ordering_fields = ['id'] ordering = ['id'] - @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=PickSerializer) - def test(): - pass + @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=WpmTestRecordCreateSerializer) + @transaction.atomic + def test(self, request, pk=None): + """ + 检测 + """ + serializer = WpmTestRecordCreateSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + record_data = vdata.pop('record_data') + wproduct = vdata['wproduct'] + if wproduct.act_state != WProduct.WPR_ACT_STATE_TOTEST: + raise exceptions.APIException('该半成品无需检测') + if 'is_testok' not in vdata: + raise exceptions.APIException('未填写检测结论') + + obj = serializer.save(create_by = self.request.user, m_state=vdata['wproduct'].m_state) + tris = [] + for m in record_data: # 保存记录详情 + form_field = m['form_field'] + m['field_name'] = form_field.field_name + m['field_key'] = form_field.field_key + m['field_type'] = form_field.field_type + m['field_value'] = m['field_value'] + m['sort'] = form_field.sort + m['need_judge'] = form_field.need_judge + m['is_testok'] = m['is_testok'] if 'is_testok' in m else True + m['test_record'] = obj + tris.append(TestRecordItem(**m)) + TestRecordItem.objects.bulk_create(tris) + + # 如果检测合格 + + return Response() class OperationViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): """ @@ -151,6 +184,7 @@ class DoFormSubmit(CreateAPIView, GenericAPIView): action_obj.m_state = vdata['wproducts'][0].m_state action_obj.remark = vdata.get('remark', '') # 操作备注 action_obj.create_by = request.user + action_obj.use_scrap = vdata.get('use_scrap', False) action_obj.save() # 保存物料消耗 @@ -207,7 +241,7 @@ class DoFormSubmit(CreateAPIView, GenericAPIView): pindex = stepIds.index(vdata['step'].id) if pindex + 1 < len(stepIds): # 如果不是最后一步 newstep = Step.objects.get(pk=stepIds[pindex+1]) - wproducts.update(p_state=newstep, is_executed=False) + wproducts.update(p_state=newstep, is_executed=False, pre_pstate=vdata['step']) # 特殊情况如果是夹层结合 if vdata['step'].type == Step.STEP_TYPE_COMB: @@ -221,9 +255,10 @@ class DoFormSubmit(CreateAPIView, GenericAPIView): parent = data['wproducts'] ) - else: # 如果是最后一步, 此时需要转序并更新状态为待检测 + else: # 如果是最后一步, 此时需要转序并更新状态为待检测, 此时物料状态需变成当前子计划的主产物状态 newstep = vdata['step'] - wproducts.update(p_state=newstep, is_executed=True, act_state=WProduct.WPR_ACT_STATE_TOTEST) + wproducts.update(p_state=newstep, is_executed=True, + act_state=WProduct.WPR_ACT_STATE_TOTEST, pre_pstate=newstep, m_state=vdata['subproduction_plan'].main_product) # 特殊情况如果是夹层结合 if vdata['step'].type == Step.STEP_TYPE_COMB: