diff --git a/hb_client/src/components/customForm/index.vue b/hb_client/src/components/customForm/index.vue index 961af55..c809024 100644 --- a/hb_client/src/components/customForm/index.vue +++ b/hb_client/src/components/customForm/index.vue @@ -9,7 +9,7 @@ -1||rule.indexOf('&&')>-1){ - let tam = '', arr = []; - if(rule.indexOf('||')>-1){ - arr = rule.split('||'); - }else{ - arr = rule.split('&&'); - } - for (let i = 0;i-1||rule.indexOf('&&')>-1){ + let tam = '', arr = []; + if(rule.indexOf('||')>-1){ + arr = rule.split('||'); + }else{ + arr = rule.split('&&'); + } + for (let i = 0;i-1){ - if( tam.indexOf('true')>-1){ - rea = true; - }else{ - rea = false; - } }else{ - if( tam.indexOf('false')>-1){ - rea = false; - }else{ - rea = true; - } + tam += 'false'; } - if(rea){ - that.formData[index].is_hidden = false; - }else{ - that.formData[index].is_hidden = true; - that.checkForm[field_key] = null; - } - return rea; - }else{ - let temp =rule.match(reg)[0]; - let key = temp.replace(/\{|\}/g, '');//qipao - let a = rule.replace(key, 'yy'); - a = a.replace(key, 'yy'); - let yy = "'"+that.checkForm[key]+"'"; - if(eval(eval(a))){ - that.formData[index].is_hidden = false; - }else{ - that.formData[index].is_hidden = true; - that.checkForm[field_key] = null; - } - return eval(eval(a)); } + let rea = true; + if(rule.indexOf('||')>-1){ + if( tam.indexOf('true')>-1){ + rea = true; + }else{ + rea = false; + } + }else{ + if( tam.indexOf('false')>-1){ + rea = false; + }else{ + rea = true; + } + } + if(rea){ + that.formData[index].is_hidden = false; + }else{ + that.formData[index].is_hidden = true; + that.checkForm[field_key] = null; + } + return rea; }else{ - that.formData[index].is_hidden = true; - that.checkForm[field_key] = null; - return false; + let temp =rule.match(reg)[0]; + let key = temp.replace(/\{|\}/g, '');//qipao + let a = rule.replace(key, 'yy'); + a = a.replace(key, 'yy'); + let yy = "'"+that.checkForm[key]+"'"; + if(eval(eval(a))){ + that.formData[index].is_hidden = false; + }else{ + that.formData[index].is_hidden = true; + that.checkForm[field_key] = null; + } + return eval(eval(a)); } }else{ that.formData[index].is_hidden = false; diff --git a/hb_client/src/components/customForm/review.vue b/hb_client/src/components/customForm/review.vue index 91cc206..7f4d601 100644 --- a/hb_client/src/components/customForm/review.vue +++ b/hb_client/src/components/customForm/review.vue @@ -9,7 +9,7 @@ -1||rule.indexOf('&&')>-1){ - let tam = '', arr = []; - if(rule.indexOf('||')>-1){ - arr = rule.split('||'); - }else{ - arr = rule.split('&&'); - } - for (let i = 0;i-1||rule.indexOf('&&')>-1){ + let tam = '', arr = []; + if(rule.indexOf('||')>-1){ + arr = rule.split('||'); + }else{ + arr = rule.split('&&'); + } + for (let i = 0;i-1){ - if( tam.indexOf('true')>-1){ - rea = true; - }else{ - rea = false; - } }else{ - if( tam.indexOf('false')>-1){ - rea = false; - }else{ - rea = true; - } + tam += 'false'; } - if(rea){ - that.formData[index].is_hidden = false; - }else{ - that.formData[index].is_hidden = true; - that.checkForm[field_key] = null; - } - return rea; - }else{ - let temp =rule.match(reg)[0]; - let key = temp.replace(/\{|\}/g, '');//qipao - let a = rule.replace(key, 'yy'); - a = a.replace(key, 'yy'); - let yy = "'"+that.checkForm[key]+"'"; - if(eval(eval(a))){ - that.formData[index].is_hidden = false; - }else{ - that.formData[index].is_hidden = true; - that.checkForm[field_key] = null; - } - return eval(eval(a)); } + let rea = true; + if(rule.indexOf('||')>-1){ + if( tam.indexOf('true')>-1){ + rea = true; + }else{ + rea = false; + } + }else{ + if( tam.indexOf('false')>-1){ + rea = false; + }else{ + rea = true; + } + } + if(rea){ + that.formData[index].is_hidden = false; + }else{ + that.formData[index].is_hidden = true; + that.checkForm[field_key] = null; + } + return rea; }else{ - that.formData[index].is_hidden = true; - that.checkForm[field_key] = null; - return false; + let temp =rule.match(reg)[0]; + let key = temp.replace(/\{|\}/g, '');//qipao + let a = rule.replace(key, 'yy'); + a = a.replace(key, 'yy'); + let yy = "'"+that.checkForm[key]+"'"; + if(eval(eval(a))){ + that.formData[index].is_hidden = false; + }else{ + that.formData[index].is_hidden = true; + that.checkForm[field_key] = null; + } + return eval(eval(a)); } }else{ that.formData[index].is_hidden = false; diff --git a/hb_client/src/views/statistics/materialStatistics.vue b/hb_client/src/views/statistics/materialStatistics.vue index 80a9fc9..aeadea4 100644 --- a/hb_client/src/views/statistics/materialStatistics.vue +++ b/hb_client/src/views/statistics/materialStatistics.vue @@ -9,14 +9,21 @@
废料原因统计
-
+ +
@@ -54,9 +61,9 @@
采购物料表
@@ -67,9 +74,9 @@
采购供应商表
@@ -109,9 +116,9 @@
废料来源
@@ -157,17 +164,21 @@ }, data() { return { + chartId0: 'chart0', chartId1: 'chart1', chartId2: 'chart2', chartId3: 'chart3', barChart: null, barChart1: null, pieOptions: {}, - barOptions: {}, + barOptions1: {}, + barOptions21: {}, + barOptions22: {}, + barOptions3: {}, activeName: '废料统计', chartsName: "chartsName", + domHeight:'300px', cdata: { - xData: ["气泡", "划痕"], seriesData: [ {value: 10, name: "气泡"}, {value: 5, name: "破点"}, @@ -175,12 +186,22 @@ {value: 6, name: "其他"}, ] }, - chartData: { + chartData1: { xAxisData: ["气泡", "破点", "划伤", "其他"], - seriesData: [10, 5, 19, 6], + seriesData: [30, 25, 35, 10], + }, + chartData21: { + xAxisData: ["物料1", "物料2", "物料3", "物料4", "物料5", "物料6", "物料7"], + seriesData: [800, 950, 960, 906, 600, 800, 900], + }, + chartData22: { + xAxisData: ["供应商1", "供应商2", "供应商3", "供应商4", "供应商5", "供应商6", "供应商7"], + seriesData: [5, 20, 15, 25,10, 10,15], + }, + chartData3: { + xAxisData: ["供应商1", "供应商2", "供应商3", "供应商4", "供应商5", "供应商6", "供应商7"], + seriesData:[2, 2, 4, 5,1, 0,1], }, - xAxisbar: ["供应商1", "供应商2", "供应商3", "供应商4", "供应商5", "供应商6", "供应商7"], - barData: [80, 95, 96, 96, 96, 98, 99], list: [ {id: 1, name: 'HIehd91', card: '3331', sco: 'REF-31', num: 2, reason: "气泡"}, {id: 2, name: 'HIehd92', card: '3332', sco: 'REF-32', num: 1, reason: "划痕"}, @@ -233,87 +254,22 @@ immediate: true, deep: true }, + created(){ + let hei = document.getElementsByClassName('app-main')[0].clientHeight; + this.domHeight =( hei- 20)/2+'px'; + }, methods: { - drawChart() { - let that = this; - this.barChart = this.$echarts.init(document.getElementById('barChart')); - this.barChart.setOption({ - grid: { - top: '10%', - left: '3%', - right: '5%', - bottom: '1%', - containLabel: true - }, - tooltip: { - trigger: 'item', - formatter: function (params) { - let color = params.color;//图例颜色 - let htmlStr = '
'; - htmlStr += params.seriesName + '
'; - htmlStr += ''; - htmlStr += params.name + ':' + params.value + '件'; - htmlStr += '
'; - return htmlStr; - } - }, - xAxis: { - axisTick: { - show: false - }, - splitLine: { - show: false, //去掉X轴分割线 - }, - data: that.chartData.xAxisData, - }, - yAxis: { - axisLine: { - show: true,//y轴线 - }, - axisTick: { - show: false//Y轴刻度线 - }, - axisLabel: { - color: '#333333'//Y轴文本颜色 - }, - splitLine: { - show: true, //Y轴分割线 - lineStyle: { - color: '#dddddd'//Y轴分割线颜色 - } - }, - }, - series: [{ - name: '废料统计', - type: 'bar', - barWidth: 20, - data: that.chartData.seriesData, - label: { - show: true, //开启显示 - position: 'top', //在上方显示 - formatter: '{c}',//显示百分号 - textStyle: { //数值样式 - color: 'black',//字体颜色 - fontSize: 10//字体大小 - } - }, - itemStyle: { - color: '#409EFF' - }, - }] - }); - }, - handleClick(tab) { + handleClick() { // console.log(tab); // debugger; }, }, mounted() { - this.drawChart(); - this.barOptions = { + let that = this; + this.barOptions1 = { grid: { top: '10%', - left: '3%', + left: '5%', right: '5%', bottom: '1%', containLabel: true @@ -323,9 +279,9 @@ formatter: function (params) { let color = params.color;//图例颜色 let htmlStr = '
'; - htmlStr += params.name + '
'; + htmlStr += params.seriesName + '
'; htmlStr += ''; - htmlStr += params.seriesName + ':' + params.value + '%'; + htmlStr += params.name + ':' + params.value + '件'; htmlStr += '
'; return htmlStr; } @@ -337,7 +293,7 @@ splitLine: { show: false, //去掉X轴分割线 }, - data: this.xAxisbar, + data: that.chartData1.xAxisData, }, yAxis: { axisLine: { @@ -357,10 +313,10 @@ }, }, series: [{ - name: '成品率', + name: '废料统计', type: 'bar', barWidth: 20, - data: this.barData, + data: that.chartData1.seriesData, label: { show: true, //开启显示 position: 'top', //在上方显示 @@ -374,7 +330,202 @@ color: '#409EFF' }, }] - } + }; + this.barOptions21 = { + grid: { + top: '10%', + left: '5%', + right: '5%', + bottom: '1%', + containLabel: true + }, + tooltip: { + trigger: 'item', + formatter: function (params) { + let color = params.color;//图例颜色 + let htmlStr = '
'; + htmlStr += '物料采购' + '
'; + htmlStr += ''; + htmlStr += params.name + ':' + params.value + '件'; + htmlStr += '
'; + return htmlStr; + } + }, + xAxis: { + axisTick: { + show: false + }, + splitLine: { + show: false, //去掉X轴分割线 + }, + data: this.chartData21.xAxisData, + }, + yAxis: { + axisLine: { + show: true,//y轴线 + }, + axisTick: { + show: false//Y轴刻度线 + }, + axisLabel: { + color: '#333333'//Y轴文本颜色 + }, + splitLine: { + show: true, //Y轴分割线 + lineStyle: { + color: '#dddddd'//Y轴分割线颜色 + } + }, + }, + series: [{ + name: '物料', + type: 'bar', + barWidth: 20, + data: this.chartData21.seriesData, + label: { + show: true, //开启显示 + position: 'top', //在上方显示 + formatter: '{c}',//显示百分号 + textStyle: { //数值样式 + color: 'black',//字体颜色 + fontSize: 10//字体大小 + } + }, + itemStyle: { + color: '#409EFF' + }, + }] + }; + this.barOptions22 = { + grid: { + top: '10%', + left: '5%', + right: '5%', + bottom: '1%', + containLabel: true + }, + tooltip: { + trigger: 'item', + formatter: function (params) { + let color = params.color;//图例颜色 + let htmlStr = '
'; + htmlStr += params.seriesName + '
'; + htmlStr += ''; + htmlStr += params.name + ':' + params.value + '%'; + htmlStr += '
'; + return htmlStr; + } + }, + xAxis: { + axisTick: { + show: false + }, + splitLine: { + show: false, //去掉X轴分割线 + }, + data: this.chartData22.xAxisData, + }, + yAxis: { + axisLine: { + show: true,//y轴线 + }, + axisTick: { + show: false//Y轴刻度线 + }, + axisLabel: { + color: '#333333'//Y轴文本颜色 + }, + splitLine: { + show: true, //Y轴分割线 + lineStyle: { + color: '#dddddd'//Y轴分割线颜色 + } + }, + }, + series: [{ + name: '采购占比', + type: 'bar', + barWidth: 20, + data: this.chartData22.seriesData, + label: { + show: true, //开启显示 + position: 'top', //在上方显示 + formatter: '{c}',//显示百分号 + textStyle: { //数值样式 + color: 'black',//字体颜色 + fontSize: 10//字体大小 + } + }, + itemStyle: { + color: '#409EFF' + }, + }] + }; + this.barOptions3 = { + grid: { + top: '10%', + left: '5%', + right: '5%', + bottom: '1%', + containLabel: true + }, + tooltip: { + trigger: 'item', + formatter: function (params) { + let color = params.color;//图例颜色 + let htmlStr = '
'; + htmlStr += params.seriesName + '
'; + htmlStr += ''; + htmlStr += params.name + ':' + params.value + '件'; + htmlStr += '
'; + return htmlStr; + } + }, + xAxis: { + axisTick: { + show: false + }, + splitLine: { + show: false, //去掉X轴分割线 + }, + data: this.chartData3.xAxisData, + }, + yAxis: { + axisLine: { + show: true,//y轴线 + }, + axisTick: { + show: false//Y轴刻度线 + }, + axisLabel: { + color: '#333333'//Y轴文本颜色 + }, + splitLine: { + show: true, //Y轴分割线 + lineStyle: { + color: '#dddddd'//Y轴分割线颜色 + } + }, + }, + series: [{ + name: '废料来源', + type: 'bar', + barWidth: 20, + data: this.chartData3.seriesData, + label: { + show: true, //开启显示 + position: 'top', //在上方显示 + formatter: '{c}',//显示百分号 + textStyle: { //数值样式 + color: 'black',//字体颜色 + fontSize: 10//字体大小 + } + }, + itemStyle: { + color: '#409EFF' + }, + }] + }; } } diff --git a/hb_client/src/views/statistics/progressStatistics.vue b/hb_client/src/views/statistics/progressStatistics.vue index 0e5ee33..8afeeb4 100644 --- a/hb_client/src/views/statistics/progressStatistics.vue +++ b/hb_client/src/views/statistics/progressStatistics.vue @@ -7,7 +7,7 @@
@@ -18,7 +18,7 @@ :id="chartId1" :options="barOptions" :className="chartsName" - height="45vh" + :height="domHeight" width="100%" > @@ -31,7 +31,7 @@ :id="chartId2" :options="barOptions1" :className="chartsName" - height="45vh" + :height="domHeight" width="100%" > @@ -54,7 +54,6 @@ v-el-height-adaptive-table="{bottomOffset: 42}" > - @@ -64,7 +63,6 @@ - @@ -168,6 +166,7 @@ {value: 1, name: "逾期率"}, ] }, + domHeight:'300px', xAxisbar:["冷加工", "热弯", "钢化", "镀膜", "夹层", "包边", "装框"], barData: [80, 95, 96, 96, 96, 98, 99], xAxisbar1:[ "2021/09", "2021/10", "2021/11", "2021/12", "2022/01", "2022/02"], @@ -217,6 +216,10 @@ immediate: true, deep: true }, + created(){ + let hei = document.getElementsByClassName('app-main')[0].clientHeight; + this.domHeight =( hei- 60)/2+'px'; + }, methods: { getList() { this.orderLoading = true; @@ -378,6 +381,7 @@ }, }] } + } } diff --git a/hb_client/src/views/wpm/need.vue b/hb_client/src/views/wpm/need.vue index 2af65be..c05e711 100644 --- a/hb_client/src/views/wpm/need.vue +++ b/hb_client/src/views/wpm/need.vue @@ -1008,25 +1008,23 @@ handleInspection(scope, index) { //调该物料对应的检查表 let that = this; - this.innerIndex = index; + that.innerIndex = index; // this.outerVisible = true; - this.wproduct = scope.row.id;//半成品ID - debugger; - console.log(scope.row.material_check); - this.listQueryrecordform.material = scope.row.material_check!==null ? scope.row.material_check :scope.row.material;// - this.listQueryrecordform.type = 2; - this.listQueryrecordform.enabled = true; - this.recordform = null; - getrecordformList(this.listQueryrecordform).then((response) => { + that.wproduct = scope.row.id;//半成品ID + that.listQueryrecordform.material = scope.row.material; + that.listQueryrecordform.type = 20; + that.listQueryrecordform.enabled = true; + that.recordform = null; + getrecordformList(that.listQueryrecordform).then((response) => { if (response.data) { - this.recordformList = response.data; + that.recordformList = response.data; if (response.data.length === 1) { that.recordform = response.data[0].id; that.formName = response.data[0].name; that.submitrecordform(index); } else { //弹出列表选择框 - this.outerVisible = true; + that.outerVisible = true; } } }); @@ -1038,8 +1036,6 @@ return item.id === that.recordform; }); that.formName = arr[0].name; - // this.outerVisible = false; - // that.submitrecordform(that.innerIndex); }, //检验记录 checkRecord(scope, index) { diff --git a/hb_server/apps/mtm/views.py b/hb_server/apps/mtm/views.py index 5a3f3e0..4a02aa5 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, PackItem, Process, RecordForm, RecordFormField, Step, SubprodctionMaterial, TechDoc, UsedStep, SubProduction -from apps.mtm.serializers import InputMaterialSerializer, InputMaterialUpdateSerializer, MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, OtherMaterialSerializer, OutputMaterialSerializer, OutputMaterialUpdateSerializer, PackItemCreateSerializer, PackItemUpdateSerializer, ProcessSerializer, RecordFormCreateSerializer, RecordFormDetailSerializer, RecordFormFieldCreateSerializer, RecordFormFieldSerializer, RecordFormFieldUpdateSerializer, RecordFormSerializer, RecordFormUpdateSerializer, StepDetailSerializer, StepSerializer, SubProductionCreateUpdateSerializer, SubProductionSerializer, SubprodctionMaterialListSerializer, TechDocCreateSerializer, TechDocListSerializer, TechDocUpdateSerializer, UsedStepCreateSerializer, UsedStepListSerializer, UsedStepUpdateSerializer +from apps.mtm.serializers import InputMaterialSerializer, InputMaterialUpdateSerializer, MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, OtherMaterialSerializer, OutputMaterialSerializer, OutputMaterialUpdateSerializer, PackItemCreateSerializer, PackItemSerializer, PackItemUpdateSerializer, 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 @@ -38,7 +38,7 @@ class PackItemViewSet(CreateUpdateModelAMixin, ModelViewSet): perms_map = {'get': '*', 'post': 'packitem_create', 'put': 'packitem_update', 'delete': 'packitem_delete'} queryset = PackItem.objects.all() - serializer_class = MaterialSerializer + serializer_class = PackItemSerializer search_fields = ['name', 'number'] filterset_fields = ['material'] ordering = ['sort'] diff --git a/hb_server/apps/pm/migrations/0025_auto_20220218_1616.py b/hb_server/apps/pm/migrations/0025_auto_20220218_1616.py new file mode 100644 index 0000000..84dfbed --- /dev/null +++ b/hb_server/apps/pm/migrations/0025_auto_20220218_1616.py @@ -0,0 +1,45 @@ +# Generated by Django 3.2.9 on 2022-02-18 08:16 + +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', '0047_packitem'), + ('pm', '0024_auto_20220217_1524'), + ] + + operations = [ + migrations.AddField( + model_name='subproductionplan', + name='first_tester', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='首件检查员'), + ), + migrations.AddField( + model_name='subproductionplan', + name='is_first_testok', + field=models.BooleanField(default=True, verbose_name='首件是否合格'), + ), + migrations.CreateModel( + name='FirstItem', + 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_value', models.JSONField(blank=True, null=True, verbose_name='录入值')), + ('is_hidden', models.BooleanField(default=False, verbose_name='是否隐藏')), + ('is_testok', models.BooleanField(blank=True, null=True, verbose_name='是否合格')), + ('form_field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.recordformfield', verbose_name='关联自定义表格字段')), + ('subproduction_plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_test_record', to='pm.subproductionplan', verbose_name='关联的子计划')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/hb_server/apps/pm/migrations/0026_auto_20220218_1636.py b/hb_server/apps/pm/migrations/0026_auto_20220218_1636.py new file mode 100644 index 0000000..bde5e8c --- /dev/null +++ b/hb_server/apps/pm/migrations/0026_auto_20220218_1636.py @@ -0,0 +1,65 @@ +# Generated by Django 3.2.9 on 2022-02-18 08:36 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0047_packitem'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('pm', '0025_auto_20220218_1616'), + ] + + operations = [ + migrations.RemoveField( + model_name='subproductionplan', + name='first_tester', + ), + migrations.RemoveField( + model_name='subproductionplan', + name='is_first_testok', + ), + migrations.AddField( + model_name='subproductionplan', + name='first_sign_time', + field=models.DateTimeField(blank=True, null=True, verbose_name='首件签字时间'), + ), + migrations.AddField( + model_name='subproductionplan', + name='form', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mtm.recordform', verbose_name='首件检查表'), + ), + migrations.AddField( + model_name='subproductionplan', + name='is_testok', + field=models.BooleanField(blank=True, null=True, verbose_name='首件是否合格'), + ), + migrations.AddField( + model_name='subproductionplan', + name='leader_1', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='first_leader_1', to=settings.AUTH_USER_MODEL, verbose_name='工序负责人'), + ), + migrations.AddField( + model_name='subproductionplan', + name='leader_2', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='first_leader_2', to=settings.AUTH_USER_MODEL, verbose_name='技术负责人'), + ), + migrations.AddField( + model_name='subproductionplan', + name='leader_3', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='first_leader_3', to=settings.AUTH_USER_MODEL, verbose_name='总检'), + ), + migrations.AddField( + model_name='subproductionplan', + name='remark', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='备注'), + ), + migrations.AddField( + model_name='subproductionplan', + name='tester', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='first_tester', to=settings.AUTH_USER_MODEL, verbose_name='首件检查员'), + ), + ] 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..ee1ca1b --- /dev/null +++ b/hb_server/apps/pm/migrations/0027_auto_20220221_1027.py @@ -0,0 +1,35 @@ +# 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.DeleteModel( + name='FirstItem', + ), + ] diff --git a/hb_server/apps/pm/models.py b/hb_server/apps/pm/models.py index d15141a..7a06a16 100644 --- a/hb_server/apps/pm/models.py +++ b/hb_server/apps/pm/models.py @@ -1,5 +1,5 @@ -from io import open_code -from apps.system.models import CommonAModel, Organization + +from apps.system.models import CommonAModel, Organization, User from django.db import models from django.contrib.auth.models import AbstractUser from django.db.models.base import Model @@ -7,7 +7,7 @@ import django.utils.timezone as timezone from django.db.models.query import QuerySet from utils.model import SoftModel, BaseModel -from apps.mtm.models import Material, Process, RecordFormField, SubProduction, SubprodctionMaterial +from apps.mtm.models import Material, Process, RecordForm, RecordFormField, SubProduction, SubprodctionMaterial from apps.sam.models import Order class ProductionPlan(CommonAModel): @@ -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') @@ -90,22 +91,21 @@ class SubProductionPlan(CommonAModel): start_date_real = models.DateField('实际开工日期', null=True, blank=True) end_date_real = models.DateField('实际完工日期', null=True, blank=True) is_picked = models.BooleanField('是否已领料', default=False) + + 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, + verbose_name="技术负责人", null=True, blank=True, related_name='first_leader_2') + leader_3 = models.ForeignKey(User, on_delete=models.CASCADE, + verbose_name="总检", null=True, blank=True, related_name='first_leader_3') + first_sign_time = models.DateTimeField('首件签字时间', null=True, blank=True) + remark = models.CharField('备注', max_length=100, null=True, blank=True) - # wproducts = models.JSONField('半成品表', default=list, blank=True) class Meta: 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 a537ae9..470ad67 100644 --- a/hb_server/apps/pm/serializers.py +++ b/hb_server/apps/pm/serializers.py @@ -1,8 +1,10 @@ +from apps.mtm.models import RecordForm from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress from rest_framework import serializers +from apps.qm.serializers import TestRecordShortSerializer from apps.sam.serializers import OrderSerializer, OrderSimpleSerializer -from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, SubProductionSimpleSerializer -from apps.system.serializers import OrganizationSimpleSerializer +from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, RecordFormSimpleSerializer, SubProductionSimpleSerializer +from apps.system.serializers import OrganizationSimpleSerializer, UserSimpleSerializer from utils.mixins import DynamicFieldsSerializerMixin @@ -32,12 +34,16 @@ 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) + first_test_ = TestRecordShortSerializer(source='first_test', read_only=True) class Meta: model=SubProductionPlan fields = '__all__' @@ -74,4 +80,13 @@ class SubProductionProgressSerializer(serializers.ModelSerializer): subproduction_plan_ = SubproductionPlanSimpleSerializer(source='subproduction_plan', read_only=True) class Meta: model = SubProductionProgress - fields = '__all__' \ No newline at end of file + fields = '__all__' + + +class FirstTestInitSerializer(serializers.Serializer): + form = serializers.PrimaryKeyRelatedField(queryset=RecordForm.objects.all(), required=True) + + +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 5df4d6f..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 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,6 +235,67 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo have = MaterialBatchSerializer(instance=objs, many=True).data return Response({'need':need, 'have':have}) + @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.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') + 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) + + + class SubProductionProgressViewSet(ListModelMixin, GenericViewSet): """ 生产进度 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..65c0cf3 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,11 +56,12 @@ 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) - is_testok = models.BooleanField('是否合格', default=True) + is_testok = models.BooleanField('是否合格', null=True, blank=True) number = models.CharField('产品编号', null=True, blank=True, max_length=50) wproduct = models.ForeignKey('wpm.wproduct', verbose_name='关联的动态产品', on_delete=models.CASCADE, null=True, blank=True, related_name='test_wproduct') material = models.ForeignKey('mtm.material', verbose_name='关联的物料状态', on_delete=models.CASCADE, null=True, blank=True) diff --git a/hb_server/apps/qm/serializers.py b/hb_server/apps/qm/serializers.py index d1379c6..28a8829 100644 --- a/hb_server/apps/qm/serializers.py +++ b/hb_server/apps/qm/serializers.py @@ -63,7 +63,11 @@ class TestRecordItemSerializer(serializers.ModelSerializer): class Meta: model = TestRecordItem fields = '__all__' - + +class TestRecordShortSerializer(serializers.ModelSerializer): + class Meta: + model = TestRecord + fields = ['id', 'form', 'is_testok', 'is_submited'] class TestRecordCreateSerializer(serializers.ModelSerializer): record_data = TestRecordItemCreateSerializer(many=True) class Meta: 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