diff --git a/.gitignore b/.gitignore index fa116e5..2f02e50 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ deploy.sh package-lock.json .idea/ .vscode/ +.idea/ server/static/ \ No newline at end of file diff --git a/hb_client/src/api/srm.js b/hb_client/src/api/srm.js index 7e3a614..ea41540 100644 --- a/hb_client/src/api/srm.js +++ b/hb_client/src/api/srm.js @@ -7,3 +7,11 @@ export function getPlanGantt(data) { params: data }) } +//合格率 +export function getProcessYield(data) { + return request({ + url: '/srm/process/yield/', + method: 'post', + data + }) +} diff --git a/hb_client/src/components/Gantt/components/dashLeftMenu.vue b/hb_client/src/components/Gantt/components/dashLeftMenu.vue new file mode 100644 index 0000000..9014840 --- /dev/null +++ b/hb_client/src/components/Gantt/components/dashLeftMenu.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/hb_client/src/components/Gantt/components/leftMenu.vue b/hb_client/src/components/Gantt/components/leftMenu.vue index 632bcef..43a331f 100644 --- a/hb_client/src/components/Gantt/components/leftMenu.vue +++ b/hb_client/src/components/Gantt/components/leftMenu.vue @@ -19,7 +19,7 @@ @expand-change="handlerExpand" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" > - + diff --git a/hb_client/src/components/Gantt/dashGantt.vue b/hb_client/src/components/Gantt/dashGantt.vue new file mode 100644 index 0000000..4d84376 --- /dev/null +++ b/hb_client/src/components/Gantt/dashGantt.vue @@ -0,0 +1,1394 @@ + + + + + diff --git a/hb_client/src/components/faceLogin/faceLogin.vue b/hb_client/src/components/faceLogin/faceLogin.vue index 269d70f..d1aaaf3 100644 --- a/hb_client/src/components/faceLogin/faceLogin.vue +++ b/hb_client/src/components/faceLogin/faceLogin.vue @@ -35,12 +35,56 @@ } }, mounted() { - this.init(); + this.openTheCamera(); }, methods: { + openTheCamera () { + this.$nextTick(function () { + let _this = this; + this.video = document.getElementById('video'); + // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象 + if (navigator.mediaDevices === undefined) { + navigator.mediaDevices = {} + } + // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象 + // 使用getUserMedia,因为它会覆盖现有的属性。 + // 这里,如果缺少getUserMedia属性,就添加它。 + if (navigator.mediaDevices.getUserMedia === undefined) { + navigator.mediaDevices.getUserMedia = function (constraints) { + // 首先获取现存的getUserMedia(如果存在) + let getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia; + // 有些浏览器不支持,会返回错误信息 + // 保持接口一致 + if (!getUserMedia) { + return Promise.reject(new Error('getUserMedia is not implemented in this browser')) + } + // 否则,使用Promise将调用包装到旧的navigator.getUserMedia + return new Promise(function (resolve, reject) { + getUserMedia.call(navigator, constraints, resolve, reject) + }) + } + } + let constraints = { audio: false, video: { width: this.videoWidth, height: this.videoHeight, transform: 'scaleX(-1)' } }; + navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { + // 旧的浏览器可能没有srcObject + if ('srcObject' in _this.video) { + _this.video.srcObject = stream + } else { + // 避免在新的浏览器中使用它,因为它正在被弃用。 + _this.video.src = window.URL.createObjectURL(stream) + } + _this.video.onloadedmetadata = function (e) { + _this.video.play(); + }; + _this.init(); + }).catch(err => { + console.log(err) + }) + }); + }, // 初始化设置 init() { - this.video = document.getElementById('video'); + // this.video = document.getElementById('video'); this.screenshotCanvas = document.getElementById('screenshotCanvas'); let canvas = document.getElementById('canvas'); let context = canvas.getContext('2d'); @@ -64,7 +108,6 @@ event.data.forEach(function (rect) { context.strokeStyle = '#0764B7'; context.strokeRect(rect.x, rect.y, rect.width, rect.height); - // window.plot(rect.x, rect.y, rect.width, rect.height+20); // 避免重复发送请求 if(!_this.uploadLock){ _this.uploadLock = true; @@ -133,7 +176,10 @@ that.uploadLock = false; // this.$message.error('面部识别失败请重新验证'); }); - } + }, + closeCamera () { + this.video.srcObject.getTracks()[0].stop(); + }, } } diff --git a/hb_client/src/components/faceLogin/tracking.vue b/hb_client/src/components/faceLogin/tracking.vue index 67457c6..4ef978b 100644 --- a/hb_client/src/components/faceLogin/tracking.vue +++ b/hb_client/src/components/faceLogin/tracking.vue @@ -24,12 +24,55 @@ } }, mounted() { - this.init(); + this.openTheCamera(); }, methods: { + openTheCamera () { + this.$nextTick(function () { + let _this = this; + this.video = document.getElementById('video'); + // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象 + if (navigator.mediaDevices === undefined) { + navigator.mediaDevices = {} + } + // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象 + // 使用getUserMedia,因为它会覆盖现有的属性。 + // 这里,如果缺少getUserMedia属性,就添加它。 + if (navigator.mediaDevices.getUserMedia === undefined) { + navigator.mediaDevices.getUserMedia = function (constraints) { + // 首先获取现存的getUserMedia(如果存在) + let getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia; + // 有些浏览器不支持,会返回错误信息 + // 保持接口一致 + if (!getUserMedia) { + return Promise.reject(new Error('getUserMedia is not implemented in this browser')) + } + // 否则,使用Promise将调用包装到旧的navigator.getUserMedia + return new Promise(function (resolve, reject) { + getUserMedia.call(navigator, constraints, resolve, reject) + }) + } + } + let constraints = { audio: false, video: { width: this.videoWidth, height: this.videoHeight, transform: 'scaleX(-1)' } }; + navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { + // 旧的浏览器可能没有srcObject + if ('srcObject' in _this.video) { + _this.video.srcObject = stream + } else { + // 避免在新的浏览器中使用它,因为它正在被弃用。 + _this.video.src = window.URL.createObjectURL(stream) + } + _this.video.onloadedmetadata = function (e) { + _this.video.play(); + }; + _this.init(); + }).catch(err => { + console.log(err) + }) + }); + }, // 初始化设置 init() { - this.video = document.getElementById('video'); this.screenshotCanvas = document.getElementById('screenshotCanvas'); let canvas = document.getElementById('canvas'); let context = canvas.getContext('2d'); @@ -46,7 +89,6 @@ let _this = this; //添加事件 tracker.on('track', function (event) { - // 检测出人脸 绘画人脸位置 context.clearRect(0, 0, canvas.width, canvas.height); // 给每个人脸绘制对应的框 @@ -112,7 +154,10 @@ that.uploadLock = false; // this.$message.error('面部识别失败请重新验证'); }); - } + }, + closeCamera () { + this.video.srcObject.getTracks()[0].stop(); + }, } } diff --git a/hb_client/src/views/dashboard/index.vue b/hb_client/src/views/dashboard/index.vue index 7ff725e..1fbc573 100644 --- a/hb_client/src/views/dashboard/index.vue +++ b/hb_client/src/views/dashboard/index.vue @@ -1,47 +1,122 @@ diff --git a/hb_client/src/views/login/index.vue b/hb_client/src/views/login/index.vue index 4f256c6..53da72e 100644 --- a/hb_client/src/views/login/index.vue +++ b/hb_client/src/views/login/index.vue @@ -64,10 +64,10 @@ - +
打卡
- +
@@ -179,9 +179,61 @@ //人脸登录 takePhoto(){ this.limitedPhoto = true; + this.openTheCamera(); + }, + /*打开相机*/ + openTheCamera () { + this.$nextTick(function () { + let _this = this; + this.thisVideo = document.getElementById('videoCamera'); + // 旧版本浏览器可能根本不支持mediaDevices,我们首先设置一个空对象 + if (navigator.mediaDevices === undefined) { + navigator.mediaDevices = {} + } + // 一些浏览器实现了部分mediaDevices,我们不能只分配一个对象 + // 使用getUserMedia,因为它会覆盖现有的属性。 + // 这里,如果缺少getUserMedia属性,就添加它。 + if (navigator.mediaDevices.getUserMedia === undefined) { + navigator.mediaDevices.getUserMedia = function (constraints) { + // 首先获取现存的getUserMedia(如果存在) + let getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.getUserMedia; + // 有些浏览器不支持,会返回错误信息 + // 保持接口一致 + if (!getUserMedia) { + return Promise.reject(new Error('getUserMedia is not implemented in this browser')) + } + // 否则,使用Promise将调用包装到旧的navigator.getUserMedia + return new Promise(function (resolve, reject) { + getUserMedia.call(navigator, constraints, resolve, reject) + }) + } + } + + let constraints = { audio: false, video: { width: this.videoWidth, height: this.videoHeight, transform: 'scaleX(-1)' } }; + navigator.mediaDevices.getUserMedia(constraints).then(function (stream) { + // 旧的浏览器可能没有srcObject + if ('srcObject' in _this.thisVideo) { + _this.thisVideo.srcObject = stream + } else { + // 避免在新的浏览器中使用它,因为它正在被弃用。 + _this.thisVideo.src = window.URL.createObjectURL(stream) + } + _this.thisVideo.onloadedmetadata = function (e) { + _this.thisVideo.play(); + } + }).catch(err => { + console.log(err) + }) + }); + }, + /*关闭相机*/ + closeCamera () { + debugger; + this.$refs.faceTracking.closeCamera(); + // this.thisVideo.srcObject.getTracks()[0].stop(); }, getMsgFormSon(data){ - this.limitedPhoto = data; + // this.limitedPhoto = data; }, }, }; diff --git a/hb_client/src/views/mtm/material.vue b/hb_client/src/views/mtm/material.vue index 0caa4fb..384a3fd 100644 --- a/hb_client/src/views/mtm/material.vue +++ b/hb_client/src/views/mtm/material.vue @@ -121,7 +121,7 @@ @@ -131,41 +131,42 @@ - + - + + :value="item.value" + > - - - + + + :value="item.value" + > - + - - - - + + + + + -
取消 @@ -189,8 +190,14 @@ import {genTree} from "@/utils"; import Pagination from "@/components/Pagination"; // secondary package based on el-pagination const defaultmaterial = { - name: "", - number: "", + name: null, + number: null, + specification: null, + unit: null, + type: null, + piece_count: null, + sort_str: null, + count_safe: 0, processes: [], }; export default { @@ -270,19 +277,13 @@ created() { this.getList(); this.getProcessList(); - }, methods: { checkPermission, - //物料详情 - handledetail(scope) - { + handledetail(scope){ this.$router.push({name: "MaterialDetail", params: { id: scope.row.id,type: scope.row.type }, }) - }, - - //选项卡切换 handleClick(tab) { this.listLoading = true; @@ -311,10 +312,8 @@ }, //绑定工序 handlebind(scope) { - this.$router.push({name: "MaterialDO", params: {id: scope.row.id},}) - } - , + }, handleFilter() { this.listQuery.page = 1; this.getList(); @@ -324,7 +323,7 @@ this.listQuery = { page: 1, page_size: 20, - } + }; this.getList(); }, handleCreate() { @@ -335,7 +334,6 @@ this.$refs["Form"].clearValidate(); }); }, - handleEdit(scope) { this.material = Object.assign({}, scope.row); // copy obj this.dialogType = "edit"; @@ -359,7 +357,6 @@ console.error(err); }); }, - async confirm(form) { this.$refs[form].validate((valid) => { if (valid) { diff --git a/hb_client/src/views/mtm/materialdo.vue b/hb_client/src/views/mtm/materialdo.vue index 19b98e6..b42acd3 100644 --- a/hb_client/src/views/mtm/materialdo.vue +++ b/hb_client/src/views/mtm/materialdo.vue @@ -82,8 +82,8 @@
取消 - - 管理员授权 + 确认 +
diff --git a/hb_client/src/views/pm/plandetails.vue b/hb_client/src/views/pm/plandetails.vue index fdcf322..2988bab 100644 --- a/hb_client/src/views/pm/plandetails.vue +++ b/hb_client/src/views/pm/plandetails.vue @@ -1,313 +1,255 @@ + diff --git a/hb_client/src/views/qm/producttest.vue b/hb_client/src/views/qm/producttest.vue index ef5b134..c8e9e87 100644 --- a/hb_client/src/views/qm/producttest.vue +++ b/hb_client/src/views/qm/producttest.vue @@ -119,7 +119,6 @@ :title="formName" :visible.sync="recordVisible" :close-on-click-modal="false" - @close="recordCancel" > diff --git a/hb_client/src/views/qm/taskdetails.vue b/hb_client/src/views/qm/taskdetails.vue index 37a1a93..117c79e 100644 --- a/hb_client/src/views/qm/taskdetails.vue +++ b/hb_client/src/views/qm/taskdetails.vue @@ -1,305 +1,283 @@ - - - - - + + + + - - - -
diff --git a/hb_client/src/views/qm/taskrecordfrom.vue b/hb_client/src/views/qm/taskrecordfrom.vue index c12eff3..f5edea3 100644 --- a/hb_client/src/views/qm/taskrecordfrom.vue +++ b/hb_client/src/views/qm/taskrecordfrom.vue @@ -46,7 +46,6 @@ :title="formName" :visible.sync="recordVisible" :close-on-click-modal="false" - @close="recordCancel" > diff --git a/hb_client/src/views/workflow/index.vue b/hb_client/src/views/workflow/index.vue index 5a57a88..1694c63 100644 --- a/hb_client/src/views/workflow/index.vue +++ b/hb_client/src/views/workflow/index.vue @@ -102,8 +102,10 @@

