Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop

This commit is contained in:
shilixia 2022-02-24 08:32:48 +08:00
commit 0e2cc60409
23 changed files with 1097 additions and 105 deletions

View File

@ -105,6 +105,23 @@ export function createConvert(data) {
data
})
}
//首件检查表初始化
export function firstTestInit(id,data) {
return request({
url: `/pm/subproduction_plan/${id}/first_test_init/`,
method: 'post',
data
})
}
//首件检查责任人审核
export function firstAudit(id,data) {
return request({
url: `/pm/subproduction_plan/${id}/first_audit/`,
method: 'post',
data
})
}
//任务终止
export function planstop(id) {
return request({
@ -120,4 +137,4 @@ export function plantoggle(id) {
method: 'put',
})
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

View File

@ -239,13 +239,15 @@
let imag= this.formData.filter(item => {
return item.field_type === 'draw';
});
that.img = new Image();
that.img.crossOrigin = 'anonymous';
let value = imag[0].field_value?imag[0].field_value:imag[0].draw_template;
that.img = 'http://47.95.0.242:2222'+value;
setTimeout(function(){
that.canvasInit();
},500);
if(imag.length>0){
that.img = new Image();
that.img.crossOrigin = 'anonymous';
let value = imag[0].field_value?imag[0].field_value:imag[0].draw_template;
that.img = 'http://47.95.0.242:2222'+value;
setTimeout(function(){
that.canvasInit();
},500);
}
},
data(){
return{
@ -695,7 +697,7 @@
that.field = []; //检查项目
let submit = isSubmit=='1'?false:true;
that.formData.forEach((item) => {
let field_value = null;
let field_value;
if(item.field_type==='int'){
field_value = parseInt(that.checkForm[item.field_key])
}else if(item.field_type==='float'){

View File

@ -0,0 +1,190 @@
<template>
<div class="faceLoginWrap">
<div style="height: 500px;">
<div class="video-box">
<video id="video" width="500" height="500" preload autoplay loop muted></video>
<canvas id="canvas" width="500" height="500"></canvas>
</div>
<canvas id="screenshotCanvas" width="500" height="500"></canvas>
</div>
</div>
</template>
<script>
import tracking from '@/assets/tracking/build/tracking-min.js';
import {faceLogin} from "@/api/testModel";
import '@/assets/tracking/build/data/face-min.js';
import {createrecordform,updaterecordform} from "@/api/mtm";
export default {
props: {
dialogType: {
type:String,
default:"new"
},
recordform: {
type:Object,
default:null
}
},
data() {
return {
video: null,
screenshotCanvas: null,
uploadLock: false // 上传锁
}
},
mounted() {
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');
// 实例化tracker对象
let tracker = new window.tracking.ObjectTracker('face');
tracker.setInitialScale(4);
tracker.setStepSize(2);
tracker.setEdgesDensity(0.1);
//tracker对象和标签关联
window.tracking.track('#video', tracker, {
camera: true
});
let _this = this;
//添加事件
tracker.on('track', function (event) {
// 检测出人脸 绘画人脸位置
context.clearRect(0, 0, canvas.width, canvas.height);
// 给每个人脸绘制对应的框
event.data.forEach(function (rect) {
context.strokeStyle = '#0764B7';
context.strokeRect(rect.x, rect.y, rect.width, rect.height);
// 避免重复发送请求
if(!_this.uploadLock){
_this.uploadLock = true;
// 上传图片
_this.screenshotAndUpload();
}
});
});
},
// 上传图片
screenshotAndUpload() {
let that = this;
// 绘制当前帧图片转换为base64格式
let canvas = this.screenshotCanvas;
let video = this.video;
let ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
let base64Img = canvas.toDataURL('image/jpeg');
let img = base64Img.split(',')[1];
let imgData = {base64:img};
faceLogin(imgData).then((res) => {
if (res.code >= 200) {
let item= {name:res.data.username,token:res.data.access};
that.$emit('func',item);
that.$message.success("身份认证成功!");
}else{
// 打开锁
that.uploadLock = false;
this.$message.error(res.msg);
}
}).catch(()=>{
// 打开锁
that.uploadLock = false;
// this.$message.error('面部识别失败请重新验证');
});
},
closeCamera () {
this.video.srcObject.getTracks()[0].stop();
},
}
}
</script>
<style scoped>
.faceLoginWrap{
padding: 50px;
width: 600px;
height: 600px;
background: #000000;
margin:50px auto 0 auto;
box-sizing: border-box;
}
#screenshotCanvas {
display: none;
}
.video-box {
margin: auto;
position: relative;
width: 500px;
height: 500px;
background: #000000;
text-align: center;
}
#video {
object-fit: fill;
-webkit-border-radius: 250px;
-moz-border-radius: 250px;
border-radius: 250px;
}
video, canvas {
position: absolute;
top: 0;
left: 0;
}
</style>

View File

@ -1,16 +1,16 @@
<template>
<div style="width: 100%;height: 620px;">
<div>
<div class="faceLoginWrap">
<div style="height: 500px;">
<div class="video-box">
<video id="video" width="749" height="620" preload autoplay loop muted></video>
<canvas id="canvas" width="749" height="620"></canvas>
<video id="video" width="500" height="500" preload autoplay loop muted></video>
<canvas id="canvas" width="500" height="500"></canvas>
</div>
<canvas id="screenshotCanvas" width="749" height="620"></canvas>
<canvas id="screenshotCanvas" width="500" height="500"></canvas>
</div>
</div>
</template>
<script>
import { clockRecord } from '@/api/hrm'
import {clockRecord} from '@/api/hrm'
import tracking from '@/assets/tracking/build/tracking-min.js';
import '@/assets/tracking/build/data/face-min.js';
@ -26,7 +26,7 @@
this.openTheCamera();
},
methods: {
openTheCamera () {
openTheCamera() {
this.$nextTick(function () {
let _this = this;
this.video = document.getElementById('video');
@ -52,7 +52,10 @@
})
}
}
let constraints = { audio: false, video: { width: this.videoWidth, height: this.videoHeight, transform: 'scaleX(-1)' } };
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) {
@ -96,7 +99,7 @@
context.strokeRect(rect.x, rect.y, rect.width, rect.height);
// window.plot(rect.x, rect.y, rect.width, rect.height+20);
// 避免重复发送请求
if(!_this.uploadLock){
if (!_this.uploadLock) {
_this.uploadLock = true;
// 上传图片
_this.screenshotAndUpload();
@ -115,31 +118,40 @@
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
let base64Img = canvas.toDataURL('image/jpeg');
let img = base64Img.split(',')[1];
let imgData = {base64:img};
let imgData = {base64: img};
clockRecord(imgData).then((res) => {
if (res.code === 200&&res.data.id) {
this.$message.success(res.data.name+'签到成功!');
setTimeout(()=>{
if (res.code === 200 && res.data.id) {
this.$message.success(res.data.name + '签到成功!');
setTimeout(() => {
that.uploadLock = false;
},3000)
}else{
}, 3000)
} else {
// 打开锁
that.uploadLock = false;
this.$message.error(res.msg);
}
}).catch(()=>{
}).catch(() => {
// 打开锁
that.uploadLock = false;
// this.$message.error('面部识别失败请重新验证');
});
},
closeCamera () {
closeCamera() {
this.video.srcObject.getTracks()[0].stop();
},
}
}
</script>
<style scoped>
.faceLoginWrap{
padding: 50px;
width: 600px;
height: 600px;
background: #000000;
margin:50px auto 0 auto;
box-sizing: border-box;
}
#screenshotCanvas {
display: none;
}
@ -147,8 +159,13 @@
.video-box {
margin: auto;
position: relative;
/*width: 90%;
height: 90%;*/
}
#video {
object-fit: fill;
-webkit-border-radius: 250px;
-moz-border-radius: 250px;
border-radius: 250px;
}
video, canvas {

View File

@ -226,8 +226,7 @@ export const asyncRoutes = [
name: 'operation',
component: () => import('@/views/wpm/operation'),
meta: { title: '车间操作', icon: 'workshopOperation', perms: ['index_manage'] }
}
,
},
{
path: 'operationdo/:id',
name: 'operationdo',
@ -240,13 +239,18 @@ export const asyncRoutes = [
name: 'need',
component: () => import('@/views/wpm/need'),
meta: { title: '过程检验', icon: 'processTest', perms: ['index_manage'] }
}
,
},
{
path: 'productjy',
name: 'productjy',
component: () => import('@/views/wpm/productjy'),
meta: { title: '成品检验', icon: 'finishedCheck', perms: ['index_manage'] }
},
{
path: 'firstCheck',
name: 'firstCheck',
component: () => import('@/views/wpm/firstCheck'),
meta: { title: '首件确认', icon: 'finishedCheck', perms: ['index_manage'] }
}
]

View File

@ -20,7 +20,7 @@ service.interceptors.request.use(
// please modify it according to the actual situation
config.headers['Authorization'] = 'Bearer ' + getToken()
}
let data = config.params;
let data = config.data;
/*debugger;
console.log(data)*/
if(data){

View File

@ -1,8 +1,8 @@
<template>
<div class="login">
<div class="login-form" style="display: flex;justify-content: space-between">
<div style="width: 250px;border-right: 1px dashed #409EFF;">
<img src="./../../assets/face.png" style="margin-top: 65px;" @click="takePhoto()">
<div class="login-form">
<div class="faceLoginBtnWrap">
<img class="faceLoginBtn" src="./../../assets/face.png" @click="takePhoto()">
</div>
<div style="width: 360px;">
<h3 class="title">航玻生产管理系统</h3>
@ -245,7 +245,7 @@
justify-content: center;
align-items: center;
height: 100%;
background-image: url("../../assets/beijing.jpg");
background-image: url("../../assets/bg-login.png");
background-size: cover;
}
.title {
@ -263,6 +263,10 @@
background: #ffffff;
width: 700px;
padding: 25px;
display: flex;
justify-content: space-between;
box-shadow: 0 0 10px 5px #eeeeee;
.el-input {
height: 45px;
input {
@ -275,6 +279,15 @@
margin-left: 2px;
}
}
.faceLoginBtnWrap{
width: 250px;
border-right: 1px dashed #409EFF;
text-align: center;
}
.faceLoginBtn{
width: 150px;
margin-top: 90px;
}
#passwordInput{
padding-right: 35px;
}
@ -309,6 +322,6 @@
}
.testTracking{
width:100%;
height: 620px;
height: 500px;
}
</style>

