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

This commit is contained in:
shijing 2022-01-21 17:14:44 +08:00
commit 400173c0cf
28 changed files with 1473 additions and 1125 deletions

View File

@ -36,34 +36,34 @@ export function deleteEquipment(id, data) {
} }
export function getEquipmentrecordList(query) { export function getEquipmentrecordList(query) {
return request({ return request({
url: '/em/equipmentrecord/', url: '/em/echeck_record/',
method: 'get', method: 'get',
params: query params: query
}) })
} }
export function getEquipmentrecordAll() { export function getEquipmentrecordAll() {
return request({ return request({
url: '/em/equipmentrecord/', url: '/em/echeck_record/',
method: 'get' method: 'get'
}) })
} }
export function createEquipmentrecord(data) { export function createEquipmentrecord(data) {
return request({ return request({
url: '/em/equipmentrecord/', url: '/em/echeck_record/',
method: 'post', method: 'post',
data data
}) })
} }
export function updateEquipmentrecord(id, data) { export function updateEquipmentrecord(id, data) {
return request({ return request({
url: `/em/equipmentrecord/${id}/`, url: `/em/echeck_record/${id}/`,
method: 'put', method: 'put',
data data
}) })
} }
export function deleteEquipmentrecord(id, data) { export function deleteEquipmentrecord(id, data) {
return request({ return request({
url: `/em/equipmentrecord/${id}/`, url: `/em/echeck_record/${id}/`,
method: 'delete', method: 'delete',
data data
}) })

View File

@ -357,4 +357,14 @@ export function getCard(id) {
url: `/wpm/wproduct/${id}/card/`, url: `/wpm/wproduct/${id}/card/`,
method: 'GET', method: 'GET',
}) })
} }
//指派发货订单
export function toorder(data) {
return request({
url: '/wpm/wproduct/to_order/',
method: 'post',
data
})
}

View File

@ -273,12 +273,6 @@ export const asyncRoutes = [
name: 'record', name: 'record',
component: () => import('@/views/em/record'), component: () => import('@/views/em/record'),
meta: { title: '校准检定记录', icon: 'example', perms: ['em_record'] } meta: { title: '校准检定记录', icon: 'example', perms: ['em_record'] }
},
{
path: 'detection ',
name: 'detection ',
component: () => import('@/views/em/detection'),
meta: { title: '运维记录', icon: 'example', perms: ['em_detection'] }
} }
] ]
}, },
@ -399,6 +393,12 @@ export const asyncRoutes = [
name: 'producttest', name: 'producttest',
component: () => import('@/views/qm/producttest'), component: () => import('@/views/qm/producttest'),
meta: { title: '成品检验', icon: 'example', perms: ['index_manage'] } meta: { title: '成品检验', icon: 'example', perms: ['index_manage'] }
},
{
path: 'unproduct',
name: 'unproduct',
component: () => import('@/views/qm/unproduct'),
meta: { title: '不合格品', icon: 'example', perms: ['index_manage'] }
} }
] ]

View File