创建时间 :{{watchedCreateTime}}

- - +
+ + +
- +
diff --git a/hb_client/src/views/wpm/need.vue b/hb_client/src/views/wpm/need.vue index 5c58541..8c9e79c 100644 --- a/hb_client/src/views/wpm/need.vue +++ b/hb_client/src/views/wpm/need.vue @@ -613,6 +613,27 @@ 确 定
+ + + + +
+ {{item.field_name}}: + {{item.field_value}} +
+
+ +
+ {{item.field_name}}: + +
+
+
+
+ diff --git a/hb_server/apps/develop/urls.py b/hb_server/apps/develop/urls.py index b284243..79f8f04 100644 --- a/hb_server/apps/develop/urls.py +++ b/hb_server/apps/develop/urls.py @@ -2,13 +2,14 @@ from django.db.models import base from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateFIFOItem, UpdateLastTestResult +from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateFIFOItem, UpdateLastTestResult, UpdateSpg urlpatterns = [ path('cleandata/', CleanDataView.as_view()), path('update_cutting/', UpdateCuttingView.as_view()), path('update_last_result/', UpdateLastTestResult.as_view()), path('update_last_result/', UpdateLastTestResult.as_view()), - path('update_fifoitem/', UpdateFIFOItem.as_view()) + path('update_fifoitem/', UpdateFIFOItem.as_view()), + path('update_spg/', UpdateSpg.as_view()) ] diff --git a/hb_server/apps/develop/views.py b/hb_server/apps/develop/views.py index f02f55f..f6a52fc 100644 --- a/hb_server/apps/develop/views.py +++ b/hb_server/apps/develop/views.py @@ -6,7 +6,7 @@ from rest_framework.permissions import IsAdminUser from rest_framework.response import Response from apps.inm.models import FIFO, FIFOItem, Inventory, MaterialBatch from apps.mtm.models import Material -from apps.pm.models import ProductionPlan +from apps.pm.models import ProductionPlan, SubProductionPlan from apps.sam.models import Order from apps.wf.models import Ticket from apps.wpm.models import Operation, OperationMaterial, WProduct, WproductFlow @@ -80,3 +80,14 @@ class UpdateFIFOItem(APIView): i.is_testok = None i.save() return Response() + +class UpdateSpg(APIView): + permission_classes = [IsAdminUser] + @transaction.atomic + def post(self, request, format=None): + """ + 冷加工重新计算合格率 + """ + for i in SubProductionPlan.objects.filter(subproduction__process__id=1): + WpmServies.update_subproduction_progress_main(sp=i) + return Response() diff --git a/hb_server/apps/em/migrations/0010_alter_equipment_state.py b/hb_server/apps/em/migrations/0010_alter_equipment_state.py new file mode 100644 index 0000000..4f8de3c --- /dev/null +++ b/hb_server/apps/em/migrations/0010_alter_equipment_state.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-18 00:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('em', '0009_auto_20210916_1108'), + ] + + operations = [ + migrations.AlterField( + model_name='equipment', + name='state', + field=models.PositiveIntegerField(choices=[(0, '完好'), (1, '限用'), (2, '在修'), (3, '禁用')], default=0, verbose_name='设备状态'), + ), + ] diff --git a/hb_server/apps/em/migrations/0011_auto_20220120_0956.py b/hb_server/apps/em/migrations/0011_auto_20220120_0956.py new file mode 100644 index 0000000..845ec26 --- /dev/null +++ b/hb_server/apps/em/migrations/0011_auto_20220120_0956.py @@ -0,0 +1,63 @@ +# Generated by Django 3.2.9 on 2022-01-20 01:56 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('em', '0010_alter_equipment_state'), + ] + + operations = [ + migrations.CreateModel( + name='ECheckRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('check_date', models.DateField(blank=True, null=True, verbose_name='校准检查日期')), + ('description', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='echeckrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.RemoveField( + model_name='equipment', + name='belong_dept', + ), + migrations.RemoveField( + model_name='equipment', + name='statedm', + ), + migrations.AddField( + model_name='equipment', + name='next_check_date', + field=models.DateField(blank=True, null=True, verbose_name='下次校准检查日期'), + ), + migrations.AlterField( + model_name='equipment', + name='state', + field=models.PositiveIntegerField(choices=[(10, '完好'), (20, '限用'), (30, '在修'), (40, '禁用')], default=0, verbose_name='设备状态'), + ), + migrations.DeleteModel( + name='Equipmentrecord', + ), + migrations.AddField( + model_name='echeckrecord', + name='equipment', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='em.equipment', verbose_name='校准检定设备'), + ), + migrations.AddField( + model_name='echeckrecord', + name='update_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='echeckrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'), + ), + ] diff --git a/hb_server/apps/em/migrations/0012_auto_20220120_1048.py b/hb_server/apps/em/migrations/0012_auto_20220120_1048.py new file mode 100644 index 0000000..a0aff45 --- /dev/null +++ b/hb_server/apps/em/migrations/0012_auto_20220120_1048.py @@ -0,0 +1,43 @@ +# Generated by Django 3.2.9 on 2022-01-20 02:48 + +import datetime +from django.db import migrations, models +import django.db.models.deletion +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('em', '0011_auto_20220120_0956'), + ] + + operations = [ + migrations.AddField( + model_name='equipment', + name='check_date', + field=models.DateField(blank=True, null=True, verbose_name='最近校准检查日期'), + ), + migrations.AlterField( + model_name='echeckrecord', + name='check_date', + field=models.DateField(default=datetime.datetime(2022, 1, 20, 2, 48, 20, 706844, tzinfo=utc), verbose_name='校准检查日期'), + preserve_default=False, + ), + migrations.AlterField( + model_name='echeckrecord', + name='equipment', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='em.equipment', verbose_name='校准检定设备'), + preserve_default=False, + ), + migrations.AlterField( + model_name='equipment', + name='cycle', + field=models.IntegerField(blank=True, null=True, verbose_name='校准或检定周期(月)'), + ), + migrations.AlterField( + model_name='equipment', + name='next_check_date', + field=models.DateField(blank=True, null=True, verbose_name='预计下次校准检查日期'), + ), + ] diff --git a/hb_server/apps/em/models.py b/hb_server/apps/em/models.py index f2cb67c..194474f 100644 --- a/hb_server/apps/em/models.py +++ b/hb_server/apps/em/models.py @@ -3,32 +3,34 @@ from django.contrib.auth.models import AbstractUser from django.db.models.base import Model import django.utils.timezone as timezone from django.db.models.query import QuerySet -from apps.system.models import CommonAModel, CommonBModel, Organization, User, Dict, File -#from apps.mtm.models import Process +from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File from utils.model import SoftModel, BaseModel from simple_history.models import HistoricalRecords -class Equipment(CommonBModel): +class Equipment(CommonAModel): """ 设备台账信息 """ + EQUIP_STATE_OK = 10 + EQUIP_STATE_LIMIT = 20 + EQUIP_STATE_FIX = 30 + EQUIP_STATE_DISABLE = 40 state_choices = ( - (0, '完好'), - (1, '限用'), - (2, '在修'), - (3, '禁用') + (EQUIP_STATE_OK, '完好'), + (EQUIP_STATE_LIMIT, '限用'), + (EQUIP_STATE_FIX, '在修'), + (EQUIP_STATE_DISABLE, '禁用') + ) - statedm_choices = ( - (0, '合格'), - (1, '准用'), - (2, '限用'), - (3, '禁用'), - (4, '停用'), - (5, '封存') + state2_choices = ( + (EQUIP_STATE_OK, '合格'), + (EQUIP_STATE_DISABLE, '禁用') + ) - + EQUIP_TYPE_PRO = 1 + EQUIP_TYPE_TEST = 2 type_choices = ( (1, '生产设备'), (2, '检验工具') @@ -54,20 +56,21 @@ class Equipment(CommonBModel): factory = models.CharField('生产厂', max_length=50, null=True, blank=True) production_date = models.DateField('生产日期', null=True, blank=True) buy_date = models.DateField('购置日期', null=True, blank=True) - state = models.CharField('设备状态', max_length=11, choices=state_choices, default=0) + state = models.PositiveIntegerField('设备状态', choices=state_choices, default=0) parameter = models.TextField('技术参数', null=True, blank=True) place = models.CharField('存放位置', max_length=50, null=True, blank=True) count = models.IntegerField('数量', default=0) keeper = models.ForeignKey(User, verbose_name='保管人', on_delete=models.CASCADE, null=True, blank=True) description = models.CharField('描述', max_length=200, blank=True, null=True) - #process = models.ForeignKey(Process, verbose_name='工序', on_delete=models.CASCADE, null=True, blank=True) - mgmtype = models.IntegerField('管理类别', choices=mgmtype_choices, default=1)#监视,测量设备 - way = models.IntegerField('校准或检定方式', choices=way_choices, default=1)#监视,测量设备 - standard = models.CharField('溯源标准或依据', max_length=200, blank=True, null=True)#监视,测量设备 - cycle = models.IntegerField('校准或检定周期', default=0)#监视,测量设备 - usetype = models.IntegerField('使用类别', choices=usetype_choices, default=1)#监视,测量设备 - statedm = models.IntegerField('设备状态', choices=statedm_choices, default=0)#监视,测量设备 + # 以下是监视测量设备单独字段 + mgmtype = models.IntegerField('管理类别', choices=mgmtype_choices, default=1) + way = models.IntegerField('校准或检定方式', choices=way_choices, default=1) + standard = models.CharField('溯源标准或依据', max_length=200, blank=True, null=True) + cycle = models.IntegerField('校准或检定周期(月)', null=True, blank=True) + usetype = models.IntegerField('使用类别', choices=usetype_choices, default=1) + check_date = models.DateField('最近校准检查日期', blank=True, null=True) + next_check_date = models.DateField('预计下次校准检查日期',blank=True, null=True) class Meta: verbose_name = '设备信息' verbose_name_plural = verbose_name @@ -75,8 +78,10 @@ class Equipment(CommonBModel): def __str__(self): return self.number + '-' + self.name -class Equipmentrecord(CommonBModel): - equipment = models.ForeignKey(Equipment, verbose_name='校准检定设备', on_delete=models.CASCADE, null=True, blank=True) - recentlydate = models.DateField('最近一次校准/检定日期',blank=True, null=True) - nextdate = models.DateField('下次应校准或检定日期',blank=True, null=True) +class ECheckRecord(CommonADModel): + """ + 校准鉴定记录 + """ + equipment = models.ForeignKey(Equipment, verbose_name='校准检定设备', on_delete=models.CASCADE) + check_date = models.DateField('校准检查日期') description = models.CharField('描述', max_length=200, blank=True, null=True) \ No newline at end of file diff --git a/hb_server/apps/em/serializers.py b/hb_server/apps/em/serializers.py index be34bfc..c22238d 100644 --- a/hb_server/apps/em/serializers.py +++ b/hb_server/apps/em/serializers.py @@ -1,13 +1,12 @@ from apps.mtm.models import Step from rest_framework import serializers from rest_framework.serializers import ModelSerializer - -from .models import Equipment,Equipmentrecord +from rest_framework import exceptions +from .models import Equipment, ECheckRecord from apps.system.serializers import OrganizationSimpleSerializer, UserSimpleSerializer -class EquipmentSerializer(ModelSerializer): - belong_dept_ = OrganizationSimpleSerializer(source='belong_dept', read_only=True) +class EquipmentListSerializer(ModelSerializer): keeper_ = UserSimpleSerializer(source='keeper', read_only=True) step_ = serializers.SerializerMethodField() class Meta: @@ -17,24 +16,35 @@ class EquipmentSerializer(ModelSerializer): @staticmethod def setup_eager_loading(queryset): """ Perform necessary eager loading of data. """ - queryset = queryset.select_related('belong_dept','keeper') + queryset = queryset.select_related('keeper') return queryset def get_step_(self, obj): return Step.objects.filter(equipments=obj).values('id', 'name', 'number') +class EquipmentCreateUpdateSerializer(ModelSerializer): + class Meta: + model = Equipment + exclude = ['create_by', 'update_by', 'create_time', 'update_time', 'check_date', 'next_check_date'] + + def validate(self, attrs): + if attrs['type'] == Equipment.EQUIP_TYPE_TEST: + if attrs['state'] not in [Equipment.EQUIP_STATE_OK, Equipment.EQUIP_STATE_DISABLE]: + raise exceptions.APIException('设备状态错误') + return super().validate(attrs) + class EquipmentSimpleSerializer(ModelSerializer): class Meta: model = Equipment - fields = ['id', 'number', 'name', 'state'] + fields = ['id', 'number', 'name', 'state', 'model'] -class EquipmentrecordSerializer(ModelSerializer): - equipment_ = EquipmentSerializer(source='equipment', read_only=True) +class ECheckRecordListSerializer(ModelSerializer): + equipment_ = EquipmentSimpleSerializer(source='equipment', read_only=True) class Meta: - model = Equipmentrecord + model = ECheckRecord fields = '__all__' @staticmethod @@ -43,6 +53,11 @@ class EquipmentrecordSerializer(ModelSerializer): queryset = queryset.select_related('equipment') return queryset +class EChcekRecordCreateSerializer(ModelSerializer): + class Meta: + model = ECheckRecord + fields = ['equipment', 'check_date', 'description'] + class DaqCreateSerializer(serializers.Serializer): number = serializers.CharField() file = serializers.FileField() \ No newline at end of file diff --git a/hb_server/apps/em/services.py b/hb_server/apps/em/services.py new file mode 100644 index 0000000..5e1781c --- /dev/null +++ b/hb_server/apps/em/services.py @@ -0,0 +1,20 @@ +from tabnanny import check +from apps.em.models import ECheckRecord, Equipment +from dateutil.relativedelta import relativedelta +from django.utils import timezone +class EmService: + + @classmethod + def update_check_date(cls, equip:Equipment): + # 根据校准检定记录变更下次日期 + check = ECheckRecord.objects.filter(equipment=equip).order_by('id').last() + if check: + equip.check_date = check.check_date + if equip.cycle: + equip.next_check_date = equip.check_date + relativedelta(months=equip.cycle) + else: + equip.next_check_date = None + else: + equip.check_date = None + equip.next_check_date = None + equip.save() \ No newline at end of file diff --git a/hb_server/apps/em/urls.py b/hb_server/apps/em/urls.py index e2f7e91..2fc4bb8 100644 --- a/hb_server/apps/em/urls.py +++ b/hb_server/apps/em/urls.py @@ -1,12 +1,12 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.em.views import DaqView, EquipmentViewSet,EquipmentrecordViewSet +from apps.em.views import DaqView, EquipmentViewSet, EChcekRecordViewSet from django.urls import path, include from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('equipment', EquipmentViewSet, basename='equipment') -router.register('equipmentrecord', EquipmentrecordViewSet, basename='equipmentrecord') +router.register('echeck_record', EChcekRecordViewSet, basename='echeck_record') urlpatterns = [ path('daq/', DaqView.as_view()), path('', include(router.urls)), diff --git a/hb_server/apps/em/views.py b/hb_server/apps/em/views.py index 00ae4b9..aec05f7 100644 --- a/hb_server/apps/em/views.py +++ b/hb_server/apps/em/views.py @@ -1,14 +1,20 @@ +from datetime import timedelta from django.shortcuts import render +from numpy import delete from rest_framework.exceptions import APIException from rest_framework.views import APIView -from rest_framework.viewsets import ModelViewSet - +from rest_framework.viewsets import ModelViewSet, GenericViewSet +from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, ListModelMixin, DestroyModelMixin from rest_framework import serializers, status from rest_framework.response import Response -from apps.em.models import Equipment,Equipmentrecord -from apps.em.serializers import DaqCreateSerializer, EquipmentSerializer,EquipmentrecordSerializer +from apps.em.models import Equipment, ECheckRecord +from apps.em.serializers import DaqCreateSerializer, EChcekRecordCreateSerializer, ECheckRecordListSerializer, \ + EquipmentCreateUpdateSerializer, EquipmentListSerializer +from apps.em.services import EmService from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin +from django.utils import timezone +from django.db import transaction # Create your views here. class EquipmentViewSet(CreateUpdateModelAMixin, OptimizationMixin, ModelViewSet): @@ -18,48 +24,48 @@ class EquipmentViewSet(CreateUpdateModelAMixin, OptimizationMixin, ModelViewSet) perms_map = {'get': '*', 'post': 'equipment_create', 'put': 'equipment_update', 'delete': 'equipment_delete'} queryset = Equipment.objects.all() - serializer_class = EquipmentSerializer + serializer_class = EquipmentListSerializer search_fields = ['number', 'name','description'] - filterset_fields = ['belong_dept', 'keeper', 'type'] + filterset_fields = ['keeper', 'type'] ordering_fields = ['create_time'] ordering = ['-create_time'] + def get_serializer_class(self): + if self.action in ['create', 'update']: + return EquipmentCreateUpdateSerializer + return super().get_serializer_class() # Create your views here. -class EquipmentrecordViewSet(CreateUpdateModelAMixin, OptimizationMixin, ModelViewSet): +class EChcekRecordViewSet(CreateUpdateModelAMixin, OptimizationMixin, + CreateModelMixin, RetrieveModelMixin, ListModelMixin, DestroyModelMixin, GenericViewSet): """ 设备校准-增删改查 """ - perms_map = {'get': '*', 'post': 'equipmentrecord_create', - 'put': 'equipmentrecord_update', 'delete': 'equipmentrecord_delete'} - queryset = Equipmentrecord.objects.all() - serializer_class = EquipmentrecordSerializer + perms_map = {'get': '*', 'post': 'echeckrecord_create', 'delete': 'echeckrecord_delete'} + queryset = ECheckRecord.objects.all() + serializer_class = ECheckRecordListSerializer filterset_fields = ['equipment'] - ordering_fields = ['create_time'] - ordering = ['-create_time'] + ordering = ['-id'] + def get_serializer_class(self): + if self.action in ['create']: + return EChcekRecordCreateSerializer + return super().get_serializer_class() + + @transaction.atomic def create(self, request, *args, **kwargs): - - data = request.data - if data.get('equipment', None): - equipment = Equipment.objects.get(pk=data['equipment']) - equipment.statedm = data['state'] - equipment.save() serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save() - return Response(status=status.HTTP_200_OK) - def update(self, request, *args, **kwargs): - data = request.data - if data.get('equipment', None): - equipment = Equipment.objects.get(pk=data['equipment']) - equipment.statedm = data['state'] - equipment.save() - id = self.get_object() - serializer = self.get_serializer(id, data=data) - serializer.is_valid(raise_exception=True) - serializer.save() - return Response(status=status.HTTP_200_OK) + instance = serializer.save(create_by=request.user) + EmService.update_check_date(equip=instance.equipment) + return Response() + + @transaction.atomic + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + instance.delete() + EmService.update_check_date(equip=instance.equipment) + return Response() import uuid import os diff --git a/hb_server/apps/hrm/filters.py b/hb_server/apps/hrm/filters.py new file mode 100644 index 0000000..40c9b45 --- /dev/null +++ b/hb_server/apps/hrm/filters.py @@ -0,0 +1,9 @@ +from django_filters import rest_framework as filters +from apps.hrm.models import ClockRecord + +class ClockRecordFilterSet(filters.FilterSet): + create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte') + create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte') + class Meta: + model = ClockRecord + fields = ['create_by', 'create_time_start', 'create_time_end'] \ No newline at end of file diff --git a/hb_server/apps/hrm/migrations/0004_clockrecord.py b/hb_server/apps/hrm/migrations/0004_clockrecord.py new file mode 100644 index 0000000..97ae889 --- /dev/null +++ b/hb_server/apps/hrm/migrations/0004_clockrecord.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.9 on 2022-01-21 06:45 + +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), + ('hrm', '0003_employee_face_data'), + ] + + operations = [ + migrations.CreateModel( + name='ClockRecord', + 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.PositiveSmallIntegerField(choices=[(10, '上班打卡')], default=10, verbose_name='打卡类型')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clockrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clockrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/hb_server/apps/hrm/models.py b/hb_server/apps/hrm/models.py index dc85cf5..490237e 100644 --- a/hb_server/apps/hrm/models.py +++ b/hb_server/apps/hrm/models.py @@ -36,8 +36,12 @@ class Employee(CommonAModel): def __str__(self): return self.name -class Attendance(CommonADModel): +class ClockRecord(CommonADModel): """ - 出勤记录 + 打卡记录 """ - \ No newline at end of file + ClOCK_WORK1 = 10 + type_choice = ( + (ClOCK_WORK1, '上班打卡'), + ) + type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1) \ No newline at end of file diff --git a/hb_server/apps/hrm/serializers.py b/hb_server/apps/hrm/serializers.py index 7a14400..429cc40 100644 --- a/hb_server/apps/hrm/serializers.py +++ b/hb_server/apps/hrm/serializers.py @@ -1,7 +1,7 @@ from apps.system.models import User from rest_framework.serializers import ModelSerializer from rest_framework import serializers -from .models import Employee +from .models import ClockRecord, Employee from apps.system.serializers import UserListSerializer, UserSimpleSerializer from django.db.models.query import Prefetch @@ -23,3 +23,13 @@ class EmployeeSerializer(ModelSerializer): class FaceLoginSerializer(serializers.Serializer): base64 = serializers.CharField() + + +class FaceClockCreateSerializer(serializers.Serializer): + base64 = serializers.CharField() + +class ClockRecordListSerializer(serializers.ModelSerializer): + create_by_ = UserSimpleSerializer(source='create_by', read_only=True) + class Meta: + model = ClockRecord + fields = '__all__' diff --git a/hb_server/apps/hrm/services.py b/hb_server/apps/hrm/services.py new file mode 100644 index 0000000..a80cb8e --- /dev/null +++ b/hb_server/apps/hrm/services.py @@ -0,0 +1,39 @@ +from django.conf import settings +import uuid +import face_recognition +import os +from apps.hrm.models import Employee +from apps.system.models import User + +class HRMService: + + @classmethod + def face_compare_from_base64(cls, base64_data): + filename = str(uuid.uuid4()) + filepath = settings.BASE_DIR +'/temp/' + filename +'.png' + with open(filepath, 'wb') as f: + f.write(base64_data) + try: + unknown_picture = face_recognition.load_image_file(filepath) + unknown_face_encoding = face_recognition.face_encodings(unknown_picture)[0] + os.remove(filepath) + except: + os.remove(filepath) + return None, '头像解码失败' + + # 匹配人脸库 + user_faces = Employee.objects.filter(face_data__isnull=False, + user__is_active=True).values('user', 'face_data') + user_l = [] + face_l = [] + for i in user_faces: + user_l.append(i['user']) + face_l.append(i['face_data']) + + results = face_recognition.compare_faces(face_l, unknown_face_encoding, tolerance=0.5) + for index, value in enumerate(results): + if value: + # 识别成功 + user = User.objects.get(id=user_l[index]) + return user, '' + return None, '识别失败' \ No newline at end of file diff --git a/hb_server/apps/hrm/tasks.py b/hb_server/apps/hrm/tasks.py new file mode 100644 index 0000000..bd4449b --- /dev/null +++ b/hb_server/apps/hrm/tasks.py @@ -0,0 +1,8 @@ +from __future__ import absolute_import, unicode_literals + +from celery import shared_task + + +@shared_task +def x(): + print('ok') \ No newline at end of file diff --git a/hb_server/apps/hrm/urls.py b/hb_server/apps/hrm/urls.py index 293124c..55c2e55 100644 --- a/hb_server/apps/hrm/urls.py +++ b/hb_server/apps/hrm/urls.py @@ -1,11 +1,12 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.hrm.views import EmployeeViewSet, FaceLogin +from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, FaceLogin from django.urls import path, include from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register('employee', EmployeeViewSet, basename='employee') +router.register('clock_record', ClockRecordViewSet, basename='clock_record') urlpatterns = [ path('facelogin/', FaceLogin.as_view()), path('', include(router.urls)), diff --git a/hb_server/apps/hrm/views.py b/hb_server/apps/hrm/views.py index e4b7b3e..9c533d0 100644 --- a/hb_server/apps/hrm/views.py +++ b/hb_server/apps/hrm/views.py @@ -1,10 +1,13 @@ from django.shortcuts import render +from django.utils import timezone from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet, GenericViewSet -from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin +from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, CreateModelMixin, ListModelMixin +from apps.hrm.filters import ClockRecordFilterSet +from apps.hrm.services import HRMService from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin -from apps.hrm.models import Employee -from apps.hrm.serializers import EmployeeSerializer, FaceLoginSerializer +from apps.hrm.models import ClockRecord, Employee +from apps.hrm.serializers import ClockRecordListSerializer, EmployeeSerializer, FaceClockCreateSerializer, FaceLoginSerializer import face_recognition from django.conf import settings from django.core.cache import cache @@ -12,8 +15,10 @@ import logging from rest_framework.generics import CreateAPIView from rest_framework import status from rest_framework_simplejwt.tokens import RefreshToken - +from rest_framework import exceptions from apps.system.models import User +from apps.system.serializers import UserSimpleSerializer +from rest_framework.permissions import AllowAny logger = logging.getLogger('log') @@ -50,9 +55,56 @@ class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMix except: logger.error('人脸识别出错') -import uuid + +class ClockRecordViewSet(CreateModelMixin, ListModelMixin, GenericViewSet): + """ + 打卡记录 + """ + perms_map = {'get':'*', 'post':'*'} + authentication_classes = [] + permission_classes = [AllowAny] + queryset = ClockRecord.objects.select_related('create_by').all() + serializer_class = ClockRecordListSerializer + filterset_class = ClockRecordFilterSet + ordering = ['-pk'] + + def get_serializer_class(self): + if self.action == 'create': + return FaceClockCreateSerializer + return super().get_serializer_class() + + def create(self, request, *args, **kwargs): + now = timezone.now() + now_local = timezone.localtime() + if 8<=now_local.hour<=17: + base64_data = base64.urlsafe_b64decode(tran64( + request.data.get('base64').replace(' ', '+'))) + user, msg = HRMService.face_compare_from_base64(base64_data) + if user: + ins, created = ClockRecord.objects.get_or_create( + create_by=request.user, create_time__hour__range = [8,18], + create_time__year=now.year, create_time__month=now.month, + create_time__day=now.day, + defaults={ + 'type':ClockRecord.ClOCK_WORK1, + 'create_by':user, + 'create_time':now + }) + if not created: + ins.create_time = now + ins.save() + # 设为在岗 + user.is_atwork = True + user.save() + return Response(UserSimpleSerializer(instance=user).data) + return Response(msg, status=status.HTTP_400_BAD_REQUEST) + return Response('非打卡时间范围', status=status.HTTP_400_BAD_REQUEST) + + + + + import base64 -import os def tran64(s): missing_padding = len(s) % 4 @@ -70,41 +122,13 @@ class FaceLogin(CreateAPIView): """ 人脸识别登录 """ - # serializer = FaceLoginSerializer(data=request.data) - # serializer.is_valid(raise_exception=True) - filename = str(uuid.uuid4()) - filepath = settings.BASE_DIR +'/temp/' + filename +'.png' - with open(filepath, 'wb') as f: - data = tran64(request.data.get('base64').replace(' ', '+')) - f.write(base64.urlsafe_b64decode(data)) - # picture_of_me = face_recognition.load_image_file(settings.BASE_DIR +'/temp/me.png') - # my_face_encoding = face_recognition.face_encodings(picture_of_me)[0] - #results = face_recognition.compare_faces([my_face_encoding], unknown_face_encoding, tolerance=0.2) - try: - unknown_picture = face_recognition.load_image_file(filepath) - unknown_face_encoding = face_recognition.face_encodings(unknown_picture)[0] - os.remove(filepath) - except: - os.remove(filepath) - return Response('头像解码失败', status=status.HTTP_400_BAD_REQUEST) - - # 匹配人脸库 - user_faces = Employee.objects.filter(face_data__isnull=False, user__is_active=True).values('user', 'face_data') - user_l = [] - face_l = [] - for i in user_faces: - user_l.append(i['user']) - face_l.append(i['face_data']) - - results = face_recognition.compare_faces(face_l, unknown_face_encoding, tolerance=0.5) - for index, value in enumerate(results): - if value: - # 识别成功 - user = User.objects.get(id=user_l[index]) - refresh = RefreshToken.for_user(user) - return Response({ - 'refresh': str(refresh), - 'access': str(refresh.access_token), - 'username':user.username - }) - return Response('未找到对应用户', status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + base64_data = base64.urlsafe_b64decode(tran64(request.data.get('base64').replace(' ', '+'))) + user, msg = HRMService.face_compare_from_base64(base64_data) + if user: + refresh = RefreshToken.for_user(user) + return Response({ + 'refresh': str(refresh), + 'access': str(refresh.access_token), + 'username':user.username + }) + return Response(msg, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file diff --git a/hb_server/apps/inm/filters.py b/hb_server/apps/inm/filters.py index db876ec..73f92a6 100644 --- a/hb_server/apps/inm/filters.py +++ b/hb_server/apps/inm/filters.py @@ -21,7 +21,10 @@ class MbFilterSet(filters.FilterSet): class IProductFilterSet(filters.FilterSet): order = filters.NumberFilter(field_name="wproduct__subproduction_plan__production_plan__order") - + to_order = filters.NumberFilter(field_name="wproduct__to_order") + update_time_start = filters.DateFilter(field_name="update_time", lookup_expr='gte') + update_time_end = filters.DateFilter(field_name="update_time", lookup_expr='lte') class Meta: model = IProduct - fields = ['material', 'warehouse', 'batch', 'order', 'material__type'] + fields = ['material', 'warehouse', 'batch', 'order', 'material__type', + 'is_saled', 'update_time_start', 'update_time_end', 'to_order'] diff --git a/hb_server/apps/inm/serializers.py b/hb_server/apps/inm/serializers.py index e983108..766a164 100644 --- a/hb_server/apps/inm/serializers.py +++ b/hb_server/apps/inm/serializers.py @@ -9,6 +9,7 @@ from apps.mtm.serializers import MaterialSimpleSerializer from django.db import transaction + class WareHouseSerializer(serializers.ModelSerializer): create_by_ = UserSimpleSerializer('create_by', read_only=True) diff --git a/hb_server/apps/mtm/migrations/0044_subproduction_need_combtest.py b/hb_server/apps/mtm/migrations/0044_subproduction_need_combtest.py new file mode 100644 index 0000000..c570518 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0044_subproduction_need_combtest.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-19 07:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mtm', '0043_auto_20220106_0942'), + ] + + operations = [ + migrations.AddField( + model_name='subproduction', + name='need_combtest', + field=models.BooleanField(default=False, verbose_name='需要质检'), + ), + ] diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index b7b99c2..4afb8b5 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -210,6 +210,7 @@ class SubProduction(CommonAModel): name = models.CharField('命名', max_length=50, null=True, blank=True) product = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='隶属大工序', on_delete=models.CASCADE, related_name='subproduction_process') + need_combtest = models.BooleanField('需要质检', default=False) sort = models.IntegerField('排序号', default=1) class Meta: diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 8e5eb53..99cdfb6 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -72,6 +72,11 @@ class SubProductionSerializer(serializers.ModelSerializer): model = SubProduction fields = '__all__' +class SubProductionCreateUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = SubProduction + fields = ['name', 'product', 'process', 'need_combtest', 'sort'] + class OtherMaterialSerializer(serializers.ModelSerializer): class Meta: model = SubprodctionMaterial diff --git a/hb_server/apps/mtm/views.py b/hb_server/apps/mtm/views.py index 04f3caa..1bb9e47 100644 --- a/hb_server/apps/mtm/views.py +++ b/hb_server/apps/mtm/views.py @@ -4,7 +4,7 @@ from rest_framework.mixins import CreateModelMixin, ListModelMixin, UpdateModelM from apps.mtm.filters import MaterialFilterSet, TechDocFilterset from apps.mtm.models import Material, Process, RecordForm, RecordFormField, Step, SubprodctionMaterial, TechDoc, UsedStep, SubProduction -from apps.mtm.serializers import InputMaterialSerializer, InputMaterialUpdateSerializer, MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, OtherMaterialSerializer, OutputMaterialSerializer, OutputMaterialUpdateSerializer, ProcessSerializer, RecordFormCreateSerializer, RecordFormDetailSerializer, RecordFormFieldCreateSerializer, RecordFormFieldSerializer, RecordFormFieldUpdateSerializer, RecordFormSerializer, RecordFormUpdateSerializer, StepDetailSerializer, StepSerializer, SubProductionSerializer, SubprodctionMaterialListSerializer, TechDocCreateSerializer, TechDocListSerializer, TechDocUpdateSerializer, UsedStepCreateSerializer, UsedStepListSerializer, UsedStepUpdateSerializer +from apps.mtm.serializers import InputMaterialSerializer, InputMaterialUpdateSerializer, MaterialDetailSerializer, MaterialSerializer, MaterialSimpleSerializer, OtherMaterialSerializer, OutputMaterialSerializer, OutputMaterialUpdateSerializer, ProcessSerializer, RecordFormCreateSerializer, RecordFormDetailSerializer, RecordFormFieldCreateSerializer, RecordFormFieldSerializer, RecordFormFieldUpdateSerializer, RecordFormSerializer, RecordFormUpdateSerializer, StepDetailSerializer, StepSerializer, SubProductionCreateUpdateSerializer, SubProductionSerializer, SubprodctionMaterialListSerializer, TechDocCreateSerializer, TechDocListSerializer, TechDocUpdateSerializer, UsedStepCreateSerializer, UsedStepListSerializer, UsedStepUpdateSerializer from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from rest_framework.decorators import action from rest_framework.response import Response @@ -80,6 +80,10 @@ class SubProductionViewSet(CreateUpdateModelAMixin, ModelViewSet): search_fields = ['name'] serializer_class = SubProductionSerializer ordering = ['sort'] + def get_serializer_class(self): + if self.action in ['create', 'update']: + return SubProductionCreateUpdateSerializer + return super().get_serializer_class() class InputMaterialViewSet(CreateUpdateModelAMixin, ModelViewSet): """ diff --git a/hb_server/apps/pm/migrations/0023_alter_productionplan_order.py b/hb_server/apps/pm/migrations/0023_alter_productionplan_order.py new file mode 100644 index 0000000..4fd00c6 --- /dev/null +++ b/hb_server/apps/pm/migrations/0023_alter_productionplan_order.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.9 on 2022-01-18 00:39 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0010_auto_20211208_1408'), + ('pm', '0022_auto_20211229_1429'), + ] + + operations = [ + migrations.AlterField( + model_name='productionplan', + name='order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='plan_order', to='sam.order', verbose_name='关联订单'), + ), + ] diff --git a/hb_server/apps/pm/views.py b/hb_server/apps/pm/views.py index 3fe8799..3761ead 100644 --- a/hb_server/apps/pm/views.py +++ b/hb_server/apps/pm/views.py @@ -4,7 +4,7 @@ from django.db import transaction from rest_framework import serializers from rest_framework.views import APIView from apps.em.models import Equipment -from apps.em.serializers import EquipmentSerializer +from apps.em.serializers import EquipmentSimpleSerializer from apps.inm.models import MaterialBatch from apps.inm.serializers import MaterialBatchSerializer from apps.mtm.models import Step, SubProduction, SubprodctionMaterial, UsedStep @@ -248,5 +248,5 @@ class ResourceViewSet(GenericViewSet): subproductions = SubProduction.objects.filter(product__id__in=rdata_l, is_deleted=False) steps = Step.objects.filter(usedstep__is_deleted=False, usedstep__subproduction__in=subproductions) equips = Equipment.objects.filter(step_equips__in=steps, is_deleted=False).distinct() - serializer = EquipmentSerializer(instance=equips, many=True) + serializer = EquipmentSimpleSerializer(instance=equips, many=True) return Response(serializer.data) diff --git a/hb_server/apps/sam/serializers.py b/hb_server/apps/sam/serializers.py index db2117e..154b1e7 100644 --- a/hb_server/apps/sam/serializers.py +++ b/hb_server/apps/sam/serializers.py @@ -44,7 +44,7 @@ class ContractCreateUpdateSerializer(serializers.ModelSerializer): class OrderCreateUpdateSerializer(serializers.ModelSerializer): class Meta: model = Order - fields = ['number', 'customer', 'contract', 'product', 'count', 'delivery_date'] + fields = ['customer', 'contract', 'product', 'count', 'delivery_date'] def create(self, validated_data): validated_data['number'] = 'DD' + ranstr(7) @@ -60,6 +60,7 @@ class OrderSerializer(serializers.ModelSerializer): class OrderSimpleSerializer(serializers.ModelSerializer): contract_ = ContractSimpleSerializer(source='contract', read_only=True) + customer_ = CustomerSimpleSerializer(source='customer', read_only=True) class Meta: model = Order fields = '__all__' diff --git a/hb_server/apps/srm/serializers.py b/hb_server/apps/srm/serializers.py index edea221..10e2b94 100644 --- a/hb_server/apps/srm/serializers.py +++ b/hb_server/apps/srm/serializers.py @@ -19,4 +19,8 @@ class PlanGanttSerializer(serializers.ModelSerializer): def get_children(self, obj): subplans = SubProductionPlan.objects.filter(production_plan=obj).order_by('process__number') - return SubplanGanttSerializer(instance=subplans, many=True).data \ No newline at end of file + return SubplanGanttSerializer(instance=subplans, many=True).data + +class ProcessYieldSerializer(serializers.Serializer): + datetime_start = serializers.DateField(label='开始时间', required=False, allow_null=True) + datetime_end = serializers.DateField(label='结束时间', required=False, allow_null=True) \ No newline at end of file diff --git a/hb_server/apps/srm/urls.py b/hb_server/apps/srm/urls.py index cbaf2ed..0d8a992 100644 --- a/hb_server/apps/srm/urls.py +++ b/hb_server/apps/srm/urls.py @@ -3,11 +3,12 @@ from rest_framework import urlpatterns from django.urls import path, include from rest_framework.routers import DefaultRouter -from apps.srm.views import GanttPlan +from apps.srm.views import GanttPlan, ProcessYieldView router = DefaultRouter() urlpatterns = [ path('gantt/plan/', GanttPlan.as_view()), + path('process/yield/', ProcessYieldView.as_view()), path('', include(router.urls)), ] diff --git a/hb_server/apps/srm/views.py b/hb_server/apps/srm/views.py index 4551f30..ec7a398 100644 --- a/hb_server/apps/srm/views.py +++ b/hb_server/apps/srm/views.py @@ -1,9 +1,14 @@ + from django.shortcuts import render from rest_framework import serializers -from rest_framework.generics import ListAPIView +from rest_framework.generics import ListAPIView, CreateAPIView +from rest_framework.views import APIView from rest_framework.response import Response +from apps.mtm.models import Process, Step from apps.pm.models import ProductionPlan, SubProductionPlan -from apps.srm.serializers import PlanGanttSerializer +from apps.srm.serializers import PlanGanttSerializer, ProcessYieldSerializer +from apps.wpm.models import WProduct, WproductFlow +from django.db.models import Count # Create your views here. class GanttPlan(ListAPIView): @@ -15,4 +20,48 @@ class GanttPlan(ListAPIView): queryset = ProductionPlan.objects.filter(is_deleted=False, is_planed=True).prefetch_related('subplan_plan', 'subplan_plan__process') ordering = ['-id'] +class ProcessYieldView(CreateAPIView): + """ + 工序成品率统计 + """ + perms_map = {'get':'*'} + serializer_class = ProcessYieldSerializer + + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + wpfs = WproductFlow.objects.filter(is_lastlog=True) + if vdata.get('datetime_start', None): + wpfs = wpfs.filter(update_time__gte = vdata.get('datetime_start')) + if vdata.get('datetime_end', None): + wpfs = wpfs.filter(update_time__lte = vdata.get('datetime_end')) + # 根据产品日志记录进行聚合 + count_ok_g = list(wpfs.filter(act_state__in=[WProduct.WPR_ACT_STATE_INM, + WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_SELLED]).values('step__process__id').annotate(count_ok=Count('id'))) + count_notok_g = list( + ( + wpfs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP]).exclude(step__process__id=1) + | wpfs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP], + step__process__id=1).exclude(number=None) + )\ + .values('step__process__id', + ).annotate(count_notok=Count('id'))) + ret = [] + process_l = list(Process.objects.filter(is_deleted=False).order_by('number').values('id', 'name')) + for i in process_l: + ret_item = {'id':i['id'], 'name':i['name'], 'count_ok':0, 'count_notok':0, 'rate':1} + for m in count_ok_g: + if m['step__process__id'] == ret_item['id']: + ret_item['count_ok'] = m['count_ok'] + for n in count_notok_g: + if n['step__process__id'] == ret_item['id']: + ret_item['count_notok'] = n['count_notok'] + rate = (ret_item['count_ok']/(ret_item['count_ok']+ret_item['count_notok'])) \ + if ret_item['count_ok']+ret_item['count_notok']>0 else 1 + ret_item['rate'] = rate + ret.append(ret_item) + return Response(ret) + diff --git a/hb_server/apps/system/migrations/0004_user_is_atwork.py b/hb_server/apps/system/migrations/0004_user_is_atwork.py new file mode 100644 index 0000000..b512387 --- /dev/null +++ b/hb_server/apps/system/migrations/0004_user_is_atwork.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-21 05:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('system', '0003_auto_20210812_0909'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='is_atwork', + field=models.BooleanField(default=False, verbose_name='当前在岗'), + ), + ] diff --git a/hb_server/apps/system/models.py b/hb_server/apps/system/models.py index c773fcf..e77eae3 100644 --- a/hb_server/apps/system/models.py +++ b/hb_server/apps/system/models.py @@ -116,6 +116,7 @@ class User(AbstractUser): superior = models.ForeignKey( 'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='上级主管') roles = models.ManyToManyField(Role, blank=True, verbose_name='角色') + is_atwork = models.BooleanField('当前在岗', default=False) class Meta: verbose_name = '用户信息' diff --git a/hb_server/apps/system/serializers.py b/hb_server/apps/system/serializers.py index 10636ea..8d644b3 100644 --- a/hb_server/apps/system/serializers.py +++ b/hb_server/apps/system/serializers.py @@ -141,7 +141,9 @@ class UserListSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'name', 'phone', 'email', 'position', - 'username', 'is_active', 'date_joined', 'dept_name', 'dept', 'roles', 'avatar', 'roles_name'] + 'username', 'is_active', 'date_joined', + 'dept_name', 'dept', 'roles', 'avatar', + 'roles_name', 'is_atwork'] @staticmethod def setup_eager_loading(queryset): diff --git a/hb_server/apps/wpm/filters.py b/hb_server/apps/wpm/filters.py index c8392d3..54454f8 100644 --- a/hb_server/apps/wpm/filters.py +++ b/hb_server/apps/wpm/filters.py @@ -41,6 +41,12 @@ class WProductFilterSet(filters.FilterSet): def filter_tag(self, queryset, name, value): if value == 'no_scrap': queryset = queryset.exclude(act_state=WProduct.WPR_ACT_STATE_SCRAP) + elif value == 'notok': + queryset = queryset.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP])\ + .exclude(step__process__id = 1) # 不算冷加工的报废 + elif value == 'ok': + queryset = queryset.filter(act_state__in=[WProduct.WPR_ACT_STATE_INM, + WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_SELLED]) return queryset diff --git a/hb_server/apps/wpm/migrations/0049_operationequip_state.py b/hb_server/apps/wpm/migrations/0049_operationequip_state.py new file mode 100644 index 0000000..5541350 --- /dev/null +++ b/hb_server/apps/wpm/migrations/0049_operationequip_state.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-18 00:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0048_operationwproduct_place'), + ] + + operations = [ + migrations.AddField( + model_name='operationequip', + name='state', + field=models.PositiveSmallIntegerField(choices=[(0, '完好'), (1, '限用'), (2, '在修'), (3, '禁用')], default=0, verbose_name='当前设备状态'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0050_alter_operationequip_state.py b/hb_server/apps/wpm/migrations/0050_alter_operationequip_state.py new file mode 100644 index 0000000..a04569a --- /dev/null +++ b/hb_server/apps/wpm/migrations/0050_alter_operationequip_state.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2022-01-20 02:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wpm', '0049_operationequip_state'), + ] + + operations = [ + migrations.AlterField( + model_name='operationequip', + name='state', + field=models.PositiveSmallIntegerField(choices=[(10, '完好'), (20, '限用'), (30, '在修'), (40, '禁用')], default=10, verbose_name='当前设备状态'), + ), + ] diff --git a/hb_server/apps/wpm/migrations/0051_auto_20220120_1541.py b/hb_server/apps/wpm/migrations/0051_auto_20220120_1541.py new file mode 100644 index 0000000..9a0c6fb --- /dev/null +++ b/hb_server/apps/wpm/migrations/0051_auto_20220120_1541.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.9 on 2022-01-20 07:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0010_auto_20211208_1408'), + ('wpm', '0050_alter_operationequip_state'), + ] + + operations = [ + migrations.AddField( + model_name='wproduct', + name='to_order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sam.order', verbose_name='指派的订单'), + ), + migrations.AddField( + model_name='wproductflow', + name='to_order', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sam.order', verbose_name='指派的订单'), + ), + ] diff --git a/hb_server/apps/wpm/models.py b/hb_server/apps/wpm/models.py index dd870b4..c52a061 100644 --- a/hb_server/apps/wpm/models.py +++ b/hb_server/apps/wpm/models.py @@ -118,7 +118,8 @@ class WProduct(CommonAModel): on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_test') ticket = models.ForeignKey('wf.ticket', verbose_name='当前工单', on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_ticket') - + + to_order = models.ForeignKey('sam.order', verbose_name='指派的订单', null=True, blank=True, on_delete = models.CASCADE) is_mtested = models.BooleanField('是否军检', default=False) is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True) remark_mtest = models.TextField('军检备注', null=True, blank=True) @@ -192,7 +193,7 @@ class WproductFlow(CommonAModel): on_delete=models.SET_NULL, null=True, blank=True) ticket = models.ForeignKey('wf.ticket', verbose_name='当前工单', on_delete=models.SET_NULL, null=True, blank=True) - + to_order = models.ForeignKey('sam.order', verbose_name='指派的订单', null=True, blank=True, on_delete = models.CASCADE) is_mtested = models.BooleanField('是否军检', default=False) is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True) remark_mtest = models.TextField('军检备注', null=True, blank=True) @@ -336,4 +337,5 @@ class OperationEquip(BaseModel): Operation, verbose_name='关联操作', on_delete=models.CASCADE, related_name='oe_operation') equip = models.ForeignKey(Equipment, verbose_name='生产设备', on_delete=models.CASCADE, related_name='oe_equip') + state = models.PositiveSmallIntegerField('当前设备状态', choices=Equipment.state_choices, default=Equipment.EQUIP_STATE_OK) remark = models.TextField('备注', null=True, blank=True) diff --git a/hb_server/apps/wpm/serializers.py b/hb_server/apps/wpm/serializers.py index 8956eb3..3181088 100644 --- a/hb_server/apps/wpm/serializers.py +++ b/hb_server/apps/wpm/serializers.py @@ -19,6 +19,7 @@ from apps.system.models import User from apps.system.serializers import UserSimpleSerializer from apps.wpm.models import Operation, OperationEquip, OperationMaterial, OperationWproduct, Pick, WMaterial, WProduct, OperationRecord, OperationRecordItem, WprouctTicket from django.db import transaction +from apps.sam.models import Order class PickHalfSerializer(serializers.Serializer): id = serializers.PrimaryKeyRelatedField(queryset=SubProductionProgress.objects.all(), label='子计划进度ID') @@ -155,6 +156,7 @@ class WProductListSerializer(serializers.ModelSerializer): subproduction_plan_ = SubproductionPlanSimpleSerializer(source='subproduction_plan', read_only=True) warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True) children = serializers.SerializerMethodField() + to_order_ = OrderSimpleSerializer(source='to_order', read_only=True) class Meta: model = WProduct fields = '__all__' @@ -560,4 +562,8 @@ class CuttingListSerializer(serializers.ModelSerializer): class WproductMtestSerializer(serializers.ModelSerializer): class Meta: model = WProduct - fields = ['remark_mtest', 'is_mtestok'] \ No newline at end of file + fields = ['remark_mtest', 'is_mtestok'] + +class WproductToOrderSerializer(serializers.Serializer): + wproducts = serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all(), many=True) + order = serializers.PrimaryKeyRelatedField(queryset=Order.objects.all()) \ No newline at end of file diff --git a/hb_server/apps/wpm/services.py b/hb_server/apps/wpm/services.py index 3734106..d880f7f 100644 --- a/hb_server/apps/wpm/services.py +++ b/hb_server/apps/wpm/services.py @@ -54,7 +54,8 @@ class WpmServies(object): elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and wproduct.material.type == Material.MA_TYPE_GOOD: # 成品检验 wproduct.act_state = WProduct.WPR_ACT_STATE_TOFINALTEST - elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and wproduct.step.type == Step.STEP_TYPE_COMB: # 夹层检验 + elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and \ + wproduct.subproduction_plan.subproduction.need_combtest : # 配置中需要质检 wproduct.act_state = WProduct.WPR_ACT_STATE_TOCOMBTEST else: wproduct.act_state = WProduct.WPR_ACT_STATE_OK @@ -113,7 +114,11 @@ class WpmServies(object): objs = WproductFlow.objects.filter(subproduction_plan=sp, is_lastlog=True) count_ok = objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_INM, WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_SELLED]).count() - count_notok = objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP]).count() + count_notok = ( + objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP]).exclude(step__process__id=1) + | objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP], + step__process__id=1).exclude(number=None) + ).count() count_real = objs.exclude(act_state__in=[WProduct.WPR_ACT_STATE_TORETEST, WProduct.WPR_ACT_STATE_DOWAIT, WProduct.WPR_ACT_STATE_DOING]).count() ins = SubProductionProgress.objects.filter(subproduction_plan=sp, diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index 28d8a12..e300f40 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -16,7 +16,7 @@ from rest_framework.decorators import action from apps.wf.models import Workflow from apps.wpm.filters import CuttingFilterSet, OperationRecordFilterSet, WMaterialFilterSet, WProductFilterSet from apps.wpm.models import OperationEquip, OperationWproduct, Pick, PickWproduct, WMaterial, WProduct, Operation, \ - OperationMaterial, OperationRecord, OperationRecordItem, WprouctTicket + OperationMaterial, OperationRecord, OperationRecordItem, WproductFlow, WprouctTicket from apps.wpm.serializers import CuttingListSerializer, OperationEquipListSerializer, OperationEquipUpdateSerializer, \ OperationMaterialCreate1ListSerailizer, OperationMaterialCreate1Serailizer, OperationMaterialCreate2ListSerailizer, \ @@ -27,7 +27,7 @@ from apps.wpm.serializers import CuttingListSerializer, OperationEquipListSerial PickSerializer, OperationInitSerializer, OperationSubmitSerializer, ScrapSerializer, WMaterialListSerializer, \ WProductCardSerializer, WProductDetailSerializer, WProductListSerializer, \ WpmTestFormInitSerializer, WproductMtestSerializer, WproductPutInSerializer, \ - WproductPutInsSerializer, WproductTicketListSerializer + WproductPutInsSerializer, WproductTicketListSerializer, WproductToOrderSerializer from rest_framework.response import Response from django.db import transaction @@ -148,7 +148,7 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): """ perms_map = {'*': '*'} queryset = WProduct.objects.select_related('step', 'material', - 'subproduction_plan', 'warehouse').prefetch_related('wproduct_child') + 'subproduction_plan', 'warehouse', 'to_order').prefetch_related('wproduct_child') serializer_class = WProductListSerializer filterset_class = WProductFilterSet search_fields = ['number'] @@ -441,7 +441,31 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): ret.append([str(index + 1), item['step_name'], item['actions']]) return Response(ret) - + @action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproductToOrderSerializer) + @transaction.atomic + def to_order(self, request, pk=None): + """ + 指派发货订单 + """ + serializer = WproductToOrderSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + wps = WProduct.objects.filter(id__in = [i.id for i in vdata.get('wproducts')]) + wp = wps.first() + order = vdata['order'] + if wp.material != order.product: + raise exceptions.ValidationError('所选订单与产品不符') + for i in wps: + if i.material != wp.material and i.material.type != Material.MA_TYPE_GOOD and i.act_state \ + not in [WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_INM]: + raise exceptions.ValidationError('所选产品错误') + for i in wps: + i.to_order = order + i.update_by = request.user + i.save() + WpmServies.add_wproduct_flow_log(i,change_str='to_order') + return Response() + class WproductTicketViewSet(ListModelMixin, GenericViewSet): """ 玻璃审批工单 @@ -559,6 +583,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd ope = OperationEquip() ope.operation = op ope.equip = i + ope.state = i.state ope.save() # 查询所需的工具工装 for i in SubprodctionMaterial.objects.filter(type=SubprodctionMaterial.SUB_MA_TYPE_TOOL, diff --git a/hb_server/utils/response.py b/hb_server/utils/response.py index f4379f4..0411547 100644 --- a/hb_server/utils/response.py +++ b/hb_server/utils/response.py @@ -47,17 +47,20 @@ class FitJSONRenderer(JSONRenderer): """ response_body = BaseResponse() response = renderer_context.get("response") - response_body.code = response.status_code - if response_body.code >= 400: # 响应异常 - response_body.data = data # data里是详细异常信息 - prefix = "" - if isinstance(data, dict): - prefix = list(data.keys())[0] - data = data[prefix] - if isinstance(data, list): - data = data[0] - response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert + status_code = response.status_code # Http状态异常码 + if status_code >= 400: # 如果http响应异常 + if isinstance(data, dict) and 'code' in data: # 如果自定义了异常码 + response_body = data + else: + response_body.data = data # data里是详细异常信息 + prefix = "" + if isinstance(data, dict): + prefix = list(data.keys())[0] + data = data[prefix] + elif isinstance(data, list): + data = data[0] + response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert else: response_body.data = data - renderer_context.get("response").status_code = 200 # 统一成200响应,用code区分 + renderer_context.get("response").status_code = 200 # 统一成200响应, 可用body里code区分业务异常 return super(FitJSONRenderer, self).render(response_body.dict, accepted_media_type, renderer_context)