View File

@ -0,0 +1,565 @@
<template>
<div class="app-container">
<el-card>
<div class="tabButtonWrap">
<div
v-for="(item,$index) in processOption"
:key="item.id"
class="tabButton"
:class="{activeTab:activeIndex===$index}"
@click="changeIndex(item,$index)"
>
{{item.name}}
</div>
</div>
<el-table
:data="subPlanList"
fit
style="width: 100%"
height="100"
stripe
border
v-el-height-adaptive-table="{bottomOffset: 50}"
>
<el-table-column type="index" width="50"/>
<el-table-column label="子计划编号" min-width="70px" show-overflow-tooltip>
<template slot-scope="scope">{{scope.row.number}}</template>
</el-table-column>
<el-table-column label="产品名称" min-width="120px" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.product_.name }}</template>
</el-table-column>
<el-table-column label="产品型号">
<template slot-scope="scope">{{ scope.row.product_.specification }}</template>
</el-table-column>
<el-table-column label="子工序" min-width="120px" show-overflow-tooltip>
<template slot-scope="scope" v-if="scope.row.steps">
<el-tag v-for="item in scope.row.steps"
:key="item.number"
:label="item.name"
:value="item.number">{{item.name}}
</el-tag>
</template>
</el-table-column>
<el-table-column label="生产车间">
<template slot-scope="scope">{{ scope.row.workshop_.name }}</template>
</el-table-column>
<el-table-column label="生产数量" prop="count">
</el-table-column>
<el-table-column label="开工时间" prop="start_date">
</el-table-column>
<el-table-column label="完工时间" prop="end_date">
</el-table-column>
<el-table-column label="检验状态">
<template slot-scope="scope">
<span v-if="scope.row.leader_1!==null&&scope.row.leader_2!==null&&scope.row.leader_3!==null">已完成</span>
<span v-else>未完成</span>
</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
width="120px"
fixed="right"
>
<template slot-scope="scope">
<el-link
v-if="scope.row.first_test===null"
type="primary"
@click="handleTest(scope)"
>
首件检验
</el-link>
<el-link
v-else-if="scope.row.first_test!==null&&!scope.row.first_test_.is_submited"
type="primary"
@click="handleTestContinue(scope)"
>
检验
</el-link>
<el-link
v-else-if="scope.row.first_test_.is_submited&&(scope.row.leader_1===null||scope.row.leader_2===null||scope.row.leader_3===null)"
type="primary"
@click="handleSelectclick(scope,'0')"
>
首件审批
</el-link>
<el-link
v-else-if="scope.row.leader_1!==null&&scope.row.leader_2!==null&&scope.row.leader_3!==null"
type="primary"
@click="handleSelectclick(scope,'1')"
>
查看
</el-link>
</template>
</el-table-column>
</el-table>
<pagination
:total="count"
:page.sync="listQuery.page"
:limit.sync="listQuery.page_size"
@pagination="getsList"
/>
</el-card>
<!--物料检查表&&-->
<el-dialog title="首件确认检查表" :close-on-click-modal="false" :visible.sync="listVisible">
<el-select style="width: 100%" v-model="recordForm" placeholder="请选择" @change="recordFormChange">
<el-option
v-for="item in recordFormList"
:key="item.name"
:label="item.name"
:value="item.id"
>
</el-option>
</el-select>
<div slot="footer" class="dialog-footer">
<el-button @click="listVisible = false">
</el-button>
<el-button type="primary" @click="selectedRecordForm()">填写检查项目</el-button>
</div>
</el-dialog>
<!--检查表显示-->
<el-dialog
width="60%"
:title="formName"
:visible.sync="recordVisible"
:close-on-click-modal="false"
@close="recordCancel"
>
<customForm
v-if="recordVisible"
:results="fieldList"
:hasPicture="hasPicture"
:formID="recordForm"
:wproduct="recordFormQuery.material"
:recordId="recordId"
:isDisabled="isDisabled"
:isMidTesting="is_midtesting"
@recordSubmit="recordSubmit"
@recordSave="recordSave"
@recordCancel="recordCancel"
/>
</el-dialog>
<!--已完成检查表查后的审核-->
<el-dialog
title="首件确认审核"
:visible.sync="reviewVisible"
:close-on-click-modal="false"
>
<el-row>
<el-col v-for="item in fieldList" :key="item.id+item.id" :span="12">
<div class="items" v-if="item.field_type!=='draw'&&item.field_value!==null&&item.field_value!==''">
<span class="itemLabel">{{item.field_name}}</span>
<span>{{item.field_value}}</span>
</div>
</el-col>
<el-col :span="12" v-if="achieve">
<div class="items">
<span class="itemLabel">工序负责人</span>
<span>{{leader_1}}</span>
</div>
</el-col>
<el-col :span="12" v-if="achieve">
<div class="items">
<span class="itemLabel">技术负责人</span>
<span>{{leader_2}}</span>
</div>
</el-col>
<el-col :span="12" v-if="achieve">
<div class="items">
<span class="itemLabel">总检</span>
<span>{{leader_3}}</span>
</div>
</el-col>
<el-col :span="12" v-if="achieve">
<div class="items">
<span class="itemLabel">首件检查时间</span>
<span>{{update_time}}</span>
</div>
</el-col>
<el-col :span="12" v-if="achieve">
<div class="items">
<span class="itemLabel">首件审批时间</span>
<span>{{first_sign_time}}</span>
</div>
</el-col>
<el-col v-for="item in fieldList" :key="item.id" :span="24">
<div class="items" v-if="item.field_type==='draw'" style="height: 400px">
<span class="itemLabel">{{item.field_name}}</span>
<img style="width: 45%;vertical-align: text-top;" :src="'http://47.95.0.242:2222'+item.field_value"/>
</div>
</el-col>
</el-row>
<el-row class="reviewWrap" v-if="!achieve">
<div class="reviewBlock">
<span>工序负责人:</span>
<div class="directorName">{{leader_1}}</div>
<span class="reviewBtn" @click="directorConfirm('leader_1')">人脸验证</span>
</div>
<div class="reviewBlock">
<span>技术负责人:</span>
<div class="directorName">{{leader_2}}</div>
<span class="reviewBtn" @click="directorConfirm('leader_2')">人脸验证</span>
</div>
<div class="reviewBlock">
<span>总检:</span>
<div class="directorName">{{leader_3}}</div>
<span class="reviewBtn" @click="directorConfirm('leader_3')">人脸验证</span>
</div>
</el-row>
</el-dialog>
<el-dialog :visible.sync="limitedPhoto" @close="closeCamera" id="loginFaceWrap">
<div style="font-size: 28px;color: #333333;text-align: center;font-weight: bold;">审核人员确认</div>
<div class="testTracking">
<faceLogin v-if="limitedPhoto" ref="faceTracking" name="faceLogin" @func="getMsgFormSon"></faceLogin>
</div>
</el-dialog>
</div>
</template>
<script>
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination";
import customForm from '@/components/customForm/index';
import faceLogin from '@/components/faceLogin/review.vue';
import {getProcessList,getrecordformList} from "@/api/mtm";
import {getsubproductionplanList,firstTestInit,firstAudit} from "@/api/pm";
import {getTestRecordItem,putTestRecordItem,subTestRecordItem} from "@/api/qm";
export default {
name: "firstCheck",
components: {Pagination,faceLogin,customForm},
data() {
return {
count: 0,
activeIndex: 0,
fieldList: [],
subPlanList: [],
processOption: [],
recordFormList: [],
listQuery:{
page:1,
page_size:20,
process: null,
},
recordFormQuery:{
type:50,
enabled:true,
material:null
},
planId:null,
leader:null,
recordId: null,
leader_1: null,
leader_2: null,
leader_3: null,
recordForm:null,
achieve:false,
hasPicture:false,
isDisabled: false,
listLoading:false,
listVisible:false,
limitedPhoto:false,
reviewVisible:false,
recordVisible:false,
is_midtesting:false,
formName:'首件确认检查表',
update_time:'',
first_sign_time:'',
}
},
methods: {
checkPermission,
//工序渲染
getProcessList() {
let that = this;
getProcessList({page: 0}).then((response) => {
if (response.data) {
that.processOption = response.data;
that.listQuery.process = response.data[0].id;
that.getTableData();
}
});
},
//工序转换
changeIndex(item,index) {
this.activeIndex = index;
this.listQuery.process = item.id;
this.getTableData();
},
//获取table数据
getTableData() {
this.listLoading = true;
this.listQuery.production_plan = this.id;
getsubproductionplanList(this.listQuery).then((response) => {
if (response.data) {
this.subPlanList = response.data.results;
this.count = response.data.count;
}
this.listLoading = false;
});
},
//分页
getsList(){},
//第一次点击首件检验
handleTest(scope){
let that = this;
that.planId = scope.row.id;//子生产计划ID
that.recordFormQuery.material = scope.row.product;
that.recordForm = null;
getrecordformList(that.recordFormQuery).then((response) => {
if (response.data) {
that.recordformList = response.data.results;
if (that.recordformList.length === 1) {
that.recordForm = that.recordformList[0].id;
that.formName = that.recordformList[0].name;
that.selectedRecordForm();
} else {
//弹出列表选择框
that.listVisible = true;
}
}
});
},
//首件审批
handleSelectclick(scope,index){
let that = this;
this.reviewVisible = true;
that.planId = scope.row.id;
that.leader_1 = scope.row.leader_1_?scope.row.leader_1_.name:null;
that.leader_2 = scope.row.leader_2_?scope.row.leader_2_.name:null;
that.leader_3 = scope.row.leader_3_?scope.row.leader_3_.name:null;
that.first_sign_time = scope.row.first_sign_time?scope.row.first_sign_time:'';
getTestRecordItem(scope.row.first_test).then((res) => {
that.formName = res.data.form_.name;
that.update_time = res.data.update_time;
let fieldList = res.data.record_data;
that.fieldList = [...fieldList];
let arr = fieldList.filter(item => {
return item.field_type === 'draw'
});
if (arr.length > 0) {
that.hasPicture = true;
}
that.$nextTick(() => {
if(index==='1'){
this.achieve = true;
}else{
this.achieve = false;
}
that.reviewVisible = true;
});
})
},
//选择物料检查表
recordFormChange() {
let that = this;
let arr = this.recordFormList.filter(item => {
return item.id === that.recordForm;
});
that.formName = arr[0].name;
},
//根据选择的表渲染检查项目
selectedRecordForm() {
let that = this;
this.listVisible = false;
that.fieldList = [];
if (that.recordForm != "") {
firstTestInit(that.planId,{ form: that.recordForm}).then((response) => {
if (response.code===200) {
that.hasPicture = false;
that.recordId = response.data.id;//记录id
that.formName = response.data.form_.name;//引用的检查表id
let fieldList = response.data.record_data;
that.fieldList = [...fieldList];
let arr = fieldList.filter(item => {
return item.field_type === 'draw'
});
if (arr.length > 0) {
that.hasPicture = true;
}
that.$nextTick(() => {
that.recordVisible = true;
});
}
});
} else this.$message.error("请选择检查表!");
},
//第一次保存提交检查项目
recordCancel() {
this.recordVisible = false;
this.listVisible = false;
this.getTableData();
},
/*关闭相机*/
closeCamera () {
this.$refs.faceTracking.closeCamera();
// this.thisVideo.srcObject.getTracks()[0].stop();
},
//保存首件检查
recordSave(value) {
let that = this;
let id = value.id;
let params = {};
params.record_data = value.record_data;
params.is_testok = value.is_testok;
putTestRecordItem(id, params).then((res) => {
if (res.code >= 200) {
that.recordVisible = false;
that.getTableData();
} else {
that.$message.error(res.msg)
}
}).catch((err) => {
// console.error(err);
that.$message.error(err)
});
},
//提交首件检查
recordSubmit(value) {
let that = this;
let id = value.id;
let params = {};
params.record_data = value.record_data;
params.is_testok = value.is_testok;
putTestRecordItem(id, params).then((res) => {
if (res.code >= 200) {
subTestRecordItem(id, params).then((res) => {
if (res.code >= 200) {
that.recordVisible = false;
that.getTableData();
}
});
} else {
that.$message.error(res.msg)
}
}).catch((err) => {
that.$message.error(err);
});
},
//再次点击首件检验
handleTestContinue(scope) {
let that = this;
that.fieldList = [];
that.recordVisible = false;
that.planId = scope.row.id;
that.recordId = scope.row.first_test;
that.recordForm = scope.row.first_test_.form;
getTestRecordItem(scope.row.first_test).then((res) => {
that.formName = res.data.form_.name;
let fieldList = res.data.record_data;
that.fieldList = [...fieldList];
let arr = fieldList.filter(item => {
return item.field_type === 'draw'
});
if (arr.length > 0) {
that.hasPicture = true;
}
that.$nextTick(() => {
that.recordVisible = true;
});
})
/*getrffieldList({form: this.recordform,enabled:true, page: 1, page_size: 100}).then((response) => {
if (response.data) {
that.hasPicture = false;
let fieldList = response.data.results;
that.fieldList = [...fieldList];
let arr = fieldList.filter(item => {
return item.field_type === 'draw'
});
if (arr.length > 0) {
that.hasPicture = true;
}
}
});*/
},
//点击人脸验证
directorConfirm(index){
this.leader = index;
this.limitedPhoto = true;
},
//获取人脸数据
getMsgFormSon(data){
let that =this;
if(that.leader==='leader_1'){
that.leader_1=data.name;
}else if(that.leader==='leader_2'){
that.leader_2=data.name;
}else if(that.leader==='leader_3'){
that.leader_3=data.name;
}
firstAudit(that.planId,{leader:that.leader ,token : data.token}).then(res=>{
if(res.code===200){
this.limitedPhoto = false;
if(that.leader_1!==null&&that.leader_2!==null&&that.leader_3!==null){
this.reviewVisible = false;
}
that.getTableData();
}
})
},
},
mounted() {
this.getProcessList();
}
}
</script>
<style scoped>
.tabButtonWrap{
display: flex;
margin-bottom: 2px;
border-bottom: 1px solid #EBEEF5;
}
.tabButton{
height: 40px;
width: 100px;
color: #555555;
line-height: 40px;
text-align: center;
border: 1px solid #EBEEF5;
border-radius: 10px 10px 0 0;
}
.activeTab {
color: #FFFFFF;
font-weight: bold;
background: #409EFF;
}
.reviewWrap{
display: flex;
font-size: 16px;
justify-content: flex-end;
}
.reviewBlock{
display: flex;
flex-direction: column;
margin-right: 40px
}
.directorName{
height: 30px;
}
.reviewBtn{
width: 80px;
height: 35px;
font-size: 15px;
line-height: 33px;
text-align: center;
border: 1px solid #409eff;
border-radius: 5px;
/*background: #409eff;*/
color: #409eff;
}
.items {
height: 35px;
line-height: 35px;
padding-left: 20px;
}
.itemLabel {
font-size: 14px;
color: #606266;
font-weight: 600;
}
</style>