@ -2,109 +2,116 @@
<div class="app-container"> <div class="app-container">
<el-card> <el-card>
<div> <div>
<el-button type="primary" icon="el-icon-plus" @click="handleCreate" <el-button type="primary" icon="el-icon-plus" @click="handleCreate"
>新增设备</el-button >新增设备</el-button
> >
<el-input <el-input
v-model="listQuery.search" v-model="listQuery.search"
placeholder="设备名称/设备编号/备注" placeholder="设备名称/设备编号/备注"
style="width: 300px" style="width: 300px"
class="filter-item" class="filter-item"
@keyup.enter.native="handleFilter" @keyup.enter.native="handleFilter"
/> />
<el-button <el-button
class="filter-item" class="filter-item"
type="primary" type="primary"
icon="el-icon-search" icon="el-icon-search"
@click="handleFilter" @click="handleFilter"
>搜索</el-button >搜索</el-button
> >
<el-button <el-button
class="filter-item" class="filter-item"
type="primary" type="primary"
icon="el-icon-refresh-left" icon="el-icon-refresh-left"
@click="resetFilter" @click="resetFilter"
>重置</el-button >重置</el-button
> >
</div> </div>
</el-card> </el-card>
<el-card > <el-card>
<el-table <el-table
v-loading="listLoading" v-loading="listLoading"
:data="equipmentList.results" :data="equipmentList.results"
border :cell-style="tableRowClassName"
border
fit fit
stripe stripe
highlight-current-row height="100"
height="100" v-el-height-adaptive-table="{ bottomOffset: 42 }"
v-el-height-adaptive-table="{bottomOffset: 42}"
> >
<el-table-column type="index" width="50" /> <el-table-column type="index" width="50" />
<el-table-column label="设备编号"> <el-table-column label="设备编号">
<template slot-scope="scope">{{ scope.row.number }}</template> <template slot-scope="scope">{{ scope.row.number }}</template>
</el-table-column> </el-table-column>
<el-table-column label="管理类别"> <el-table-column label="管理类别">
<template slot-scope="scope">{{ mgmtype_[scope.row.mgmtype] }}</template> <template slot-scope="scope">{{
mgmtype_[scope.row.mgmtype]
}}</template>
</el-table-column> </el-table-column>
<el-table-column label="校/检方式"> <el-table-column label="校/检方式" width="100">
<template slot-scope="scope">{{ waytype_[scope.row.way] }}</template> <template slot-scope="scope">{{ waytype_[scope.row.way] }}</template>
</el-table-column> </el-table-column>
<el-table-column label="标准/依据"> <el-table-column label="标准/依据" width="100">
<template slot-scope="scope">{{ scope.row.standard }}</template> <template slot-scope="scope">{{ scope.row.standard }}</template>
</el-table-column> </el-table-column>
<el-table-column label="校/检周期"> <el-table-column label="校/检周期" width="100">
<template slot-scope="scope">{{ scope.row.cycle }}</template> <template slot-scope="scope">{{ scope.row.cycle }}</template>
</el-table-column> </el-table-column>
<el-table-column label="设备名称"> <el-table-column label="最近一次校准日期" width="140">
<template slot-scope="scope">{{ scope.row.check_date }}</template>
</el-table-column>
<el-table-column label="下次校准日期" width="130">
<template slot-scope="scope">
{{ scope.row.next_check_date }}</template
>
</el-table-column>
<el-table-column label="设备名称">
<template slot-scope="scope">{{ scope.row.name }}</template> <template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column> </el-table-column>
<el-table-column label="型号规格"> <el-table-column label="型号规格">
<template slot-scope="scope">{{ scope.row.model }}</template> <template slot-scope="scope">{{ scope.row.model }}</template>
</el-table-column> </el-table-column>
<el-table-column label="生产厂家"> <el-table-column label="生产厂家">
<template slot-scope="scope">{{ scope.row.factory }}</template> <template slot-scope="scope">{{ scope.row.factory }}</template>
</el-table-column> </el-table-column>
<el-table-column label="使用类别"> <el-table-column label="使用类别">
<template slot-scope="scope">{{ usetype_[scope.row.way] }}</template> <template slot-scope="scope">{{ usetype_[scope.row.way] }}</template>
</el-table-column> </el-table-column>
<el-table-column label="校/检状态"> <el-table-column label="校/检状态" width="100">
<template slot-scope="scope">{{ state_[scope.row.statedm] }}</template> <template slot-scope="scope">{{ state_[scope.row.state] }}</template>
</el-table-column> </el-table-column>
<el-table-column label="保管人"> <el-table-column label="保管人">
<template slot-scope="scope" v-if="scope.row.keeper_">{{ scope.row.keeper_.username }}</template> <template slot-scope="scope" v-if="scope.row.keeper_">{{
scope.row.keeper_.username
}}</template>
</el-table-column> </el-table-column>
<el-table-column label="存放位置"> <el-table-column label="存放位置">
<template slot-scope="scope">{{ scope.row.place }}</template> <template slot-scope="scope">{{ scope.row.place }}</template>
</el-table-column> </el-table-column>
<el-table-column label="绑定工序"> <el-table-column label="绑定工序">
<template slot-scope="scope" v-if="scope.row.step_"> <template slot-scope="scope" v-if="scope.row.step_">
<el-tag v-for="item in scope.row.step_" <el-tag
:key="item.number" v-for="item in scope.row.step_"
:label="item.name" :key="item.number"
:value="item.number">{{item.name}}</el-tag> :label="item.name"
:value="item.number"
>{{ item.name }}</el-tag
>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="备注"> <el-table-column label="备注">
<template slot-scope="scope">{{ scope.row.description }}</template> <template slot-scope="scope">{{ scope.row.description }}</template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column align="center" label="操作" fixed="right" width="120px">
align="center"
label="操作"
width="120px"
>
<template slot-scope="scope"> <template slot-scope="scope">
<el-link <el-link
v-if="checkPermission(['equipment_update'])" v-if="checkPermission(['equipment_update'])"
type="primary" type="primary"
@click="handleEdit(scope)" @click="handleEdit(scope)"
>编辑</el-link >编辑</el-link
> >
@ -137,130 +144,153 @@
label-position="right" label-position="right"
:rules="rule1" :rules="rule1"
> >
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="设备名称" prop="name"> <el-form-item label="设备名称" prop="name">
<el-input v-model="equipment.name" placeholder="设备名称" /> <el-input v-model="equipment.name" placeholder="设备名称" />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="设备编号" prop="number">
<el-input v-model="equipment.number" placeholder="设备编号" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="型号规格" prop="model">
<el-input v-model="equipment.model" placeholder="规格型号规格" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="标准/依据" prop="standard">
<el-input v-model="equipment.standard" placeholder="溯源标准或依据" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="校/检状态" prop="statedm">
<el-select style="width: 100%" v-model="equipment.statedm" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="管理类别" prop="mgmtype">
<el-select style="width: 100%" v-model="equipment.mgmtype" placeholder="请选择">
<el-option
v-for="item in mgmoptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="存放位置" prop="place">
<el-input v-model="equipment.place" placeholder="存放位置" />
</el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="责任人" prop="keeper"> <el-form-item label="设备编号" prop="number">
<el-input v-model="equipment.number" placeholder="设备编号" />
<el-select </el-form-item>
v-model="equipment.keeper" </el-col>
filterable </el-row>
style="width: 100%" <el-row>
allow-create <el-col :span="12">
default-first-option <el-form-item label="型号规格" prop="model">
placeholder="请选择"> <el-input v-model="equipment.model" placeholder="规格型号规格" />
<el-option </el-form-item>
v-for="item in keeperOptions" </el-col>
<el-col :span="12">
<el-form-item label="标准/依据" prop="standard">
<el-input
v-model="equipment.standard"
placeholder="溯源标准或依据"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="校/检状态" prop="state">
<el-select
style="width: 100%"
v-model="equipment.state"
placeholder="请选择"
>
<el-option
v-for="item in options"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value"> :value="item.value"
</el-option> >
</el-select> </el-option>
</el-select>
</el-form-item>
</el-form-item> </el-col>
</el-col> <el-col :span="12">
</el-row> <el-form-item label="管理类别" prop="mgmtype">
<el-row> <el-select
<el-col :span="12"> style="width: 100%"
<el-form-item label="校/检方式" prop="way"> v-model="equipment.mgmtype"
<el-select style="width: 100%" v-model="equipment.way" placeholder="请选择"> placeholder="请选择"
<el-option >
v-for="item in wayoptions" <el-option
:key="item.value" v-for="item in mgmoptions"
:label="item.label" :key="item.value"
:value="item.value"> :label="item.label"
</el-option> :value="item.value"
</el-select> >
</el-form-item> </el-option>
</el-col> </el-select>
<el-col :span="12"> </el-form-item>
<el-form-item label="存放位置" prop="place"> </el-col>
<el-input v-model="equipment.place" placeholder="存放位置" /> </el-row>
</el-form-item> <el-row>
</el-col> <el-col :span="12">
</el-row> <el-form-item label="存放位置" prop="place">
<el-row> <el-input v-model="equipment.place" placeholder="存放位置" />
<el-col :span="12"> </el-form-item>
<el-form-item label="使用类别" prop="usetype"> </el-col>
<el-select style="width: 100%" v-model="equipment.usetype" placeholder="请选择"> <el-col :span="12">
<el-option <el-form-item label="责任人" prop="keeper">
v-for="item in useoptions" <el-select
:key="item.value" v-model="equipment.keeper"
:label="item.label" filterable
:value="item.value"> style="width: 100%"
</el-option> allow-create
</el-select> default-first-option
</el-form-item> placeholder="请选择"
>
</el-col> <el-option
<el-col :span="12"> v-for="item in keeperOptions"
<el-form-item label="校/检周期" prop="cycle"> :key="item.value"
<el-input v-model="equipment.cycle" placeholder="校准或检定周期" /> :label="item.label"
</el-form-item> :value="item.value"
</el-col> >
</el-row> </el-option>
</el-select>
<el-form-item label="生产厂家" prop="factory"> </el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="校/检方式" prop="way">
<el-select
style="width: 100%"
v-model="equipment.way"
placeholder="请选择"
>
<el-option
v-for="item in wayoptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="存放位置" prop="place">
<el-input v-model="equipment.place" placeholder="存放位置" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="使用类别" prop="usetype">
<el-select
style="width: 100%"
v-model="equipment.usetype"
placeholder="请选择"
>
<el-option
v-for="item in useoptions"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="校/检周期" prop="cycle">
<el-input
v-model="equipment.cycle"
placeholder="校准或检定周期"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="生产厂家" prop="factory">
<el-input v-model="equipment.factory" placeholder="生产厂商及国别" /> <el-input v-model="equipment.factory" placeholder="生产厂商及国别" />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="description"> <el-form-item label="备注" prop="description">
<el-input <el-input
type="textarea" type="textarea"
@ -269,14 +299,6 @@
placeholder="备注" placeholder="备注"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<div style="text-align: right"> <div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false">取消</el-button> <el-button type="danger" @click="dialogVisible = false">取消</el-button>
@ -285,13 +307,19 @@
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<style>
</style>
<script> <script>
import { getpEquipmentList, createEquipment,updateEquipment,deleteEquipment } from "@/api/equipment"; import {
getpEquipmentList,
createEquipment,
updateEquipment,
deleteEquipment,
} from "@/api/equipment";
import { getUserList } from "@/api/user"; import { getUserList } from "@/api/user";
import { getOrgList } from "@/api/org"; import { getOrgList } from "@/api/org";
import checkPermission from "@/utils/permission"; import checkPermission from "@/utils/permission";
import { genTree } from "@/utils"; import { genTree } from "@/utils";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
const defaultequipment = { const defaultequipment = {
@ -306,105 +334,103 @@ export default {
equipmentList: { equipmentList: {
count: 0, count: 0,
}, },
mgmtype_: { mgmtype_: {
'1':'A', 1: "A",
'2':'B', 2: "B",
'3':'C', 3: "C",
}, },
mgmoptions: [{ mgmoptions: [
{
value: 1, value: 1,
label: 'A' label: "A",
}, { },
{
value: 2, value: 2,
label: 'B' label: "B",
}, },
{ {
value: 3, value: 3,
label: 'C' label: "C",
}],
waytype_: {
'1':'外检',
'2':'自检',
}, },
wayoptions: [{ ],
waytype_: {
1: "外检",
2: "自检",
},
wayoptions: [
{
value: 1, value: 1,
label: '外检' label: "外检",
}, {
value: 2,
label: '自检'
}],
usetype_:{
'1':'专用',
'2':'公用',
},
useoptions: [{
value: 1,
label: '专用'
}, {
value: 2,
label: '公用'
}],
state_:{
0:'合格',
1:'准用',
2:'限用',
3:'禁用',
4:'停用',
5:'封存',
},
options: [{
value: 0,
label: '合格'
}, {
value: 1,
label: '准用'
}, {
value: 2,
label: '限用'
}, },
{ {
value: 3, value: 2,
label: '禁用' label: "自检",
}, { },
value: 4, ],
label: '停用' usetype_: {
}, { 1: "专用",
value: 5, 2: "公用",
label: '封存' },
}], useoptions: [
{
value: 1,
label: "专用",
},
{
value: 2,
label: "公用",
},
],
state_: {
10: "合格",
40: "禁用",
},
options: [
{
value: 10,
label: "合格",
},
{
value: 40,
label: "禁用",
},
],
listQuery: { listQuery: {
page: 1, page: 1,
page_size: 20, page_size: 20,
}, },
keeperOptions:[], keeperOptions: [],
depOptions:[], depOptions: [],
listLoading: true, listLoading: true,
dialogVisible: false, dialogVisible: false,
dialogType: "new", dialogType: "new",
rule1: { rule1: {
name: [{ required: true, message: "请输入", trigger: "blur" }], name: [{ required: true, message: "请输入", trigger: "blur" }],
number: [{ required: true, message: "请输入", trigger: "blur" }], number: [{ required: true, message: "请输入", trigger: "blur" }],
type: [{ required: true, message: "请输入", trigger: "blur" }], type: [{ required: true, message: "请输入", trigger: "blur" }],
model: [{ required: true, message: "请输入", trigger: "blur" }], model: [{ required: true, message: "请输入", trigger: "blur" }],
state: [{ required: true, message: "请选择", trigger: "blur" }], state: [{ required: true, message: "请选择", trigger: "blur" }],
belong_dept:[{ required: true, message: "请选择", trigger: "blur" }] belong_dept: [{ required: true, message: "请选择", trigger: "blur" }],
}, },
datatime: null,
}; };
}, },
computed: {}, computed: {},
watch: {}, watch: {},
created() { created() {
this.getList(); this.getList();
this.getUserList();
this.getOrgList(); this.getUserList();
this.getOrgList();
}, },
methods: { methods: {
checkPermission, checkPermission,
//设备列表 //设备列表
getList() { getList() {
this.listLoading = true; this.listLoading = true;
this.listQuery.type=2; this.listQuery.type = 2;
getpEquipmentList(this.listQuery).then((response) => { getpEquipmentList(this.listQuery).then((response) => {
if (response.data) { if (response.data) {
this.equipmentList = response.data; this.equipmentList = response.data;
@ -412,20 +438,51 @@ export default {
this.listLoading = false; this.listLoading = false;
}); });
}, },
//组员列表
//校准时间提醒
tableRowClassName(row, column, rowIndex, columnIndex) {
if(row.row.next_check_date!=null)
{
var adata = new Date(); //获取当前时间
const date = {
year: adata.getFullYear(),
month: adata.getMonth() + 1,
date: adata.getDate(),
};
const newmonth = date.month > 10 ? date.month : "0" + date.month;
const day = date.date > 10 ? date.date : "0" + date.date;
var updateTime = date.year + "-" + newmonth + "-" + day;
var dateBegin = row.row.next_check_date;
var a =
(new Date(dateBegin).getTime() - new Date(updateTime).getTime()) /1000/60/60/24; //计算出相差的毫秒数
console.log(a);
if (4>a&&a>0 && row.column.label==='下次校准日期') {
return "background:yellow";
}
else if (a<0 && row.column.label==='下次校准日期') {
return "background:red";
}
return "";
}
},
//组员列表
getUserList() { getUserList() {
getUserList({pageoff:true}).then((res) => { getUserList({ page: 0 }).then((res) => {
this.keeperOptions = genTree(res.data); this.keeperOptions = genTree(res.data);
}); });
}, },
//部门列表 //部门列表
getOrgList() { getOrgList() {
getOrgList({pageoff:true}).then((res) => { getOrgList({ page: 0 }).then((res) => {
this.depOptions = genTree(res.data); this.depOptions = genTree(res.data);
}); });
}, },
handleFilter() { handleFilter() {
this.listQuery.page = 1; this.listQuery.page = 1;
this.getList(); this.getList();
@ -434,7 +491,7 @@ export default {
this.listQuery = { this.listQuery = {
page: 1, page: 1,
page_size: 20, page_size: 20,
} };
this.getList(); this.getList();
}, },
handleCreate() { handleCreate() {
@ -445,7 +502,7 @@ export default {
this.$refs["Form"].clearValidate(); this.$refs["Form"].clearValidate();
}); });
}, },
handleEdit(scope) { handleEdit(scope) {
this.equipment = Object.assign({}, scope.row); // copy obj this.equipment = Object.assign({}, scope.row); // copy obj
this.dialogType = "edit"; this.dialogType = "edit";
@ -469,7 +526,7 @@ export default {
console.error(err); console.error(err);
}); });
}, },
async confirm(form) { async confirm(form) {
this.$refs[form].validate((valid) => { this.$refs[form].validate((valid) => {
if (valid) { if (valid) {
@ -483,11 +540,11 @@ export default {
} }
}); });
} else { } else {
this.equipment.type=2; this.equipment.type = 2;
createEquipment(this.equipment).then((res) => { createEquipment(this.equipment).then((res) => {
if (res.code >= 200) { if (res.code >= 200) {
this.getList(); this.getList();
this.dialogVisible = false; this.dialogVisible = false;
this.$message.success("成功"); this.$message.success("成功");
} }
}); });

View File

@ -298,22 +298,22 @@ export default {
count: 0, count: 0,
}, },
state_:{ state_:{
0:'完好', 10:'完好',
1:'限用', 20:'限用',
2:'在修', 30:'在修',
3:'禁用', 40:'禁用',
}, },
options: [{ options: [{
value: 0, value: 10,
label: '完好' label: '完好'
}, { }, {
value: 1, value: 20,
label: '限用' label: '限用'
}, { }, {
value: 2, value: 30,
label: '在修' label: '在修'
}, { }, {
value: 3, value: 40,
label: '禁用' label: '禁用'
}], }],
listQuery: { listQuery: {

View File

@ -52,13 +52,10 @@
<template slot-scope="scope">{{ scope.row.equipment_.model }}</template> <template slot-scope="scope">{{ scope.row.equipment_.model }}</template>
</el-table-column> </el-table-column>
<el-table-column label="设备状态"> <el-table-column label="设备状态">
<template slot-scope="scope">{{ state_[scope.row.equipment_.statedm] }}</template> <template slot-scope="scope">{{ state_[scope.row.equipment_.state] }}</template>
</el-table-column> </el-table-column>
<el-table-column label="最近一次校准/检定日期"> <el-table-column label="校准检查日期">
<template slot-scope="scope">{{ scope.row.recentlydate }}</template> <template slot-scope="scope">{{ scope.row.check_date }}</template>
</el-table-column>
<el-table-column label="下次应校准或检定日期">
<template slot-scope="scope">{{ scope.row.nextdate }}</template>
</el-table-column> </el-table-column>
<el-table-column label="备注"> <el-table-column label="备注">
<template slot-scope="scope">{{ scope.row.description }}</template> <template slot-scope="scope">{{ scope.row.description }}</template>
@ -107,7 +104,7 @@
:rules="rule1" :rules="rule1"
> >
<el-form-item label="校准检定设备" prop="equipment"> <el-form-item label="校准检定设备" prop="equipment">
<el-select style="width: 100%" v-model="equipmentrecord.equipment" placeholder="请选择"> <el-select style="width: 100%" v-model="equipmentrecord.equipment" placeholder="请选择">
<el-option <el-option
v-for="item in equipmentoptions" v-for="item in equipmentoptions"
@ -117,22 +114,11 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="校准或检定状态" prop="state">
<el-select style="width: 100%" v-model="equipmentrecord.state" placeholder="请选择"> <el-form-item label="校准检查日期" prop="check_date">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="最近一次校准或检定日期" prop="recentlydate">
<el-date-picker <el-date-picker
v-model="equipmentrecord.recentlydate" v-model="equipmentrecord.check_date"
type="date" type="date"
placeholder="选择日期" placeholder="选择日期"
value-format="yyyy-MM-dd" value-format="yyyy-MM-dd"
@ -141,21 +127,7 @@
</el-date-picker> </el-date-picker>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下次应校准或检定日期" prop="nextdate">
<el-date-picker
v-model="equipmentrecord.nextdate"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="description"> <el-form-item label="备注" prop="description">
<el-input <el-input
type="textarea" type="textarea"
@ -176,7 +148,7 @@
</div> </div>
</template> </template>
<script> <script>
import { getpEquipmentList, createEquipment,updateEquipment,deleteEquipment, import { getpEquipmentList,
getEquipmentrecordList, createEquipmentrecord,updateEquipmentrecord,deleteEquipmentrecord } from "@/api/equipment"; getEquipmentrecordList, createEquipmentrecord,updateEquipmentrecord,deleteEquipmentrecord } from "@/api/equipment";
import { getUserList } from "@/api/user"; import { getUserList } from "@/api/user";
import { getOrgList } from "@/api/org"; import { getOrgList } from "@/api/org";
@ -202,39 +174,18 @@ export default {
}, },
equipmentoptions:[], equipmentoptions:[],
state_:{ state_:{
0:'合格', 10:'合格',
1:'准用',
2:'限用', 40:'禁用',
3:'禁用',
4:'停用',
5:'封存',
}, },
options: [{
value: 0,
label: '合格'
}, {
value: 1,
label: '准用'
}, {
value: 2,
label: '限用'
},
{
value: 3,
label: '禁用'
}, {
value: 4,
label: '停用'
}, {
value: 5,
label: '封存'
}],
listLoading: true, listLoading: true,
dialogVisible: false, dialogVisible: false,
dialogType: "new", dialogType: "new",
rule1: { rule1: {
equipment: [{ required: true, message: "请输入", trigger: "blur" }], equipment: [{ required: true, message: "请输入", trigger: "blur" }],
state: [{ required: true, message: "请输入", trigger: "blur" }],
}, },
}; };

View File

@ -77,6 +77,12 @@
</el-table-column> </el-table-column>
<el-table-column label="工序"> <el-table-column label="工序">
<template slot-scope="scope">{{ scope.row.process_.name }}</template> <template slot-scope="scope">{{ scope.row.process_.name }}</template>
</el-table-column>
<el-table-column label="是否质检">
<template slot-scope="scope">
<el-tag v-if="scope.row.need_combtest==true"> </el-tag>
<el-tag v-else></el-tag>
</template>
</el-table-column> </el-table-column>
<el-table-column label="排序"> <el-table-column label="排序">
<template slot-scope="scope">{{ scope.row.sort }}</template> <template slot-scope="scope">{{ scope.row.sort }}</template>
@ -132,6 +138,9 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="是否质检" prop="need_combtest">
<el-switch v-model="subproducation.need_combtest"></el-switch>
</el-form-item>
<el-form-item label="排序" prop="sort"> <el-form-item label="排序" prop="sort">
<el-input-number <el-input-number
v-model="subproducation.sort" v-model="subproducation.sort"
@ -554,7 +563,7 @@
<el-table-column align="center" label="文件"> <el-table-column align="center" label="文件">
<template slot-scope="scope" v-if="scope.row.file_"> <template slot-scope="scope" v-if="scope.row.file_">
<el-link :href="scope.row.file_.path" >{{scope.row.file_.name}}</el-link> <el-link :href="scope.row.file_.file" >{{scope.row.file_.name}}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
@ -659,7 +668,7 @@ const defaultusedstep = {
}; };
const defaulttechdoc = {enabled:false}; const defaulttechdoc = {enabled:false};
const defaultsubproducation = { const defaultsubproducation = {
need_combtest:false,
}; };
export default { export default {

View File

@ -149,86 +149,7 @@
:limit.sync="listQuery2.page_size" :limit.sync="listQuery2.page_size"
@pagination="getList2" @pagination="getList2"
/> />
<!--不合格半成品重审-->
<el-dialog title="不合格半成品重审" :close-on-click-modal="false" :visible.sync="limitedRetrial">
<el-form label-width="130px" label-position="right">
<el-form-item :label="item.field_name" v-for="item in customfieldList.ticket_data_" :key="item.id" v-show="!item.is_hidden">
<template v-if="item.field_type=='string'">
<el-input v-model="item.default_value" :placeholder="item.description" />
</template>
<template v-if="item.field_type==='int'">
<el-input v-model="item.default_value" type="number" :placeholder="item.description" oninput="value=value.replace(/[^\d]/g,'')" />
</template>
<template v-if="item.field_type==='float'">
<el-input v-model="item.default_value" type="number" :placeholder="item.description" />
</template>
<template v-if="item.field_type==='date'">
<el-date-picker
v-model="item.default_value"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
>
</el-date-picker>
</template>
<template v-if="item.field_type==='datetime'">
<el-date-picker
v-model="item.default_value"
type="datetime"
placeholder="选择日期"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
>
</el-date-picker>
</template>
<template v-if="item.field_type==='select'">
<el-select style="width: 100%" v-model="item.default_value" placeholder="请选择">
<el-option
v-for="item1 in item.field_choice"
:key="item1.id"
:label="item1.name"
:value="item1.id"
>
</el-option>
</el-select>
</template>
<template v-if="item.field_type==='selects'">
<el-select style="width: 100%" multiple v-model="item.default_value" placeholder="请选择">
<el-option
v-for="item1 in item.field_choice"
:key="item1.id"
:label="item1.name"
:value="item1.id"
>
</el-option>
</el-select>
</template>
<template v-if="item.field_type==='textarea'">
<el-input type="textarea" :rows="3" v-model="item.default_value" placeholder="内容" />
</template>
<template v-if="item.field_type==='file'">
<el-upload
ref="upload"
:action="upUrl"
:on-preview="handlePreview"
:on-success="handleUpSuccess"
:on-remove="handleRemove"
:headers="upHeaders"
:file-list="fileList"
:limit="1"
accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx"
>
<el-button size="small" type="primary">上传文件</el-button>
</el-upload>
</template>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="limitedRetrial = false"> </el-button>
</div>
</el-dialog>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-card> </el-card>

View File

@ -136,7 +136,7 @@ export default {
//半成品列表 //半成品列表
getList() { getList() {
this.listLoading = true; this.listLoading = true;
this.listQuery.type=1; this.listQuery.material__type=1;
getiproductList(this.listQuery).then((response) => { getiproductList(this.listQuery).then((response) => {
if (response.data) { if (response.data) {
this.iproductData= response.data; this.iproductData= response.data;

View File

@ -0,0 +1,294 @@
<template>
<div class="app-container">
<el-card style="margin-top: 2px">
<el-tabs v-model="activeName" type="card">
<el-tab-pane label="成品不合格" name="1">
<el-table
:data="wproductList.results"
border
fit
stripe
highlight-current-row
max-height="600"
>
<el-table-column type="index" label="序号" width="50"/>
<el-table-column label="产品编号">
<template slot-scope="scope">{{ scope.row.number }}</template>
</el-table-column>
<el-table-column label="任务编号">
<template slot-scope="scope" v-if="scope.row.subproduction_plan_">{{ scope.row.subproduction_plan_.number }}
</template>
</el-table-column>
<el-table-column label="产品名称">
<template slot-scope="scope" v-if="scope.row.material_">{{ scope.row.material_.name }}</template>
</el-table-column>
<el-table-column label="型号规格">
<template slot-scope="scope" v-if="scope.row.material_">{{ scope.row.material_.specification }}</template>
</el-table-column>
<el-table-column label="军检状态">
<template slot-scope="scope">
<el-tag v-if="scope.row.is_mtested==false">未军检</el-tag>
<el-tag v-if="scope.row.is_mtested==true">已军检</el-tag>
</template>
</el-table-column>
<el-table-column label="军检结论">
<template slot-scope="scope">
<el-tag v-if="scope.row.is_mtestok==false">不合格</el-tag>
<el-tag v-if="scope.row.is_mtestok==true">合格</el-tag>
</template>
</el-table-column>
<el-table-column label="仓库">
<template slot-scope="scope" v-if="scope.row.warehouse_">{{ scope.row.warehouse_.name }}</template>
</el-table-column>
<el-table-column label="检查表">
<template slot-scope="scope">{{ scope.row.number }}</template>
</el-table-column>
<el-table-column label="检查状态">
<template slot-scope="scope">{{ actstate_[scope.row.act_state]}}</template>
</el-table-column>
<el-table-column align="center" label="操作" width="220px">
<template slot-scope="scope">
<el-link
v-if="checkPermission(['warehouse_update'])&&scope.row.act_state==30&&scope.row.is_mtested==false"
@click="handleMtest(scope)"
>军检
</el-link>
<el-link
@click="checkRecord(scope)"
>检验记录
</el-link>
</template>
</el-table-column>
</el-table>
<pagination
v-show="wproductList.count > 0"
:total="wproductList.count"
:page.sync="listQuery.page"
:limit.sync="listQuery.page_size"
@pagination="getList"
/>
</el-tab-pane>
<el-tab-pane label="过程不合格品" name="2">
<el-table
:data="wproductticket.results"
border
fit
stripe
highlight-current-row
height="620"
v-el-height-adaptive-table="{ bottomOffset: 40 }"
>
<el-table-column type="index" width="50" />
<el-table-column label="玻璃编号">
<template slot-scope="scope">{{ scope.row.number }}</template>
</el-table-column>
<el-table-column label="订单编号">
<template slot-scope="scope" v-if="scope.row.order_">{{
scope.row.order_.number
}}</template>
</el-table-column>
<el-table-column label="产品名称">
<template slot-scope="scope" v-if="scope.row.product_">{{
scope.row.product_.name
}}</template>
</el-table-column>
<el-table-column label="规格型号">
<template slot-scope="scope" v-if="scope.row.product_">{{
scope.row.product_.specification
}}</template>
</el-table-column>
<el-table-column label="责任工序">
<template slot-scope="scope" v-if="scope.row.resp_process_">{{
scope.row.resp_process_.name
}}</template>
</el-table-column>
<el-table-column label="处理结果">
<template slot-scope="scope">{{
decision_[scope.row.decision]
}}</template>
</el-table-column>
<el-table-column label="创建时间">
<template slot-scope="scope">{{
scope.row.create_time
}}</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-link
v-if="checkPermission(['material_update'])"
type="primary"
@click="handledetailbhg(scope)"
>查看</el-link
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="wproductticket.count > 0"
:total="wproductticket.count"
:page.sync="listQuery2.page"
:limit.sync="listQuery2.page_size"
@pagination="getList2"
/>
</el-tab-pane>
<el-tab-pane label="入场不合格" name="4">
<el-table
v-loading="listLoading"
:data="fifodetailList4.results"
border
fit
stripe
highlight-current-row
height="620"
v-el-height-adaptive-table="{bottomOffset: 40}"
>
<el-table-column type="index" width="50" />
<el-table-column label="物料批次">
<template slot-scope="scope">{{ scope.row.batch }}</template>
</el-table-column>
<el-table-column label="物料名称">
<template slot-scope="scope">{{ scope.row.material_.name }}</template>
</el-table-column>
<el-table-column label="规格型号">
<template slot-scope="scope">{{
scope.row.material_.specification
}}</template>
</el-table-column>
<el-table-column label="物料单位">
<template slot-scope="scope">{{ scope.row.material_.unit }}</template>
</el-table-column>
<el-table-column label="创建时间">
<template slot-scope="scope">{{ scope.row.create_time }}</template>
</el-table-column>
</el-table>
<pagination
v-show="fifodetailList4.count > 0"
:total="fifodetailList4.count"
:page.sync="listQuery4.page"
:limit.sync="listQuery4.page_size"
@pagination="getList4"
/>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script>
import { getProductionplanList } from "@/api/pm";
import { getwproductticketList,getwproductList } from "@/api/wpm";
import { ticketread } from "@/api/workflow";
import { getfifodetailList} from "@/api/inm";
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
export default {
components: { Pagination },
data() {
return {
wproductList: {
count: 0,
},
actstate_: {
6: "待复检",
10: "操作进行中",
20: "待检验",
30: "已合格",
40: "库存中",
50: "不合格",
60: "待成品检验",
8: "操作准备中",
26: "待夹层检验",
70: "报废",
},
listQuery: {
page: 1,
page_size: 20,
},
wproductticket: {
count: 0,
},
listQuery2: {
page: 1,
page_size: 20,
},
customfieldList: [],
activeName: "1",
decision_: {
10: "返工",
20: "返修",
30: "报废",
40: "让步接受",
50: "偏离许可",
60: "降级使用",
70: "退回供方",
80: "召回",
},
listQuery4: {
page: 1,
page_size: 20,
},
fifodetailList4:{
count: 0,
},
};
},
computed: {},
watch: {},
created() {
this.getList(); //成品不合格
this.getList2(); //过程
this.getList4();//入场不合格
},
methods: {
checkPermission,
//不合格成品
getList() {
this.listLoading = true;
this.listQuery.material__type = 1;
this.listQuery.tag='ok';
getwproductList(this.listQuery).then((response) => {
if (response.data) {
this.wproductList = response.data;
}
});
},
//不合格玻璃审批工单列表
getList2() {
getwproductticketList(this.listQuery2).then((response) => {
if (response.data) {
this.wproductticket = response.data;
}
});
},
//不合格玻璃审理单查看
handledetailbhg(scope)
{
this.$router.push({name:"ticketDetail",params:{ticketId:scope.row.ticket}})
},
//入场检验不合格
//不合格物料
getList4() {
this.listLoading = true;
this.listQuery4.fifo__type = 3;
this.listQuery4.is_testok = false;
this.listQuery4.is_tested = true;
getfifodetailList(this.listQuery4).then((response) => {
if (response.data) {
this.fifodetailList4 = response.data;
}
this.listLoading = false;
});
},
},
};
</script>

View File

@ -206,8 +206,8 @@ export default {
dialogVisible: false, dialogVisible: false,
dialogType: "new", dialogType: "new",
rule1: { rule1: {
number: [{ required: true, message: "请输入", trigger: "blur" }],
customer: [{ required: true, message: "请输入", trigger: "blur" }],
product: [{ required: true, message: "请输入", trigger: "blur" }], product: [{ required: true, message: "请输入", trigger: "blur" }],
delivery_date: [{ required: true, message: "请输入", trigger: "blur" }], delivery_date: [{ required: true, message: "请输入", trigger: "blur" }],
}, },

View File

@ -262,7 +262,7 @@ export default {
if (response.data) { if (response.data) {
this.sale.customer = response.data.customer; this.sale.customer = response.data.customer;
this.sale.product = response.data.product; this.sale.product = response.data.product;
getiproductList({page:0,material__type:1,material:this.sale.product}).then((response) => { getiproductList({page:0,to_order:response.data.id}).then((response) => {
if (response.data) { if (response.data) {
this.iproductoptions=response.data; this.iproductoptions=response.data;
} }

View File

@ -3,8 +3,10 @@
<el-card> <el-card>
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">
<span>基本信息</span> <span>基本信息</span>
<el-button style="float: right; padding: 3px 0" @click="readbook()" type="text">查看作业指导书</el-button>
<el-button style="float: right; padding: 3px 0" @click="getWordText()" type="text">查看作业指导书</el-button>
</div> </div>
<el-form <el-form
ref="form" ref="form"
@ -57,18 +59,13 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-drawer <el-drawer
title="作业指导书点击下载查看!" title="作业指导书查看!"
:visible.sync="drawer" :visible.sync="drawer"
direction="rtl" direction="rtl"
size="10%"> size="70%">
<el-table :data="techdocList"> <div class="word-wrap">
<el-table-column label="表名称" width="150"> <div id="wordView" v-html="wordText" />
</div>
<template slot-scope="scope">
<el-link type="primary" :href="scope.row.file_.file" target="_blank">{{ scope.row.file_.name }}</el-link>
</template>
</el-table-column>
</el-table>
</el-drawer> </el-drawer>
</el-card> </el-card>
<el-row gutter="2"> <el-row gutter="2">
@ -602,7 +599,7 @@ import {
createOutputs, createOutputs,
deleteOperationeinput deleteOperationeinput
} from "@/api/wpm"; } from "@/api/wpm";
import mammoth from "mammoth";
import { getrffieldList,gettechdocList } from "@/api/mtm"; import { getrffieldList,gettechdocList } from "@/api/mtm";
import checkPermission from "@/utils/permission"; import checkPermission from "@/utils/permission";
import { getprogressList } from "@/api/pm"; import { getprogressList } from "@/api/pm";
@ -731,13 +728,15 @@ export default {
outputData: [], outputData: [],
tprogressData: [], tprogressData: [],
tool:"", tool:"",
wordText: "",
}; };
}, },
computed: {}, computed: {},
watch: {}, watch: {},
created() { created() {
this.id = this.$route.params.id; //操作ID this.id = this.$route.params.id; //操作ID
this.readbook();
this.getList(); //边角料 this.getList(); //边角料
this.getpwpList(); //半成品 this.getpwpList(); //半成品
this.getequList(); //设备 this.getequList(); //设备
@ -752,11 +751,35 @@ export default {
checkPermission, checkPermission,
//作业指导书 //作业指导书
getWordText() {
this.drawer=true;
const xhr = new XMLHttpRequest();
xhr.open("get", this.techdocList, true);
xhr.responseType = "arraybuffer";
xhr.onload = () => {
if (xhr.status == 200) {
mammoth.convertToHtml({ arrayBuffer: new Uint8Array(xhr.response) }).then((resultObject) => {
this.$nextTick(() => {
this.wordText = resultObject.value;
});
});
}
};
xhr.send();
},
readbook(){ readbook(){
this.drawer=true;
gettechdocList({operation:this.id,page:0}).then((response) => { gettechdocList({operation:this.id,page:0}).then((response) => {
if (response.data) { if (response.data) {
this.techdocList= response.data; this.techdocList= response.data[0].file_.file;
console.log( this.techdocList);
} }
}); });
}, },

File diff suppressed because it is too large Load Diff

View File

@ -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']

View File

@ -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,
},
),
]

View File

@ -36,8 +36,12 @@ class Employee(CommonAModel):
def __str__(self): def __str__(self):
return self.name return self.name
# class Attendance(CommonADModel): class ClockRecord(CommonADModel):
# """ """
# 出勤记录 打卡记录
# """ """
ClOCK_WORK1 = 10
type_choice = (
(ClOCK_WORK1, '上班打卡'),
)
type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1)

View File

@ -1,25 +1,24 @@
from apps.system.models import User from apps.system.models import User
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework import serializers from rest_framework import serializers
from .models import Employee from .models import ClockRecord, Employee
from apps.system.serializers import UserListSerializer, UserSimpleSerializer from apps.system.serializers import UserListSerializer, UserSimpleSerializer
from django.db.models.query import Prefetch from django.db.models.query import Prefetch
class EmployeeSerializer(ModelSerializer): class EmployeeSerializer(ModelSerializer):
# user_ = UserListSerializer(source='user', read_only=True)
class Meta: class Meta:
model = Employee model = Employee
fields = '__all__' exclude = ['face_data']
# @staticmethod
# def setup_eager_loading(queryset):
# """ Perform necessary eager loading of data. """
# queryset = queryset.select_related('user', 'user__dept')
# # queryset = queryset.prefetch_related('user','user__dept')
# queryset = queryset.prefetch_related(
# Prefetch('user_',
# queryset=User.objects.filter(employee_user__isnull=True))
# )
# return queryset
class FaceLoginSerializer(serializers.Serializer): class FaceLoginSerializer(serializers.Serializer):
base64 = serializers.CharField() 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__'

View File

@ -0,0 +1,48 @@
from django.conf import settings
import uuid
import face_recognition
import os
from apps.hrm.models import Employee
from apps.hrm.tasks import update_all_user_facedata_cache
from apps.system.models import User
from django.core.cache import cache
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, '头像解码失败'
# 匹配人脸库
face_datas = cache.get('face_datas')
face_users = cache.get('face_users')
if face_datas is None:
update_all_user_facedata_cache()
results = face_recognition.compare_faces(face_datas, unknown_face_encoding, tolerance=0.5)
for index, value in enumerate(results):
if value:
# 识别成功
user = User.objects.get(id=face_users[index])
return user, ''
return None, '识别失败'
def get_facedata_from_img(cls, img_rpath):
try:
photo_path = settings.BASE_DIR + img_rpath
picture_of_me = face_recognition.load_image_file(photo_path)
my_face_encoding = face_recognition.face_encodings(picture_of_me)[0]
face_data_list = my_face_encoding.tolist()
return face_data_list
except:
return None

View File

@ -2,8 +2,12 @@ from django.db.models.signals import post_save
from apps.system.models import User from apps.system.models import User
from django.dispatch import receiver from django.dispatch import receiver
from apps.hrm.models import Employee from apps.hrm.models import Employee
from django.conf import settings
import face_recognition
import logging
logger = logging.getLogger('log')
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def createEmployee(sender, instance, created, **kwargs): def createEmployee(sender, instance, created, **kwargs):
if created: if created:
Employee.objects.get_or_create(user=instance) Employee.objects.get_or_create(user=instance)

View File

@ -0,0 +1,31 @@
from __future__ import absolute_import, unicode_literals
from celery import shared_task
from apps.hrm.models import Employee
from apps.system.models import User
from django.core.cache import cache
@shared_task
def update_all_user_not_atwork():
"""
将所有员工设为非在岗状态
"""
User.objects.all().update(is_atwork=False)
@shared_task
def update_all_user_facedata_cache():
"""
更新人脸数据缓存
"""
facedata_queyset = Employee.objects.filter(face_data__isnull=False,
user__is_active=True).values('user', 'face_data')
face_users = []
face_datas = []
for i in facedata_queyset:
face_users.append(i['user'])
face_datas.append(i['face_data'])
cache.set('face_users', face_users, timeout=None)
cache.set('face_datas', face_datas, timeout=None)

View File

@ -1,11 +1,12 @@
from django.db.models import base from django.db.models import base
from rest_framework import urlpatterns 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 django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
router = DefaultRouter() router = DefaultRouter()
router.register('employee', EmployeeViewSet, basename='employee') router.register('employee', EmployeeViewSet, basename='employee')
router.register('clock_record', ClockRecordViewSet, basename='clock_record')
urlpatterns = [ urlpatterns = [
path('facelogin/', FaceLogin.as_view()), path('facelogin/', FaceLogin.as_view()),
path('', include(router.urls)), path('', include(router.urls)),

View File

@ -1,33 +1,27 @@
from functools import update_wrapper
from django.shortcuts import render from django.shortcuts import render
from django.utils import timezone
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, GenericViewSet 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.hrm.tasks import update_all_user_facedata_cache
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from apps.hrm.models import Employee from apps.hrm.models import ClockRecord, Employee
from apps.hrm.serializers import EmployeeSerializer, FaceLoginSerializer from apps.hrm.serializers import ClockRecordListSerializer, EmployeeSerializer, FaceClockCreateSerializer, FaceLoginSerializer
import face_recognition
from django.conf import settings
from django.core.cache import cache
import logging
from rest_framework.generics import CreateAPIView from rest_framework.generics import CreateAPIView
from rest_framework import status from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework import exceptions
from apps.system.models import User from apps.system.models import User
logger = logging.getLogger('log') from apps.system.serializers import UserSimpleSerializer
from rest_framework.permissions import AllowAny
def load_face_data(username:int, path:str):
"""
将某用户face_encoding加载进缓存
"""
face_datas = cache.get_or_set('face_datas', {}, timeout=None)
photo_path = settings.BASE_DIR + path
picture_of_me = face_recognition.load_image_file(photo_path)
my_face_encoding = face_recognition.face_encodings(picture_of_me)[0]
face_datas[username] = my_face_encoding
cache.set('face_datas', face_datas, timeout=None)
return my_face_encoding
# Create your views here. # Create your views here.
class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMixin, RetrieveModelMixin, GenericViewSet): class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMixin, RetrieveModelMixin, GenericViewSet):
@ -39,20 +33,74 @@ class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMix
serializer_class = EmployeeSerializer serializer_class = EmployeeSerializer
ordering = ['-pk'] ordering = ['-pk']
def perform_update(self, serializer): def update(self, request, *args, **kwargs):
instance = serializer.save(update_by = self.request.user) partial = kwargs.pop('partial', False)
try: instance = self.get_object()
photo_path = settings.BASE_DIR + instance.photo data = request.data
picture_of_me = face_recognition.load_image_file(photo_path) serializer = self.get_serializer(instance, data=data, partial=partial)
my_face_encoding = face_recognition.face_encodings(picture_of_me)[0] serializer.is_valid(raise_exception=True)
instance.face_data = my_face_encoding.tolist() photo = data.get('photo', None)
instance.save() if instance.photo != photo:
except: f_l = HRMService.get_facedata_from_img(photo)
logger.error('人脸识别出错') if f_l:
serializer.save(update_by=request.user, face_data = f_l)
# 更新人脸缓存
update_all_user_facedata_cache.delay()
return Response()
return Response('头像识别失败', status=status.HTTP_400_BAD_REQUEST)
serializer.save(update_by=request.user)
return Response()
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 uuid
import base64 import base64
import os
def tran64(s): def tran64(s):
missing_padding = len(s) % 4 missing_padding = len(s) % 4
@ -70,41 +118,13 @@ class FaceLogin(CreateAPIView):
""" """
人脸识别登录 人脸识别登录
""" """
# serializer = FaceLoginSerializer(data=request.data) base64_data = base64.urlsafe_b64decode(tran64(request.data.get('base64').replace(' ', '+')))
# serializer.is_valid(raise_exception=True) user, msg = HRMService.face_compare_from_base64(base64_data)
filename = str(uuid.uuid4()) if user:
filepath = settings.BASE_DIR +'/temp/' + filename +'.png' refresh = RefreshToken.for_user(user)
with open(filepath, 'wb') as f: return Response({
data = tran64(request.data.get('base64').replace(' ', '+')) 'refresh': str(refresh),
f.write(base64.urlsafe_b64decode(data)) 'access': str(refresh.access_token),
# picture_of_me = face_recognition.load_image_file(settings.BASE_DIR +'/temp/me.png') 'username':user.username
# 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) return Response(msg, status=status.HTTP_400_BAD_REQUEST)
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)

View File

@ -9,6 +9,7 @@ from apps.mtm.serializers import MaterialSimpleSerializer
from django.db import transaction from django.db import transaction
class WareHouseSerializer(serializers.ModelSerializer): class WareHouseSerializer(serializers.ModelSerializer):
create_by_ = UserSimpleSerializer('create_by', read_only=True) create_by_ = UserSimpleSerializer('create_by', read_only=True)

View File

@ -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='当前在岗'),
),
]

View File

@ -116,6 +116,7 @@ class User(AbstractUser):
superior = models.ForeignKey( superior = models.ForeignKey(
'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='上级主管') 'self', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='上级主管')
roles = models.ManyToManyField(Role, blank=True, verbose_name='角色') roles = models.ManyToManyField(Role, blank=True, verbose_name='角色')
is_atwork = models.BooleanField('当前在岗', default=False)
class Meta: class Meta:
verbose_name = '用户信息' verbose_name = '用户信息'

View File

@ -141,7 +141,9 @@ class UserListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ['id', 'name', 'phone', 'email', 'position', 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 @staticmethod
def setup_eager_loading(queryset): def setup_eager_loading(queryset):

View File

@ -194,7 +194,7 @@ AUTHENTICATION_BACKENDS = (
# CACHES = { # CACHES = {
# "default": { # "default": {
# "BACKEND": "django_redis.cache.RedisCache", # "BACKEND": "django_redis.cache.RedisCache",
# "LOCATION": "redis://redis:6379/1", # "LOCATION": "redis://127.0.0.1:6379/0",
# "OPTIONS": { # "OPTIONS": {
# "CLIENT_CLASS": "django_redis.client.DefaultClient", # "CLIENT_CLASS": "django_redis.client.DefaultClient",
# "PICKLE_VERSION": -1 # "PICKLE_VERSION": -1
@ -203,7 +203,7 @@ AUTHENTICATION_BACKENDS = (
# } # }
# celery配置,celery正常运行必须安装redis # celery配置,celery正常运行必须安装redis
CELERY_BROKER_URL = "redis://redis:6379/0" # 任务存储 CELERY_BROKER_URL = "redis://127.0.0.1:6379/1" # 任务存储
CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行300个任务就会被销毁可防止内存泄露 CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行300个任务就会被销毁可防止内存泄露
CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区 CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区
CELERY_ENABLE_UTC = True # 启动时区设置 CELERY_ENABLE_UTC = True # 启动时区设置