diff --git a/hb_server/apps/inm/views.py b/hb_server/apps/inm/views.py index f8b7f5d..10eb4b5 100644 --- a/hb_server/apps/inm/views.py +++ b/hb_server/apps/inm/views.py @@ -75,6 +75,11 @@ class FIFODetailViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): ordering_fields = ['create_time'] ordering = ['-create_time'] + def perform_destroy(self, instance): + if instance.fifo.is_audited: + raise APIException('该出入库记录已通过审核, 无法删除') + return super().perform_destroy(instance) + class FIFOViewSet(ListModelMixin, GenericViewSet): """ 出入库记录 diff --git a/hb_server/apps/qm/models.py b/hb_server/apps/qm/models.py index 062f5c9..5da4930 100644 --- a/hb_server/apps/qm/models.py +++ b/hb_server/apps/qm/models.py @@ -1,5 +1,8 @@ from django.db import models +from django.db.models.enums import Choices +from apps.mtm.models import RecordForm, RecordFormField from apps.system.models import CommonAModel, File +from utils.model import BaseModel # Create your models here. class Standard(CommonAModel): """ @@ -49,4 +52,17 @@ class TestRecord(CommonAModel): record_data = models.JSONField('记录数据', default=dict, blank=True) is_testok = models.BooleanField('是否合格', default=True) fifo_detail = models.ForeignKey('inm.fifodetail', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True) - \ No newline at end of file + + +class TestRecordDetail(BaseModel): + """ + 记录表格字段值 + """ + form_field = models.ForeignKey(RecordFormField, verbose_name='关联字段', on_delete=models.CASCADE, db_constraint=False) + field_name = models.CharField('字段名', max_length=50) + field_key = models.CharField('字段标识', max_length=50) + field_type = models.IntegerField('字段类型', choices=RecordForm.type_choices) + field_value = models.JSONField('录入值', default=dict, blank=True) + need_judge = models.BooleanField('是否需要判定', default=False) + is_testok = models.BooleanField('是否合格', default=True) + test_record = models.ForeignKey(TestRecord, verbose_name='关联的检测记录', on_delete=models.CASCADE) \ No newline at end of file diff --git a/hb_server/apps/wpm/migrations/0005_auto_20211108_0901.py b/hb_server/apps/wpm/migrations/0005_auto_20211108_0901.py new file mode 100644 index 0000000..174fc1d --- /dev/null +++ b/hb_server/apps/wpm/migrations/0005_auto_20211108_0901.py @@ -0,0 +1,114 @@ +# Generated by Django 3.2.6 on 2021-11-08 01:01 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('mtm', '0028_auto_20211102_1707'), + ('wpm', '0004_auto_20211104_0914'), + ] + + operations = [ + migrations.CreateModel( + name='WProductAction', + 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='删除标记')), + ('wproducts', models.JSONField(blank=True, default=list, verbose_name='关联产品ID列表')), + ('remark', 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='wproductaction_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('m_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='操作时的物料状态')), + ('p_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.step', verbose_name='操作步骤')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wproductaction_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='WProductMaterial', + 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='删除标记')), + ('type', models.IntegerField(choices=[(1, '消耗'), (2, '产出')], default=0, verbose_name='类型')), + ('count', models.IntegerField(verbose_name='消耗或产出数量')), + ('material', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='可能产出的副产品')), + ('wmaterial', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.wmaterial', verbose_name='关联的车间物料')), + ('wproduct_action', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.wproductaction', verbose_name='关联的生产操作')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='WProductRecordDetail', + 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='删除标记')), + ('field_name', models.CharField(max_length=50, verbose_name='字段名')), + ('field_key', models.CharField(max_length=50, verbose_name='字段标识')), + ('field_type', models.IntegerField(choices=[(1, '生产记录'), (2, '检验记录')], verbose_name='字段类型')), + ('field_value', models.JSONField(blank=True, default=dict, verbose_name='录入值')), + ('sort', models.IntegerField(default=1, verbose_name='排序号')), + ('form_field', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='mtm.recordformfield', verbose_name='关联字段')), + ], + options={ + 'abstract': False, + }, + ), + migrations.DeleteModel( + name='WProductFlow', + ), + migrations.RemoveField( + model_name='wproductrecord', + name='data', + ), + migrations.RemoveField( + model_name='wproductrecord', + name='record_form', + ), + migrations.AddField( + model_name='wproduct', + name='is_executed', + field=models.BooleanField(default=False, verbose_name='子工序是否已执行'), + ), + migrations.AddField( + model_name='wproductrecord', + name='form', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='mtm.recordform', verbose_name='所用的生产记录表格'), + preserve_default=False, + ), + migrations.AlterField( + model_name='wproduct', + name='act_state', + field=models.IntegerField(choices=[(1, '生产中'), (2, '待检测'), (3, '已合格')], default=0, verbose_name='进行状态'), + ), + migrations.AlterField( + model_name='wproduct', + name='parent', + field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='wpm.wproduct', verbose_name='上一级'), + ), + migrations.AddField( + model_name='wproductrecorddetail', + name='wproduct_record', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wpm.wproductrecord', verbose_name='关联的生产记录'), + ), + migrations.AddField( + model_name='wproductrecord', + name='wproduct_action', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='wpm.wproductaction', verbose_name='关联的生产操作'), + preserve_default=False, + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index a7e46a1..8566f93 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -6,7 +6,7 @@ from apps.pm.models import ProductionPlan, SubProductionPlan from apps.system.models import CommonAModel, CommonBModel, Organization, User, Dict, File from utils.model import SoftModel, BaseModel from simple_history.models import HistoricalRecords -from apps.mtm.models import Material, Process, Step, RecordForm +from apps.mtm.models import Material, Process, RecordFormField, Step, RecordForm class WMaterial(BaseModel): """ @@ -44,7 +44,7 @@ class WProductAction(CommonAModel): 生产操作 """ wproducts = models.JSONField('关联产品ID列表', default=list, blank=True) - m_state = models.ForeignKey(Material, verbose_name='操作时的物料状态', on_delete=models.CASCADE) + 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) remark = models.CharField('操作备注', max_length=200, null=True, blank=True) @@ -58,7 +58,8 @@ class WProductMaterial(BaseModel): ) type = models.IntegerField('类型', default=0, choices=type_choices) wproduct_action = models.ForeignKey(WProductAction, verbose_name='关联的生产操作', on_delete=models.CASCADE) - material = models.ForeignKey(Material, 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('消耗或产出数量') class WProductRecord(CommonAModel): @@ -66,5 +67,18 @@ class WProductRecord(CommonAModel): 记录表格 """ form = models.ForeignKey(RecordForm, verbose_name='所用的生产记录表格', on_delete=models.CASCADE) - record_data = models.JSONField('记录的数据', default=dict, blank=True) wproduct_action = models.ForeignKey(WProductAction, verbose_name='关联的生产操作', on_delete=models.CASCADE) + + +class WProductRecordDetail(BaseModel): + """ + 记录表格字段值 + """ + form_field = models.ForeignKey(RecordFormField, verbose_name='关联字段', on_delete=models.CASCADE, db_constraint=False) + field_name = models.CharField('字段名', max_length=50) + field_key = models.CharField('字段标识', max_length=50) + field_type = models.IntegerField('字段类型', choices=RecordForm.type_choices) + field_value = models.JSONField('录入值', default=dict, blank=True) + sort = models.IntegerField('排序号', default=1) + wproduct_record = models.ForeignKey(WProductRecord, verbose_name='关联的生产记录', on_delete=models.CASCADE) + diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index e842a4d..23afaa2 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -2,13 +2,13 @@ from rest_framework import serializers from rest_framework.serializers import ModelSerializer from apps.inm.models import FIFO, FIFODetail, MaterialBatch, WareHouse from apps.inm.signals import update_inm -from apps.mtm.models import Material, Step +from apps.mtm.models import Material, RecordForm, Step from apps.mtm.serializers import MaterialSimpleSerializer from apps.pm.models import SubProductionPlan, SubProductionProgress from django.utils import timezone -from apps.wpm.models import WMaterial, WProduct +from apps.wpm.models import WMaterial, WProduct, WProductRecord class PickDetailSerializer(serializers.Serializer): material = serializers.PrimaryKeyRelatedField(queryset=Material.objects.all(), label="物料ID") @@ -81,8 +81,41 @@ class WMaterialListSerializer(serializers.ModelSerializer): fields = '__all__' -class DoFormInitSerializer(serializers.Serializer): +class WActionInitSerializer(serializers.Serializer): step = serializers.PrimaryKeyRelatedField(queryset=Step.objects.all(), label="子工序ID") - subproduction_plan = serializers.PrimaryKeyRelatedField(queryset=SubProductionPlan.objects.all(), label="子计划ID", required=False) + subproduction_plan = serializers.PrimaryKeyRelatedField(queryset=SubProductionPlan.objects.all(), label="子计划ID") wproducts = serializers.ListField(child= serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all()), label="半成品ID列表", required=False) + +class DoInputSerializer(serializers.Serializer): + id = serializers.PrimaryKeyRelatedField(queryset=WMaterial.objects.all(), label='车间物料ID') + count_input = serializers.IntegerField(min_value=0, label='消耗数量') + +class DoOutputSerializer(serializers.Serializer): + material = serializers.PrimaryKeyRelatedField(queryset=Material.objects.all(), label='物料ID') + count_output = serializers.IntegerField(min_value=0, label='产出数量') + +class WProductRecordDetailSerializer(serializers.ModelSerializer): + class Meta: + model = WProductRecord + fields = ['form_field', 'field_value'] + +class WProductRecordSerializer(serializers.ModelSerializer): + record_data = WProductRecordDetailSerializer(many=True) + class Meta: + model = WProductRecord + fields = ['form', 'record_data'] + + + +class WActionSubmitSerializer(serializers.Serializer): + step = serializers.PrimaryKeyRelatedField(queryset=Step.objects.all(), label="子工序ID") + subproduction_plan = serializers.PrimaryKeyRelatedField(queryset=SubProductionPlan.objects.all(), label="子计划ID") + wproducts = serializers.ListField(child= + serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all()), label="半成品ID列表", required=False) + input = DoInputSerializer(many=True, required=False) + output = DoOutputSerializer(many=True, required=False) + forms = WProductRecordSerializer(many=True, required=False) + + + \ No newline at end of file diff --git a/hb_server/apps/wpm/urls.py b/hb_server/apps/wpm/urls.py index 1ce7fa3..89604b5 100644 --- a/hb_server/apps/wpm/urls.py +++ b/hb_server/apps/wpm/urls.py @@ -3,13 +3,14 @@ from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.wpm.views import DoFormInit, WMaterialViewSet, WPlanViewSet +from apps.wpm.views import DoFormInit, DoFormSubmit, WMaterialViewSet, WPlanViewSet router = DefaultRouter() router.register('wmaterial', WMaterialViewSet, basename='wmaterial') router.register('subplan', WPlanViewSet, basename='wplan') urlpatterns = [ path('do/init/', DoFormInit.as_view()), + path('do/submit/', DoFormSubmit.as_view()), path('', include(router.urls)), ] diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index ac6bba3..bf4fe1b 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -1,19 +1,20 @@ from django.shortcuts import render from rest_framework.generics import CreateAPIView, GenericAPIView from rest_framework.mixins import ListModelMixin +from rest_framework.utils import serializer_helpers from rest_framework.utils.field_mapping import get_relation_kwargs from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet, ModelViewSet -from apps.mtm.models import RecordForm +from apps.mtm.models import Material, RecordForm, Step from apps.mtm.serializers import RecordFormDetailSerializer from apps.pm.models import SubProductionPlan, SubProductionProgress from apps.pm.serializers import SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action -from apps.wpm.models import WMaterial +from apps.wpm.models import WMaterial, WProduct, WProductAction, WProductMaterial, WProductRecord, WProductRecordDetail -from apps.wpm.serializers import DoFormInitSerializer, PickSerializer, WMaterialListSerializer +from apps.wpm.serializers import PickSerializer, WActionInitSerializer, WActionSubmitSerializer, WMaterialListSerializer from rest_framework.response import Response # Create your views here. class WPlanViewSet(ListModelMixin, GenericViewSet): @@ -49,29 +50,42 @@ class WMaterialViewSet(CreateUpdateModelAMixin, ListModelMixin, GenericViewSet): serializer.save() return Response() -class DoFormInit(APIView): +class DoFormInit(CreateAPIView, GenericAPIView): perms_map={'*':'*'} - + serializer_class=WActionInitSerializer def post(self, request, format=None): """ 调用操作表单 """ - serializer = DoFormInitSerializer(data=request.data) + data = request.data + serializer = WActionInitSerializer(data=data) serializer.is_valid(raise_exception=True) vdata = serializer.validated_data ret = {} + ret['step'] = data['step'] + ret['subproduction_plan'] = data['subproduction_plan'] + if 'wproducts' in data and data['wproducts']: + ret['wproducts'] = data['wproducts'] + else: + ret['wproducts'] = [] # 调出该子计划现有物料 ret['input'] = list(WMaterial.objects.filter(subproduction_plan=vdata['subproduction_plan'])\ - .values('material', 'material__name', 'count')) + .values('id', 'material', 'material__name', 'count', 'material__number', 'batch')) for i in ret['input']: i['count_input'] = 0 # 需要输出的物料 # 如果传入半成品列表就不需要 - if not 'wproducts' in vdata: - ret['output'] = list(SubProductionProgress.objects.filter(subproduction_plan=vdata['subproduction_plan'], type=2)\ - .values('material', 'material__name')) - for i in ret['output']: - i['count_output']=0 + if ret['wproducts']: + # 排除wproduct列表 + # mids = WProduct.objects.filter(pk__in=data['wproducts']).values_list('m_state', flat=True) + o_objs = SubProductionProgress.objects.filter( + subproduction_plan=vdata['subproduction_plan'], type=2).exclude(is_main=True) + else: + o_objs = SubProductionProgress.objects.filter( + subproduction_plan=vdata['subproduction_plan'], type=2) + ret['output'] = list(o_objs.values('material', 'material__name', 'material__number')) + for i in ret['output']: + i['count_output']=0 ret['forms'] = [] forms = RecordForm.objects.filter(step=vdata['step'], type=1) if forms.exists(): @@ -79,8 +93,92 @@ class DoFormInit(APIView): return Response(ret) -class DoFormSubmit(APIView): +class DoFormSubmit(CreateAPIView, GenericAPIView): + perms_map={'*':'*'} + serializer_class = WActionSubmitSerializer def post(self, request, format=None): """ 提交操作表单 - """ \ No newline at end of file + """ + data = request.data + serializer = WActionInitSerializer(data=data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data #校验之后的数据 + # 创建一个生产操作记录 + action_obj = WProductAction() + action_obj.p_state = vdata['step'] + if 'wproducts' in data and data['wproducts']: + action_obj.wproducts = data['wproducts'] + action_obj.m_state = vdata[0].m_state + action_obj.remark = data['remark'] # 操作备注 + action_obj.create_by = request.user + action_obj.save() + + # 保存物料消耗 + for i in vdata['input']: + if i['count_input']: #如果有消耗 + i_wmat = i['id'] + WProductMaterial.objects.create(type=1, wproduct_action=action_obj, + wmaterial= i_wmat, count=i['count_input']) + # 更新车间物料 + i_wmat.count = i_wmat.count- i['count_input'] + i_wmat.save() + # 更新子计划物料消耗情况 + sp = SubProductionProgress.objects.get(subproduction_plan=i_wmat.subproduction_plan, + material=i_wmat.material) + sp.count_real = sp.count_real + i['count_input'] + sp.save() + + # 物料产出 + if 'output' in data and data['output']: + for i in vdata['output']: # 已经序列化好的数据 + ma = i['material'] + if ma.is_main: + # 计划开始, 第一步切割创建动态产品 + wpr = dict(m_state=ma, p_state=vdata['step'], + act_state=1, is_executed=True, remark='', + subproduction_plan=vdata['subproduction_plan'], + production_plan=vdata['subproduction_plan'].production_plan) + for x in range(i['count_output']): + WProduct.objects.create(**wpr) + else: + # 更新操作消耗物料表 + WProductMaterial.objects.create(type=2, wproduct_action=action_obj, + material= ma, count=i['count_output']) + # 更新车间物料表 + ins, _ = WMaterial.objects.get_or_create(subproduction_plan=vdata['subproduction_plan'], + material=ma) + ins.count = ins.count + i['count_output'] + ins.save() + # 更新子计划进度表 + sp = SubProductionProgress.objects.get(subproduction_plan=vdata['subproduction_plan'], + material=ma) + sp.count_real = sp.count_real + i['count_input'] + sp.save() + + # 更新主产出 + if 'wproducts' in data and data['wproducts']: + wproducts = WProduct.objects.filter(pk__in=data['wproducts']) + wproducts.update(p_state=vdata['step'], is_executed=True) + + # 保存自定义表单结果 + for i in vdata['forms']: + wr = WProductRecord() + wr.form = i['form'] + wr.create_by = request.user + wr.wproduct_action = action_obj + wr.save() + wrds = [] + for m in i['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'] = form_field.field_value + m['sort'] = form_field.sort + m['wproduct_record'] = wr + wrds.append(WProductRecordDetail(**m)) + WProductRecordDetail.objects.bulk_create(wrds) + return Response() + + \ No newline at end of file