View File

@ -30,7 +30,7 @@ class HRMService:
update_all_user_facedata_cache()
try:
results = face_recognition.compare_faces(face_datas,
unknown_face_encoding, tolerance=0.5)
unknown_face_encoding, tolerance=0.48)
except:
return None, '人脸匹配失败'
for index, value in enumerate(results):

View File

@ -29,4 +29,4 @@ class IProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
class Meta:
model = IProduct
fields = ['material', 'warehouse', 'batch', 'order', 'material__type', 'update_time_start', 'update_time_end',
'to_order', 'need_to_order']
'to_order', 'need_to_order', 'state']

View File

@ -95,6 +95,13 @@ class FIFOItemCreateSerializer(serializers.ModelSerializer):
if fifo.pu_order is not None:
raise ValidationError('非采购订单')
return super().create(validated_data)
def validate_batch(self, value):
if value == '':
return value
elif len(value) > 6:
return value
raise ValidationError('批次号错误')
class FIFOItemUpdateSerializer(serializers.ModelSerializer):
class Meta:
@ -122,6 +129,13 @@ class FIFODetailInPurSerializer(serializers.ModelSerializer):
class Meta:
model = FIFOItem
fields = ['material', 'count', 'batch', 'details', 'warehouse']
def validate_batch(self, value):
if value == '':
return value
elif len(value) > 6:
return value
raise ValidationError('批次号错误')
class MaterialBatchQuerySerializer(serializers.Serializer):

View File

@ -1,5 +1,10 @@
from itertools import count
from rest_framework.exceptions import ValidationError
from apps.inm.models import FIFOItemProduct, IProduct, Inventory, MaterialBatch, FIFO, FIFOItem
from apps.inm.models import FIFOItemProduct, IProduct, Inventory, MaterialBatch, FIFO, FIFOItem, WareHouse
from apps.mtm.models import Material
from apps.sam.models import SalePack, SaleProduct
from django.db.models import Count
from django.db.models.aggregates import Sum
class InmService:
@classmethod
@ -53,18 +58,19 @@ class InmService:
o1 = Inventory.objects.get(material=material, warehouse=warehouse)
temp_count = o1.count - i.count
if temp_count < 0:
raise ValidationError('库存不足,操作失败')
raise ValidationError('仓库库存不足,操作失败')
o1.count = temp_count
o1.save()
print(i.batch)
o2 = MaterialBatch.objects.get(material=material, warehouse=warehouse, batch=i.batch)
temp_count = o2.count - i.count
if temp_count < 0:
raise ValidationError('库存不足,操作失败')
raise ValidationError('批次库存不足,操作失败')
o2.count = temp_count
o2.save()
temp_count = material.count - i.count
if temp_count < 0:
raise ValidationError('库存不足,操作失败')
raise ValidationError('物料库存不足,操作失败')
material.count = temp_count
material.save()
@ -72,4 +78,64 @@ class InmService:
if instance.type == FIFO.FIFO_TYPE_DO_OUT:
# 生产领料的情况直接从IProduct中删除
numbers = FIFOItemProduct.objects.filter(fifoitem=i).values_list('number', flat=True)
IProduct.objects.filter(number__in=numbers).delete()
IProduct.objects.filter(number__in=numbers).delete()
# 销售的话已经处理了
# elif instance.type == FIFO.FIFO_TYPE_SALE_OUT:
# ips = FIFOItemProduct.objects.filter(fifoitem=i).values_list('iproduct', flat=True)
# IProduct.objects.filter(id__in=ips).update(state=IProduct.SALED)
@classmethod
def sale_out_audit(cls, fifo:FIFO):
sale = fifo.sale
saleps = SaleProduct.objects.filter(sale=sale, packnum__isnull=True, remark__isnull=True)
if saleps.exists():
raise ValidationError('存在未装箱的产品')
# 创建出库条目
ips = IProduct.objects.filter(sale_iproduct__sale=sale,
sale_iproduct__packnum__isnull=False)
ips.update(state=IProduct.SALED)
items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id'))
for i in items:
warehouse = WareHouse.objects.get(id=i['warehouse'])
material = Material.objects.get(id=i['material'])
fifoitem = FIFOItem()
fifoitem.need_test = False
fifoitem.warehouse = warehouse
fifoitem.material = material
fifoitem.count = i['total']
fifoitem.batch = i['batch']
fifoitem.fifo = fifo
fifoitem.save()
items_p = ips.filter(warehouse=warehouse, batch=i['batch'])
ipxs = []
for i in items_p:
# 创建出库明细半成品
ip = {}
ip['fifoitem'] = fifoitem
ip['number'] = i.number
ip['material'] = i.material
ip['iproduct'] = i
ipxs.append(FIFOItemProduct(**ip))
FIFOItemProduct.objects.bulk_create(ipxs)
# 装箱附件处理
# ml = SalePack.objects.filter(sale_product__iproduct = ips
# ).values('packitem__material').annotate(count=Sum('count'))
# for i in ml:
# material = Material.objects.get(id=i['material'])
# 更新动态产品表情况
from apps.wpm.models import WProduct
WProduct.objects.filter(id__in=ips.values_list('wproduct', flat=True)).update(
act_state=WProduct.WPR_ACT_STATE_SELLED)
# 变更销售记录实际发货数
sale.count_real = ips.count()
sale.save()
# 变更订单状态
order = sale.order
if order:
order.delivered_count = IProduct.objects.filter(sale_iproduct__sale__order=order
, sale_iproduct__packnum__isnull=False).count()
order.save()

View File

@ -184,24 +184,27 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'fifo_audit'}, serializer_class=serializers.Serializer)
@transaction.atomic
def audit(self, request, pk=None):
"""
审核通过
"""
obj = self.get_object()
if not FIFOItem.objects.filter(fifo=obj).exists():
raise ValidationError('出入库条目为空')
for i in FIFOItem.objects.filter(fifo=obj, need_test=True):
if not i.is_testok:
raise APIException('未检验通过, 不可审核')
if obj.is_audited:
raise APIException('该入库记录已审核通过')
with transaction.atomic():
obj.is_audited = True
obj.auditor = request.user
obj.inout_date = timezone.now() # 也是审核日期
obj.save()
InmService.update_inm(obj) # 更新库存
raise APIException('该入库记录已审核通过')
if obj.type == FIFO.FIFO_TYPE_SALE_OUT: # 如果是销售提货,需额外处理
InmService.sale_out_audit(obj)
else:
if not FIFOItem.objects.filter(fifo=obj).exists():
raise ValidationError('出入库条目为空')
for i in FIFOItem.objects.filter(fifo=obj, need_test=True):
if not i.is_testok:
raise APIException('未检验通过, 不可审核')
obj.is_audited = True
obj.auditor = request.user
obj.inout_date = timezone.now() # 也是审核日期
obj.save()
InmService.update_inm(obj) # 更新库存
return Response()

View File

@ -24,6 +24,8 @@ class MaterialDetailSerializer(serializers.ModelSerializer):
objs = Process.objects.filter(subproduction_process__product=obj, subproduction_process__is_deleted=False, is_deleted=False).distinct().order_by('number')
return ProcessSimpleSerializer(instance=objs, many=True).data
class PackItemSerializer(serializers.ModelSerializer):
class Meta:
model = PackItem
@ -42,7 +44,13 @@ class PackItemUpdateSerializer(serializers.ModelSerializer):
class MaterialSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Material
fields = ['id', 'name', 'number', 'unit','specification', 'type']
fields = ['id', 'name', 'number', 'unit','specification', 'type', 'count', 'count_safe']
class PackItemDetailSerializer(serializers.ModelSerializer):
material_ = MaterialSimpleSerializer(source='material', read_only=True)
class Meta:
model = PackItem
fields = '__all__'
class ProcessSerializer(serializers.ModelSerializer):
instruction_ = FileSimpleSerializer(source='instruction', read_only=True)

View File

@ -272,6 +272,9 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
@action(methods=['post'], detail=True, perms_map={'post':'*'}, 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('首件确认已完成')

View File

@ -97,6 +97,8 @@ class TestRecordDetailSerializer(serializers.ModelSerializer):
# record_data = TestRecordItemSerializer(source='item_test_record', read_only=True, many=True)
record_data = serializers.SerializerMethodField()
origin_test_ = TestRecordDetailBaseSerializer(source='origin_test', read_only=True)
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
update_by_ = UserSimpleSerializer(source='update_by', read_only=True)
class Meta:
model = TestRecord
fields = '__all__'

View File

@ -0,0 +1,36 @@
# Generated by Django 3.2.9 on 2022-02-22 07:30
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('mtm', '0049_auto_20220222_0944'),
('sam', '0013_auto_20220222_0941'),
]
operations = [
migrations.AddField(
model_name='saleproduct',
name='packnum',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='装箱单号'),
),
migrations.CreateModel(
name='SalePack',
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='删除标记')),
('count', models.PositiveSmallIntegerField(verbose_name='打包数量')),
('packitem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.packitem', verbose_name='打包项目')),
('sale_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sam.saleproduct', verbose_name='关联销售产品')),
],
options={
'abstract': False,
},
),
]

View File

@ -94,7 +94,6 @@ class Sale(CommonADModel):
receiver_address = models.CharField('收获地址', null=True, blank=True, max_length=200)
remark = models.CharField('备注', null=True, blank=True, max_length=200)
class SaleProduct(BaseModel):
"""
具体产品
@ -117,6 +116,7 @@ class SalePack(BaseModel):
on_delete=models.CASCADE)
packitem = models.ForeignKey(PackItem, verbose_name='打包项目',
on_delete=models.CASCADE)
count = models.PositiveSmallIntegerField('打包数量')

View File

@ -21,7 +21,7 @@ class CustomerCreateUpdateSerializer(serializers.ModelSerializer):
class CustomerSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Customer
fields = ['id', 'name']
fields = ['id', 'name', 'address', 'contact', 'contact_phone']
class ContractSerializer(serializers.ModelSerializer):
customer_ = CustomerSimpleSerializer(source='customer', read_only=True)

View File

@ -2,8 +2,9 @@ from rest_framework import serializers
from rest_framework import exceptions
from apps.inm.models import IProduct
from apps.inm.serializers import IProductListSerializer
from apps.mtm.serializers import MaterialSimpleSerializer
from apps.sam.models import Sale, SaleProduct
from apps.mtm.models import Material, PackItem
from apps.mtm.serializers import MaterialSimpleSerializer, PackItemDetailSerializer
from apps.sam.models import Sale, SalePack, SaleProduct
from apps.sam.serializers import CustomerSimpleSerializer, OrderSimpleSerializer
from django.db import transaction
from rest_framework.exceptions import ValidationError
@ -60,4 +61,39 @@ class SaleProductCreateSerializer(serializers.ModelSerializer):
if order:
if sale.count+order.delivered_count>order.count:
raise exceptions.APIException('超过订单所需数量')
return instance
return instance
class SPackItemSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='packitem.name', read_only=True)
specification = serializers.CharField(source='packitem.specification', read_only=True)
unit = serializers.CharField(source='packitem.unit', read_only=True)
material_ = MaterialSimpleSerializer(source='packitem.material', read_only=True)
class Meta:
model = SalePack
fields = '__all__'
class SaleProductPackDetailSerializer(serializers.ModelSerializer):
detail = serializers.SerializerMethodField()
class Meta:
model = SaleProduct
fields = ['packnum', 'detail', 'remark']
def get_detail(self, obj):
return SPackItemSerializer(instance=SalePack.objects.filter(sale_product=obj)
, many=True).data
class SPackItemCreateSerializer(serializers.Serializer):
id = serializers.PrimaryKeyRelatedField(queryset=SalePack.objects.all())
count = serializers.IntegerField()
class SaleProductPackSerializer(serializers.ModelSerializer):
detail = SPackItemCreateSerializer(many=True)
packnum = serializers.CharField(min_length=6)
class Meta:
model = SaleProduct
fields = ['packnum', 'detail', 'remark']
class SRemarkItemCreateSerializer(serializers.Serializer):
remark = serializers.CharField(min_length=6)

View File

@ -1,11 +1,12 @@
from rest_framework.mixins import ListModelMixin, DestroyModelMixin, CreateModelMixin, RetrieveModelMixin
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, WareHouse
from apps.inm.services import InmService
from apps.mtm.models import Material
from apps.sam.models import Sale, SaleProduct
from apps.sam.serializers_sale import SaleCreateSerializer, SaleListSerializer, SaleProductCreateSerializer, SaleProductListSerializer
from apps.mtm.models import Material, PackItem
from apps.sam.models import Sale, SalePack, SaleProduct
from apps.sam.serializers_sale import SRemarkItemCreateSerializer, SaleCreateSerializer, SaleListSerializer, SaleProductCreateSerializer, SaleProductListSerializer, SaleProductPackDetailSerializer, SaleProductPackSerializer
from rest_framework import exceptions
from django.db import transaction
from rest_framework.decorators import action
@ -41,7 +42,8 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
if obj.is_audited:
raise exceptions.APIException('该销售记录已审核,不可删除')
obj.delete()
IProduct.objects.filter(sale_iproduct__sale=obj).update()
IProduct.objects.filter(sale_iproduct__sale=obj).update(state=IProduct.SALE_OK)
return Response()
def create(self, request, *args, **kwargs):
data = request.data
@ -88,46 +90,10 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
fifo.create_by = request.user
fifo.number = 'CK' + ranstr(7)
fifo.save()
# 创建出库条目
# ips = IProduct.objects.filter(sale_iproduct__sale=obj)
# items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id'))
# for i in items:
# warehouse = WareHouse.objects.get(id=i['warehouse'])
# material = Material.objects.get(id=i['material'])
# fifoitem = FIFOItem()
# fifoitem.need_test = False
# fifoitem.warehouse = warehouse
# fifoitem.material = material
# fifoitem.count = i['total']
# fifoitem.batch = i['batch']
# fifoitem.fifo = fifo
# fifoitem.save()
# items_p = ips.filter(warehouse=warehouse, batch=i['batch'])
# ipxs = []
# for i in items_p:
# # 创建出库明细半成品
# ip = {}
# ip['fifoitem'] = fifoitem
# ip['number'] = i.number
# ip['material'] = i.material
# ip['iproduct'] = i
# ipxs.append(FIFOItemProduct(**ip))
# FIFOItemProduct.objects.bulk_create(ipxs)
# 更新动态产品表情况
# from apps.wpm.models import WProduct
# WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update(
# act_state=WProduct.WPR_ACT_STATE_SELLED)
# 更新库存
# InmService.update_inm(fifo)
# 变更销售提货审核状态
obj.is_audited = True
obj.save()
# 变更订单状态
# if obj.order:
# order = obj.order
# order.delivered_count = order.delivered_count + obj.count
# order.save()
return Response()
@ -157,4 +123,54 @@ class SaleProductViewSet(ListModelMixin, DestroyModelMixin, CreateModelMixin, Ge
obj.delete()
sale.count = SaleProduct.objects.filter(sale=obj.sale).count()
sale.save()
return Response()
def create(self, request, *args, **kwargs):
obj = self.get_object()
sale = obj.sale
if sale.is_audited:
raise exceptions.APIException('该销售记录已审核,不可添加产品')
return super().create(request, *args, **kwargs)
@action(methods=['get', 'post'], detail=True, perms_map={'post':'sale_pack', 'get':'*'}, serializer_class=SaleProductPackSerializer)
@transaction.atomic
def pack(self, request, pk=None):
"""
打包装箱
"""
obj = self.get_object()
if request.method == 'GET':
for i in PackItem.objects.filter(product=obj.iproduct.material, is_deleted=False):
SalePack.objects.get_or_create(sale_product=obj, packitem=i,
defaults={
"sale_product":obj,
"packitem":i,
"count":i.count
})
return Response(SaleProductPackDetailSerializer(instance=obj).data)
elif request.method == 'POST':
serializer = SaleProductPackSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
obj.packnum = vdata['packnum']
obj.remark = vdata.get('remark', '')
for i in vdata['detail']:
pi = i['id']
pi.count = i['count']
pi.save()
obj.save()
return Response()
@action(methods=['post'], detail=True, perms_map={'post':'sale_pack'}, serializer_class=SRemarkItemCreateSerializer)
@transaction.atomic
def remark(self, request, pk=None):
"""
不装箱备注
"""
obj = self.get_object()
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
obj.remark = vdata['remark']
obj.save()
return Response()