This commit is contained in:
shilixia 2022-01-25 09:57:51 +08:00
commit 60f726cda6
45 changed files with 791 additions and 264 deletions

View File

@ -28,6 +28,7 @@
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"fuse.js": "^6.4.6", "fuse.js": "^6.4.6",
"js-cookie": "^3.0.0", "js-cookie": "^3.0.0",
"mammoth": "^1.4.19",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
@ -37,7 +38,7 @@
"vue-quill-editor": "^3.0.6", "vue-quill-editor": "^3.0.6",
"vue-router": "^3.5.2", "vue-router": "^3.5.2",
"vuex": "^3.6.2", "vuex": "^3.6.2",
"webpack-dev-server": "^4.2.0", "webpack-dev-server": "^4.7.3",
"xlsx": "^0.17.1" "xlsx": "^0.17.1"
}, },
"devDependencies": { "devDependencies": {

15
hb_client/src/api/hrm.js Normal file
View File

@ -0,0 +1,15 @@
import request from '@/utils/request'
export function faceLogin(data) {
return request({
url: '/hrm/facelogin/',
method: 'post',
data
})
}
export function clockRecord(data) {
return request({
url: '/hrm/clock_record/',
method: 'post',
data
})
}

View File

@ -2,7 +2,7 @@ import request from '@/utils/request'
export function login(data) { export function login(data) {
return request({ return request({
url: '/token/', url: '/auth/token/',
method: 'post', method: 'post',
data data
}) })
@ -10,7 +10,7 @@ export function login(data) {
export function logout() { export function logout() {
return request({ return request({
url: '/token/black/', url: '/auth/token/black/',
method: 'get' method: 'get'
}) })
} }

View File

@ -299,6 +299,18 @@
} }
} , } ,
}, },
watch: {
'$route.path': function (newVal) {
if(newVal==='/dashboard'){
this.$refs.leftMenu.handlerSelect(this.list[0]);
this.$refs.chart.scrollTo({
top: 0,
left: this.list[0].left - 30,
behavior: "smooth"
});
}
}
},
data() { data() {
return { return {
windowWidth:0, windowWidth:0,

View File

@ -10,8 +10,7 @@
</div> </div>
</template> </template>
<script> <script>
import {faceLogin} from "@/api/testModel"; import { clockRecord } from '@/api/hrm'
import { getTickets } from "@/api/workflow";
import tracking from '@/assets/tracking/build/tracking-min.js'; import tracking from '@/assets/tracking/build/tracking-min.js';
import '@/assets/tracking/build/data/face-min.js'; import '@/assets/tracking/build/data/face-min.js';
@ -62,7 +61,7 @@
// 避免在新的浏览器中使用它因为它正在被弃用 // 避免在新的浏览器中使用它因为它正在被弃用
_this.video.src = window.URL.createObjectURL(stream) _this.video.src = window.URL.createObjectURL(stream)
} }
_this.video.onloadedmetadata = function (e) { _this.video.onloadedmetadata = function () {
_this.video.play(); _this.video.play();
}; };
_this.init(); _this.init();
@ -117,33 +116,12 @@
let base64Img = canvas.toDataURL('image/jpeg'); let base64Img = canvas.toDataURL('image/jpeg');
let img = base64Img.split(',')[1]; let img = base64Img.split(',')[1];
let imgData = {base64:img}; let imgData = {base64:img};
faceLogin(imgData).then((res) => { clockRecord(imgData).then((res) => {
if (res.code >= 200) { if (res.code === 200&&res.data.id) {
/* debugger; this.$message.success(res.data.name+'签到成功!');
console.log(res.data); setTimeout(()=>{
getTickets( {token:res.data.access,page:1,page_size:10}).then((resp)=>{
if(resp.code>=200){
debugger;
console.log(resp.data);
}
})*/
that.uploadLock = false;
this.$message.success(res.data.username+'签到成功!');
/*that.$confirm("是否提交?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
// that.$emit('func',false);
// 请求接口成功以后打开锁
that.uploadLock = false; that.uploadLock = false;
}).catch((err) => { },3000)
that.$message.success(err);
// that.$emit('func',false);
// 请求接口成功以后打开锁
that.uploadLock = false;
});*/
// this.$message.success("成功");
}else{ }else{
// 打开锁 // 打开锁
that.uploadLock = false; that.uploadLock = false;

View File

@ -50,7 +50,7 @@ export const constantRoutes = [
path: 'dashboard', path: 'dashboard',
name: 'Dashboard', name: 'Dashboard',
component: () => import('@/views/dashboard/index'), component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard', affix: true } meta: { title: '首页', icon: 'dashboard', affix: true,keepAlive: false }
}] }]
}, },
{ {

View File

@ -18,6 +18,9 @@
<span class="totalCount">{{contractTotalCurrent}}</span> <span class="totalCount">{{contractTotalCurrent}}</span>
<span></span> <span></span>
</div> </div>
<!--<div style="height: 35px;line-height: 35px;font-size: 16px;color: #333333">
<span>本年合同总数</span>
</div>-->
</div> </div>
</div> </div>
<div class="cardsWrap" @click="toDetail('2')"> <div class="cardsWrap" @click="toDetail('2')">
@ -141,59 +144,134 @@
</el-row> </el-row>
</div> </div>
<el-row :gutter="5"> <el-row :gutter="5">
<el-col :span="12"> <el-col :span="8">
<div class="dashboardBottomRow"> <div class="dashboardBottomRow">
<div class="dashboardCardHand"> <div class="dashboardCardHand">
<div class="CardTitleWrap"> <div class="CardTitleWrap">
<span class="verticalLine"></span><span class="dashboardCardTitle">库存列表</span> <span class="verticalLine"></span><span class="dashboardCardTitle">生产设备</span>
<span class="stockMore" @click="stockMore">更多>></span>
</div> </div>
<div class="block"> <div class="block">
<el-pagination <el-pagination
:current-page.sync="stockPage" small
:page-size="stockPageSize" :pager-count="pageCounts"
:current-page.sync="equipmentPage"
:page-size="equipmentPageSize"
layout="prev, pager, next, jumper" layout="prev, pager, next, jumper"
:total="stockTotal" :total="equipmentTotal"
@size-change="handleStockSizeChange" @current-change="handleEquipmentCurrentChange"
@current-change="handleStockCurrentChange"
> >
</el-pagination> </el-pagination>
</div> </div>
</div> </div>
<div class="dashboardCardPadding"> <div class="dashboardCardPadding">
<el-table <el-table
v-loading="listLoadingStock" small
:data="stockList" v-loading="listLoadingEm"
:data="equipmentList"
fit stripe fit stripe
size="mini" size="mini"
:height="cardTabelHeight" :height="cardTabelHeight"
pager-count="3"
style="border-top: 1px solid #f5f5f5;" style="border-top: 1px solid #f5f5f5;"
> >
<el-table-column label="物料编号" prop="material_"> <el-table-column label="序号" type="index" width="50">
<template slot-scope="scope">{{ scope.row.material_.number }}</template>
</el-table-column> </el-table-column>
<el-table-column label="物料名称" prop="material_" show-overflow-tooltip> <el-table-column label="设备编号" prop="number">
<template slot-scope="scope">{{ scope.row.material_.name }}</template>
</el-table-column> </el-table-column>
<el-table-column label="规格型号" prop="material_" show-overflow-tooltip> <el-table-column label="设备名称" prop="name" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.material_.specification }}</template>
</el-table-column> </el-table-column>
<el-table-column label="物料类型" prop="material_"> <el-table-column label="设备状态" prop="material_">
<template slot-scope="scope">{{options[scope.row.material_.type]}}</template> <!--type=1生产设备-->
<template slot-scope="scope">
<div v-if="scope.row.type===1">
<el-tag v-if="scope.row.state===40" type="danger">
禁用
</el-tag>
<el-tag v-else type="success">
合格
</el-tag>
</div>
<div v-else>
<el-tag v-if="scope.row.state===10" type="success">
{{ state_[scope.row.state] }}
</el-tag>
<el-tag v-else-if="scope.row.state===20">
{{ state_[scope.row.state] }}
</el-tag>
<el-tag v-else-if="scope.row.state===30" type="warning">
{{ state_[scope.row.state] }}
</el-tag>
<el-tag v-else type="danger">
{{ state_[scope.row.state] }}
</el-tag>
</div>
</template>
</el-table-column> </el-table-column>
<el-table-column label="单位" prop="material_"> <el-table-column label="下次校准日期" prop="model">
<template slot-scope="scope">{{scope.row.material_.unit}}</template> <template slot-scope="scope">
</el-table-column> <el-tag v-if="scope.row.next_check_date" :type="setClass(scope.row.next_check_date)">
<el-table-column label="库存" prop="count"> {{scope.row.next_check_date}}
</el-table-column> </el-tag>
<el-table-column label="仓库" prop="warehouse_"> <div v-else></div>
<template slot-scope="scope">{{ scope.row.warehouse_.name }}</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
</div> </div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="8">
<div class="dashboardBottomRow">
<div class="dashboardCardHand">
<div class="CardTitleWrap">
<span class="verticalLine"></span><span class="dashboardCardTitle">人员到岗</span>
</div>
<div class="block">
<el-pagination
small
:pager-count="pageCounts"
:current-page.sync="userPage"
:page-size="userPageSize"
layout="prev, pager, next, jumper"
:total="userTotal"
@current-change="handleUserCurrentChange"
>
</el-pagination>
</div>
</div>
<div
class="dashboardCardPadding"
@mouseenter="stopScroll()"
@mouseleave="toScroll()"
>
<el-table
v-loading="listLoadingUser"
:data="userList"
fit stripe
size="mini"
:height="cardTabelHeight"
style="border-top: 1px solid #f5f5f5;"
>
<el-table-column label="id" prop="id" width="50">
</el-table-column>
<el-table-column label="人员名称" prop="name" show-overflow-tooltip>
</el-table-column>
<el-table-column label="到岗情况" prop="dept_name">
<template slot-scope="scope">
<el-tag v-if="scope.row.is_atwork" type="success">
到岗
</el-tag>
<el-tag v-else type="danger">
未到
</el-tag>
</template>
</el-table-column>
<el-table-column label="部门" prop="dept_name">
</el-table-column>
</el-table>
</div>
</div>
</el-col>
<el-col :span="8">
<div class="dashboardBottomRow"> <div class="dashboardBottomRow">
<div class="dashboardCardHand"> <div class="dashboardCardHand">
<div class="CardTitleWrap"> <div class="CardTitleWrap">
@ -201,12 +279,14 @@
</div> </div>
<div class="block"> <div class="block">
<el-pagination <el-pagination
:current-page.sync="remindPage" small
:page-size="remindPageSize" :pager-count="pageCounts"
:current-page.sync="warningPage"
:page-size="warningPageSize"
layout="prev, pager, next, jumper" layout="prev, pager, next, jumper"
:total="remindTotal" :total="warningTotal"
@size-change="handleRemindSizeChange" @size-change="handleWarningSizeChange"
@current-change="handleRemindCurrentChange" @current-change="handleWarningCurrentChange"
> >
</el-pagination> </el-pagination>
</div> </div>
@ -215,38 +295,31 @@
<el-tabs type="card" :style="{height:cardTabelHeight+'px'}" v-model="activeName" <el-tabs type="card" :style="{height:cardTabelHeight+'px'}" v-model="activeName"
@tab-click="activeNameClick"> @tab-click="activeNameClick">
<el-tab-pane label="库存警告" name="库存警告"> <el-tab-pane label="库存警告" name="库存警告">
<ul :style="{height:cardTabelHeight-47+'px'}" class="lists" :class="{anim:animate}" @mouseenter="Stop()" @mouseleave="Up()"> <ul :style="{height:cardTabelHeight-47+'px'}" class="lists" :class="{anim:animate}" @mouseenter="Stop()"
<li v-for="item in list" :key="item.id" class="listItem"> @mouseleave="Up()">
<li class="listItem" v-for="item in warningList" :key="item.id">
<div class="itemText"> <div class="itemText">
<span>{{item.name}}</span><span style="float: right">2021-12-30</span> <span>{{item.name}}({{item.unit}})</span><span>剩余{{item.count}},低于安全库存{{item.count_safe}}</span>
</div> </div>
</li> </li>
</ul> </ul>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="临近交货" name="临近交货"> <el-tab-pane label="临近交货" name="临近交货">
<ul class="lists"> <ul :style="{height:cardTabelHeight-47+'px'}" class="lists" :class="{anim:animate}" @mouseenter="Stop()"
<li class="listItem"> @mouseleave="Up()">
<div class="itemText">
<span>某某批货临近交货日期</span><span>2021-12-20</span>
</div>
</li>
<li class="listItem" v-for="item in warningList" :key="item.id"> <li class="listItem" v-for="item in warningList" :key="item.id">
<div class="itemText"> <div class="itemText">
<span>{{item}}</span><span>2021-12-20</span> <span>{{item.name}}({{item.number}})</span><span>{{item.delivery_date}}到交货日期</span>
</div> </div>
</li> </li>
</ul> </ul>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="过期提醒" name="过期提醒"> <el-tab-pane label="过期提醒" name="过期提醒">
<ul class="lists"> <ul :style="{height:cardTabelHeight-47+'px'}" class="lists" :class="{anim:animate}" @mouseenter="Stop()"
<li class="listItem"> @mouseleave="Up()">
<div class="itemText">
<span>某某批货临近过期</span><span>2021-11-20</span>
</div>
</li>
<li class="listItem" v-for="item in warningList" :key="item.id"> <li class="listItem" v-for="item in warningList" :key="item.id">
<div class="itemText"> <div class="itemText">
<span>{{item}}</span><span>2021-12-20</span> <span>{{item.name}}({{item.number}})</span><span>{{item.delivery_date}}到期</span>
</div> </div>
</li> </li>
</ul> </ul>
@ -262,15 +335,14 @@
<script> <script>
import echarts from 'echarts' import echarts from 'echarts'
import {mapGetters} from 'vuex'; import {mapGetters} from 'vuex';
import {getPlanGantt} from "@/api/srm"; import {getUserList} from "@/api/user";
import {getProcessYield} from "@/api/srm";
import {getMaterialList} from "@/api/mtm"; import {getMaterialList} from "@/api/mtm";
import {getInventoryList} from "@/api/inm";
import {getProductionplanList} from "@/api/pm"; import {getProductionplanList} from "@/api/pm";
import {getmaterialbatchList} from "@/api/inm"; import {getmaterialbatchList} from "@/api/inm";
import gantt from "@/components/Gantt/dashGantt"; import gantt from "@/components/Gantt/dashGantt";
import {getContractList, getOrderList} from "@/api/sam"; import {getpEquipmentList} from "@/api/equipment";
import {getProcessYield ,getPlanGantt} from "@/api/srm";
import {getContractList , getOrderList} from "@/api/sam";
export default { export default {
components: {gantt}, components: {gantt},
name: 'Dashboard', name: 'Dashboard',
@ -278,27 +350,32 @@
return { return {
animate: false, animate: false,
intNum: null, intNum: null,
intNumUser: null,
animateUser: false,
chartColumn: null, chartColumn: null,
week: null, week: null,
currentTime: null, currentTime: null,
currentYear: null, currentYear: null,
currentMonth: null, currentMonth: null,
currentDay: null, currentDay: null,
stockPage: 1, pageCounts: 5,
stockPageSize: 10, userPage: 1,
stockTotal: 100, userPageSize: 10,
remindPage: 1, userTotal: 0,
remindPageSize: 20, equipmentPage: 1,
remindTotal: 100, equipmentPageSize: 10,
equipmentTotal: 0,
warningPage: 1,
warningPageSize: 10,
warningTotal: 0,
tableIndex: null, tableIndex: null,
chartIndex: null, chartIndex: null,
tableDate: '2021-12', tableDate: '2021-12',
chartDate: [], chartDate: [],
proList: [], proList: [],
planList: [], userList: [],
stockList: [],
remindList: [],
warningList: [], warningList: [],
equipmentList: [],
list: [ list: [
{id: 1, name: 'HIehd91', card: '3337', sco: 'REF-32'}, {id: 1, name: 'HIehd91', card: '3337', sco: 'REF-32'},
{id: 2, name: 'HIehd92', card: '3337', sco: 'REF-32'}, {id: 2, name: 'HIehd92', card: '3337', sco: 'REF-32'},
@ -308,6 +385,12 @@
{id: 6, name: 'HIehd96', card: '3337', sco: 'REF-32'}, {id: 6, name: 'HIehd96', card: '3337', sco: 'REF-32'},
{id: 7, name: 'HIehd97', card: '3337', sco: 'REF-32'}, {id: 7, name: 'HIehd97', card: '3337', sco: 'REF-32'},
], ],
state_: {
10: '完好',
20: '限用',
30: '在修',
40: '禁用',
},
options: { options: {
"1": '成品', "1": '成品',
"2": '半成品', "2": '半成品',
@ -330,7 +413,8 @@
rderTotalCount: null,//累计交付产品总数 rderTotalCount: null,//累计交付产品总数
derTotalCount: null,//累计不合格产品总数 derTotalCount: null,//累计不合格产品总数
listLoadingPlan: false, listLoadingPlan: false,
listLoadingStock: false, listLoadingEm: false,
listLoadingUser: false,
cardTabelHeight: null, cardTabelHeight: null,
ganttHeight: 0, ganttHeight: 0,
} }
@ -342,19 +426,48 @@
'count' 'count'
]) ])
}, },
watch: {
'$route.path': function (newVal) {
if(newVal==='/dashboard') {
this.getUserList();//用户列表
this.getEquipmentList();//设备列表
this.getGanttData();//甘特图数据
this.getStatisticsData();//统计数据
this.getNoticeData();//提醒列表
}
}
},
methods: { methods: {
setClass:function(check_date) {
let obj = {};
if(check_date!=null){
let dat = new Date();
let time = dat.getTime();
let check = new Date(check_date).getTime();
let timeDiffer = (check-time)/1000/60/60/24;
if (4>timeDiffer&&timeDiffer>0) {
obj = 'warning';
}else if (timeDiffer<0) {
obj = "danger";
}
}
return obj;
},
getNoticeData() { getNoticeData() {
this.ScrollUp(); this.ScrollUp();
}, },
ScrollUp() { ScrollUp() {
this.intNum = setInterval(() => { let that = this;
this.animate = true; that.intNum = setInterval(() => {
setTimeout(() => { if(that.warningList.length>3){
this.list.push(this.list[0]); that.animate = true;
this.list.shift(); setTimeout(() => {
this.animate = false; that.warningList.push(that.warningList[0]);
}, 500) that.warningList.shift();
}, 1000); that.animate = false;
}, 3000)
}
}, 3000);
}, },
//鼠标移上去停止 //鼠标移上去停止
Stop() { Stop() {
@ -433,12 +546,85 @@
}); });
//获取库存警告 //获取库存警告
getMaterialList({tag: 'low_inm'}).then((response) => { getMaterialList({page: 1, page_size:that.warningPageSize,tag: 'low_inm'}).then((response) => {
if (response.data) { if (response.data) {
debugger;
that.warningList = response.data.results; that.warningList = response.data.results;
that.warningTotal = response.data.count;
} }
}); });
}, },
//设备列表
getEquipmentList() {
let that = this;
this.listLoadingEm = true;
getpEquipmentList({page: that.equipmentPage, page_size: that.equipmentPageSize}).then((response) => {
if (response.data) {
that.equipmentList = response.data.results;
that.equipmentTotal = response.data.count;
}
that.listLoadingEm = false;
});
},
//设备分页跳转
handleEquipmentCurrentChange(val) {
let that = this;
that.listLoadingEm = true;
that.equipmentPage = val;
getpEquipmentList({page: val, page_size: that.equipmentPageSize}).then((response) => {
if (response.data) {
that.equipmentList = response.data.results;
that.equipmentTotal = response.data.count;
}
that.listLoadingEm = false;
});
},
//用户列表
getUserList() {
let that = this;
that.listLoadingUser = true;
getUserList({page: that.userPage, page_size: that.userPageSize, fields: 'id,name,dept_name,is_atwork'}).then((response) => {
if (response.data) {
that.userList = response.data.results;
that.userTotal = response.data.count;
that.userScroll();
}
that.listLoadingUser = false;
});
},
//用户分页跳转
handleUserCurrentChange(val) {
let that = this;
that.listLoadingUser = true;
that.userPage = val;
getUserList({page: val, page_size: that.userPageSize, fields: 'id,name,dept_name,is_atwork'}).then((response) => {
if (response.data) {
that.userList = response.data.results;
that.userTotal = response.data.count;
that.userScroll();
}
that.listLoadingUser = false;
});
},
userScroll() {
let that = this;
that.intNumUser = setInterval(() => {
that.animateUser = true;
setTimeout(() => {
that.userList.push(that.userList[0]);
that.userList.shift();
that.animateUser = false;
}, 3000)
}, 3000);
},
//鼠标移上去停止
stopScroll() {
clearInterval(this.intNumUser);
},
toScroll() {
this.userScroll();
},
//去往工作流页面
gotoTicketPage() { gotoTicketPage() {
let path = this.$route.path; let path = this.$route.path;
if (path === '/workflow/ticket') { if (path === '/workflow/ticket') {
@ -448,7 +634,7 @@
} }
}, },
//任务排期列表 //任务排期列表
getPlanList() { /*getPlanList() {
let that = this; let that = this;
this.listLoadingPlan = true; this.listLoadingPlan = true;
getProductionplanList({page: 0}).then((response) => { getProductionplanList({page: 0}).then((response) => {
@ -457,9 +643,9 @@
} }
this.listLoadingPlan = false; this.listLoadingPlan = false;
}); });
}, },*/
//库存列表 //库存列表
getStockList() { /*getStockList() {
let that = this; let that = this;
this.listLoadingStock = true; this.listLoadingStock = true;
getInventoryList({page: this.stockPage, page_size: this.stockPageSize}).then((response) => { getInventoryList({page: this.stockPage, page_size: this.stockPageSize}).then((response) => {
@ -470,6 +656,17 @@
this.listLoadingStock = false; this.listLoadingStock = false;
}); });
}, },
//更多库存
stockMore() {
this.$router.push({name: 'warehouse', params: {}})
},
//库存pageSize改变
// @size-change="handleStockSizeChange"
handleEquipmentSizeChange(val) {
this.stockPageSize = val;
this.stockPage = 1;
},
*/
//图标渲染 //图标渲染
drawChart() { drawChart() {
let that = this; let that = this;
@ -583,9 +780,7 @@
}] }]
}); });
}, },
stockMore() {
this.$router.push({name: 'warehouse', params: {}})
},
toDetail(index) { toDetail(index) {
if (index === '1') { if (index === '1') {
this.$router.push({name: 'contract', params: {page: 1, page_size: 20}}) this.$router.push({name: 'contract', params: {page: 1, page_size: 20}})
@ -594,9 +789,9 @@
} else if (index === '3') { } else if (index === '3') {
this.$router.push({name: 'management', params: {page: 1, page_size: 20}}) this.$router.push({name: 'management', params: {page: 1, page_size: 20}})
} else if (index === '4') { } else if (index === '4') {
this.$router.push({name: 'product', params: {page: 1, page_size: 20, material__type: 1}}) this.$router.push({name: 'product'})
} else if (index === '5') { } else if (index === '5') {
this.$router.push({name: 'product', params: {page: 1, page_size: 20, material__type: 1}}) this.$router.push({name: 'unproduct'})
} }
}, },
//便捷查询按钮 //便捷查询按钮
@ -650,7 +845,6 @@
this.drawChart(); this.drawChart();
} }
}); });
} }
//根据时间和类型获取数据 //根据时间和类型获取数据
}, },
@ -683,55 +877,41 @@
}); });
} }
}, },
//库存
handleStockSizeChange(val) {
this.stockPageSize = val;
this.stockPage = 1;
},
//库存
handleStockCurrentChange(val) {
let that = this;
this.listLoading = true;
this.stockPage = val;
getInventoryList({page: val, page_size: this.stockPageSize}).then((response) => {
if (response.data) {
that.stockList = response.data.results;
that.stockTotal = response.data.count;
}
this.listLoading = false;
});
},
//提示 //提示
activeNameClick(tab) { activeNameClick(tab) {
debugger;
debugger;
let that = this; let that = this;
that.warningPage = 1;
that.warningList = [];
if (tab.label === '库存警告') { if (tab.label === '库存警告') {
getMaterialList({page: 0, tag: 'low_inm'}).then((response) => { getMaterialList({page: 1, page_size:that.warningPageSize, tag: 'low_inm'}).then((response) => {
if (response.data) { if (response.data) {
that.warningList = response.data; that.warningList = response.data.results;
that.remindTotal = response.data.length; that.warningTotal = response.data.count;
} }
}); });
} else if (tab.label === '临近交货') { } else if (tab.label === '临近交货') {
getmaterialbatchList({page: 0, tag: 'expired'}).then((response) => { getOrderList({page: 1, page_size:that.warningPageSize,tag:'near_delivery'}).then((response) => {
if (response.data) { if (response.data) {
that.warningList = response.data; that.warningList = response.data.results;
that.remindTotal = response.data.length; that.warningTotal = response.data.count;
} }
}); });
} else if (tab.label === '过期提醒') { } else if (tab.label === '过期提醒') {
getmaterialbatchList({page: 0, tag: 'expired'}).then((response) => { getmaterialbatchList({page: 1, page_size:that.warningPageSize, tag: 'expired'}).then((response) => {
if (response.data) { if (response.data) {
that.warningList = response.data; that.warningList = response.data.results;
that.remindTotal = response.data.length; that.warningTotal = response.data.count;
} }
}); });
} }
}, },
handleRemindSizeChange(val) { handleWarningSizeChange(val) {
this.remindPageSize = val; this.warningPageSize = val;
this.remindPage = 1; this.warningPage = 1;
}, },
handleRemindCurrentChange(val) { handleWarningCurrentChange(val) {
console.log(`当前页: ${val}`); console.log(`当前页: ${val}`);
}, },
getGanttData() { getGanttData() {
@ -807,23 +987,21 @@
}) })
}, },
}, },
mounted() { mounted() {
let hei = document.getElementsByClassName('app-main')[0].clientHeight; let hei = document.getElementsByClassName('app-main')[0].clientHeight;
let heig = document.getElementsByClassName('dashboardTopCard')[0].clientHeight; let heig = document.getElementsByClassName('dashboardTopCard')[0].clientHeight;
this.cardTabelHeight = ((hei - heig - 130) / 2); let domHeight = ((hei - heig - 140) / 2);
this.ganttHeight = this.cardTabelHeight - 50; this.cardTabelHeight = domHeight-35;
document.getElementById('chartColumn').style.height = this.cardTabelHeight - 35 + 'px'; this.ganttHeight = domHeight - 10;
this.getPlanList(); document.getElementById('chartColumn').style.height = domHeight + 'px';
this.getStockList(); this.getUserList();//用户列表
this.getGanttData(); this.getEquipmentList();//设备列表
this.getStatisticsData(); this.getGanttData();//甘特图数据
this.getNoticeData(); this.getStatisticsData();//统计数据
this.getNoticeData();//提醒列表
}, },
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-card.is-always-shadow { .el-card.is-always-shadow {
height: auto !important; height: auto !important;
@ -867,6 +1045,7 @@
.cardsWrap:nth-child(1) { .cardsWrap:nth-child(1) {
.svgIconWrap { .svgIconWrap {
background: #e9f3ff; background: #e9f3ff;
.svgIcon { .svgIcon {
color: #409EFF; color: #409EFF;
} }

View File

@ -0,0 +1,13 @@
<template>
</template>
<script>
export default {
name: "largeScreen"
}
</script>
<style scoped>
</style>

View File

@ -91,6 +91,14 @@
<el-table-column label="检查类型"> <el-table-column label="检查类型">
<template slot-scope="scope">{{ checkTypes[scope.row.type] }}</template> <template slot-scope="scope">{{ checkTypes[scope.row.type] }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作人">
<template slot-scope="scope">{{ scope.row.create_by_.name }}</template>
</el-table-column>
<el-table-column label="操作时间">
<template slot-scope="scope">
<span>{{scope.row.update_time.substring(0,scope.row.update_time.length-3)}}</span>
</template>
</el-table-column>
<el-table-column label="是否提交"> <el-table-column label="是否提交">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.is_submited">已提交</span> <span v-if="scope.row.is_submited">已提交</span>
@ -116,30 +124,48 @@
:close-on-click-modal="false" :close-on-click-modal="false"
> >
<el-row> <el-row>
<el-col v-for="item in fieldList" :key="item.id" :span="12"> <el-col :span="12">
<div class="items"> <div class="items">
<span class="itemLabel">操作人</span>
<span>{{create_by_}}</span>
</div>
</el-col>
<el-col :span="12">
<div class="items">
<span class="itemLabel">操作时间</span>
<span>{{update_time}}</span>
</div>
</el-col>
<el-col v-for="item in fieldList" :key="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 class="itemLabel">{{item.field_name}}</span>
<span>{{item.field_value}}</span> <span>{{item.field_value}}</span>
</div> </div>
</el-col> </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>
</el-dialog> </el-dialog>
</el-card> </el-card>
</div> </div>
</template> </template>
<script> <script>
import { getfifodetailList } from "@/api/inm"; import {getfifodetailList} from "@/api/inm";
import checkPermission from "@/utils/permission"; import checkPermission from "@/utils/permission";
import { mtest,getwproductList } from "@/api/wpm"; import {mtest, getwproductList} from "@/api/wpm";
import { getrecordformList, getrffieldList} from "@/api/mtm"; import {getrecordformList, getrffieldList} from "@/api/mtm";
import {getTestRecord,getTestRecordItem} from "@/api/qm"; import {getTestRecord, getTestRecordItem} from "@/api/qm";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
export default { export default {
components: { Pagination }, components: {Pagination},
data() { data() {
return { return {
wproductList: { wproductList: {
count: 0, count: 0,
}, },
listQuery: { listQuery: {
@ -171,6 +197,8 @@ export default {
mtestform: { mtestform: {
is_mtestok: false, is_mtestok: false,
}, },
create_by_: '',
update_time: '',
wproduct: null, wproduct: null,
recordList: [], recordList: [],
fieldList: [], fieldList: [],
@ -239,6 +267,8 @@ export default {
handleRecordDetail(scope) { handleRecordDetail(scope) {
let that = this; let that = this;
that.fieldList = []; that.fieldList = [];
that.create_by_ = scope.row.create_by_.name;
that.update_time = scope.row.update_time;
getTestRecordItem(scope.row.id).then((res) => { getTestRecordItem(scope.row.id).then((res) => {
if (res.code >= 200) { if (res.code >= 200) {
debugger; debugger;

View File

@ -29,6 +29,16 @@
{{checkTypes[scope.row.type]}} {{checkTypes[scope.row.type]}}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作人">
<template slot-scope="scope">
{{ scope.row.create_by_.name }}
</template>
</el-table-column>
<el-table-column label="操作时间">
<template slot-scope="scope">
<span>{{scope.row.update_time.substring(0,scope.row.update_time.length-3)}}</span>
</template>
</el-table-column>
<el-table-column label="是否提交"> <el-table-column label="是否提交">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.is_submited">已提交</span> <span v-if="scope.row.is_submited">已提交</span>
@ -48,8 +58,20 @@
:close-on-click-modal="false" :close-on-click-modal="false"
> >
<el-row> <el-row>
<el-col :span="12">
<div class="items">
<span class="itemLabel">操作人</span>
<span>{{create_by_}}</span>
</div>
</el-col>
<el-col :span="12">
<div class="items">
<span class="itemLabel">操作时间</span>
<span>{{update_time}}</span>
</div>
</el-col>
<el-col v-for="item in fieldList" :key="item.id" :span="12"> <el-col v-for="item in fieldList" :key="item.id" :span="12">
<div class="items" v-if="item.field_type!=='draw'"> <div class="items" v-if="item.field_type!=='draw'&&item.field_value!==null&&item.field_value!==''">
<span class="itemLabel">{{item.field_name}}</span> <span class="itemLabel">{{item.field_name}}</span>
<span>{{item.field_value}}</span> <span>{{item.field_value}}</span>
</div> </div>
@ -89,6 +111,8 @@
50: '已完成', 50: '已完成',
60: '军检完成' 60: '军检完成'
}, },
create_by_:'',
update_time:'',
recordVisible: false, recordVisible: false,
customfieldList: [], customfieldList: [],
recordform: null, recordform: null,
@ -140,6 +164,8 @@
handleRecordDetail(scope) { handleRecordDetail(scope) {
let that = this; let that = this;
that.fieldList = []; that.fieldList = [];
that.create_by_ = scope.row.create_by_.name;
that.update_time = scope.row.update_time;
getTestRecordItem(scope.row.id).then((res) => { getTestRecordItem(scope.row.id).then((res) => {
if (res.code >= 200) { if (res.code >= 200) {
debugger; debugger;

View File

@ -474,6 +474,14 @@
<el-table-column label="检查类型"> <el-table-column label="检查类型">
<template slot-scope="scope">{{ checkTypes[scope.row.type] }}</template> <template slot-scope="scope">{{ checkTypes[scope.row.type] }}</template>
</el-table-column> </el-table-column>
<el-table-column label="操作人">
<template slot-scope="scope">{{ scope.row.create_by_.name }}</template>
</el-table-column>
<el-table-column label="操作时间">
<template slot-scope="scope">
<span>{{scope.row.update_time.substring(0,scope.row.update_time.length-3)}}</span>
</template>
</el-table-column>
<el-table-column label="是否提交"> <el-table-column label="是否提交">
<template slot-scope="scope"> <template slot-scope="scope">
<span v-if="scope.row.is_submited">已提交</span> <span v-if="scope.row.is_submited">已提交</span>
@ -620,8 +628,20 @@
:close-on-click-modal="false" :close-on-click-modal="false"
> >
<el-row> <el-row>
<el-col :span="12">
<div class="items">
<span class="itemLabel">操作人</span>
<span>{{create_by_}}</span>
</div>
</el-col>
<el-col :span="12">
<div class="items">
<span class="itemLabel">操作时间</span>
<span>{{update_time}}</span>
</div>
</el-col>
<el-col v-for="item in fieldList" :key="item.id" :span="12"> <el-col v-for="item in fieldList" :key="item.id" :span="12">
<div class="items" v-if="item.field_type!=='draw'"> <div class="items" v-if="item.field_type!=='draw'&&item.field_value!==null&&item.field_value!==''">
<span class="itemLabel">{{item.field_name}}</span> <span class="itemLabel">{{item.field_name}}</span>
<span>{{item.field_value}}</span> <span>{{item.field_value}}</span>
</div> </div>
@ -693,6 +713,8 @@
page: 1, page: 1,
page_size: 20, page_size: 20,
}, },
create_by_: '',
update_time: '',
formLabelWidth: '', formLabelWidth: '',
formLabelWidthL: '', formLabelWidthL: '',
actstate_: { actstate_: {
@ -1109,6 +1131,8 @@
handleRecordDetail(scope) { handleRecordDetail(scope) {
let that = this; let that = this;
that.fieldList = []; that.fieldList = [];
that.create_by_ = scope.row.create_by_.name;
that.update_time = scope.row.update_time;
getTestRecordItem(scope.row.id).then((res) => { getTestRecordItem(scope.row.id).then((res) => {
if (res.code >= 200) { if (res.code >= 200) {
that.recordFinishedVisible = true; that.recordFinishedVisible = true;

View File

View File

@ -0,0 +1,4 @@
from django.contrib import admin
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
# Register your models here.

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class AuthConfig(AppConfig):
name = 'apps.auth1'
verbose_name = "认证"

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,5 @@
from rest_framework import serializers
class LoginSerializer(serializers.Serializer):
username = serializers.CharField(label="用户名")
password = serializers.CharField(label="密码")

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@ -0,0 +1,12 @@
from django.urls import path
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
from apps.auth1.views import LoginView, LogoutView, TokenBlackView
urlpatterns = [
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('token/black/', TokenBlackView.as_view(), name='token_black'),
path('login/', LoginView.as_view(), name='api_login'),
path('logout/', LogoutView.as_view(), name='api_logout')
]

View File

@ -0,0 +1,47 @@
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import authenticate, login, logout
from rest_framework.generics import CreateAPIView
from rest_framework.permissions import IsAuthenticated
from apps.auth1.serializers import LoginSerializer
# Create your views here.
class TokenBlackView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs): # 可将token加入黑名单
return Response(status=status.HTTP_200_OK)
class LoginView(CreateAPIView):
authentication_classes = []
permission_classes = []
serializer_class = LoginSerializer
def create(self, request, *args, **kwargs):
"""
账户密码登录
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
user = authenticate(username = vdata.get('username'),
password = vdata.get('password'))
if user is not None:
login(request, user)
return Response()
return Response('登录失败', status=status.HTTP_400_BAD_REQUEST)
class LogoutView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
"""
退出登录
"""
logout(request)
return Response()

View File

@ -2,7 +2,7 @@ from django.db.models import base
from rest_framework import urlpatterns from rest_framework import urlpatterns
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateFIFOItem, UpdateLastTestResult, UpdateSpg from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateEquipState, UpdateFIFOItem, UpdateLastTestResult, UpdateSpg
urlpatterns = [ urlpatterns = [
path('cleandata/', CleanDataView.as_view()), path('cleandata/', CleanDataView.as_view()),
@ -10,6 +10,7 @@ urlpatterns = [
path('update_last_result/', UpdateLastTestResult.as_view()), path('update_last_result/', UpdateLastTestResult.as_view()),
path('update_last_result/', UpdateLastTestResult.as_view()), path('update_last_result/', UpdateLastTestResult.as_view()),
path('update_fifoitem/', UpdateFIFOItem.as_view()), path('update_fifoitem/', UpdateFIFOItem.as_view()),
path('update_spg/', UpdateSpg.as_view()) path('update_spg/', UpdateSpg.as_view()),
path('update_equip_state/', UpdateEquipState.as_view())
] ]

View File

@ -11,6 +11,7 @@ from apps.sam.models import Order
from apps.wf.models import Ticket from apps.wf.models import Ticket
from apps.wpm.models import Operation, OperationMaterial, WProduct, WproductFlow from apps.wpm.models import Operation, OperationMaterial, WProduct, WproductFlow
from apps.wpm.services import WpmServies from apps.wpm.services import WpmServies
from apps.em.tasks import update_equip_state_by_next_check_date
# Create your views here. # Create your views here.
class CleanDataView(APIView): class CleanDataView(APIView):
@ -91,3 +92,11 @@ class UpdateSpg(APIView):
for i in SubProductionPlan.objects.filter(subproduction__process__id=1): for i in SubProductionPlan.objects.filter(subproduction__process__id=1):
WpmServies.update_subproduction_progress_main(sp=i) WpmServies.update_subproduction_progress_main(sp=i)
return Response() return Response()
class UpdateEquipState(APIView):
permission_classes = [IsAdminUser]
def post(self, request, format=None):
update_equip_state_by_next_check_date()
return Response()

View File

@ -0,0 +1,13 @@
from __future__ import absolute_import, unicode_literals
from celery import shared_task
from django.utils import timezone
from apps.em.models import Equipment
@shared_task
def update_equip_state_by_next_check_date():
Equipment.objects.filter(next_check_date__lt=timezone.now()).update(
state = Equipment.EQUIP_STATE_DISABLE
)

View File

@ -6,20 +6,9 @@ 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()

View File

@ -3,7 +3,9 @@ import uuid
import face_recognition import face_recognition
import os import os
from apps.hrm.models import Employee from apps.hrm.models import Employee
from apps.hrm.tasks import update_all_user_facedata_cache
from apps.system.models import User from apps.system.models import User
from django.core.cache import cache
class HRMService: class HRMService:
@ -22,18 +24,25 @@ class HRMService:
return None, '头像解码失败' return None, '头像解码失败'
# 匹配人脸库 # 匹配人脸库
user_faces = Employee.objects.filter(face_data__isnull=False, face_datas = cache.get('face_datas')
user__is_active=True).values('user', 'face_data') face_users = cache.get('face_users')
user_l = [] if face_datas is None:
face_l = [] update_all_user_facedata_cache()
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) results = face_recognition.compare_faces(face_datas, unknown_face_encoding, tolerance=0.5)
for index, value in enumerate(results): for index, value in enumerate(results):
if value: if value:
# 识别成功 # 识别成功
user = User.objects.get(id=user_l[index]) user = User.objects.get(id=face_users[index])
return user, '' return user, ''
return None, '识别失败' 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

@ -1,8 +1,31 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from celery import shared_task 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 @shared_task
def x(): def update_all_user_not_atwork():
print('ok') """
将所有员工设为非在岗状态
"""
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,3 +1,4 @@
from functools import update_wrapper
from django.shortcuts import render from django.shortcuts import render
from django.utils import timezone from django.utils import timezone
from rest_framework.response import Response from rest_framework.response import Response
@ -5,13 +6,13 @@ from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, CreateModelMixin, ListModelMixin from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, CreateModelMixin, ListModelMixin
from apps.hrm.filters import ClockRecordFilterSet from apps.hrm.filters import ClockRecordFilterSet
from apps.hrm.services import HRMService 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 ClockRecord, Employee from apps.hrm.models import ClockRecord, Employee
from apps.hrm.serializers import ClockRecordListSerializer, EmployeeSerializer, FaceClockCreateSerializer, 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
@ -19,20 +20,8 @@ from rest_framework import exceptions
from apps.system.models import User from apps.system.models import User
from apps.system.serializers import UserSimpleSerializer from apps.system.serializers import UserSimpleSerializer
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
logger = logging.getLogger('log')
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):
@ -44,16 +33,23 @@ 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): class ClockRecordViewSet(CreateModelMixin, ListModelMixin, GenericViewSet):

View File

@ -15,7 +15,8 @@ class MbFilterSet(filters.FilterSet):
def filter_tag(self, queryset, name, value): def filter_tag(self, queryset, name, value):
if value == 'expired': if value == 'expired':
queryset = queryset.exclude(expiration_date=None).filter(expiration_date__lte=timezone.now()) queryset = queryset.exclude(expiration_date=None).filter(
expiration_date__lte=timezone.now())
return queryset return queryset

View File

@ -51,10 +51,9 @@ class MaterialBatchSerializer(serializers.ModelSerializer):
class IProductListSerializer(serializers.ModelSerializer): class IProductListSerializer(serializers.ModelSerializer):
material_ = MaterialSimpleSerializer(source='material', read_only=True) material_ = MaterialSimpleSerializer(source='material', read_only=True)
warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True) warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True)
is_mtested = serializers.BooleanField(source='wproduct.is_mtested', read_only=True)
is_mtestok = serializers.BooleanField(source='wproduct.is_mtestok', read_only=True) is_mtestok = serializers.BooleanField(source='wproduct.is_mtestok', read_only=True)
remark_mtest = serializers.CharField(source='wproduct.remark_mtest', read_only=True) remark_mtest = serializers.CharField(source='wproduct.remark_mtest', read_only=True)
class Meta: class Meta:
model = IProduct model = IProduct
fields = '__all__' fields = '__all__'

View File

@ -1,22 +1,36 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from apps.sam.models import Order from apps.sam.models import Order
from django.db.models import F
from datetime import datetime, timedelta
class OrderFilterSet(filters.FilterSet): class OrderFilterSet(filters.FilterSet):
create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte') create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte')
create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte') create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte')
material = filters.NumberFilter(method='filter_material') material = filters.NumberFilter(method='filter_material')
tag = filters.CharFilter(method='filter_tag')
class Meta: class Meta:
model = Order model = Order
fields = ['product', 'contract', 'customer', 'create_time_start', 'create_time_end'] fields = ['product', 'contract', 'customer', 'create_time_start',
'create_time_end', 'tag']
def filter_material(self, queryset, name, value): def filter_material(self, queryset, name, value):
""" """
按物料筛选 按物料筛选
""" """
return queryset.filter(plan_order__subplan_plan__progress_subplan__material__id=value).distinct() return queryset.filter(
plan_order__subplan_plan__progress_subplan__material__id=value).distinct()
def filter_tag(self, queryset, name, value):
if value == 'near_delivery':
day7_after = datetime.now() + timedelta(days=7)
queryset = queryset.filter(delivered_count__lt=F('count'),
delivery_date__lte = datetime.date(day7_after))
elif value == 'out_delivery':
queryset = queryset.filter(delivered_count__lt=F('count'),
delivery_date__gt = datetime.date(datetime.now()))
return queryset
class ContractFilterSet(filters.FilterSet): class ContractFilterSet(filters.FilterSet):
create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte') create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte')

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2022-01-24 05:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sam', '0010_auto_20211208_1408'),
]
operations = [
migrations.AddField(
model_name='order',
name='need_mtest',
field=models.BooleanField(default=False, verbose_name='是否需要军检'),
),
]

View File

@ -66,6 +66,7 @@ class Order(CommonAModel):
planed_count = models.PositiveIntegerField('已排数量', default=0) planed_count = models.PositiveIntegerField('已排数量', default=0)
delivered_count = models.PositiveIntegerField('已交货数量', default=0) delivered_count = models.PositiveIntegerField('已交货数量', default=0)
delivery_date = models.DateField('交货日期') delivery_date = models.DateField('交货日期')
need_mtest = models.BooleanField('是否需要军检', default=False)
class Meta: class Meta:
verbose_name = '订单信息' verbose_name = '订单信息'
verbose_name_plural = verbose_name verbose_name_plural = verbose_name

View File

@ -63,7 +63,7 @@ class OrderSimpleSerializer(serializers.ModelSerializer):
customer_ = CustomerSimpleSerializer(source='customer', read_only=True) customer_ = CustomerSimpleSerializer(source='customer', read_only=True)
class Meta: class Meta:
model = Order model = Order
fields = '__all__' fields = ['id', 'number', 'contract_', 'customer_', 'need_mtest']
class SaleCreateSerializer(serializers.ModelSerializer): class SaleCreateSerializer(serializers.ModelSerializer):
iproducts = serializers.PrimaryKeyRelatedField(queryset=IProduct.objects.all(), many=True) iproducts = serializers.PrimaryKeyRelatedField(queryset=IProduct.objects.all(), many=True)

View File

@ -136,13 +136,6 @@ class SaleViewSet(CreateUpdateCustomMixin, ListModelMixin, RetrieveModelMixin, C
fifo.inout_date = timezone.now() fifo.inout_date = timezone.now()
fifo.create_by = request.user fifo.create_by = request.user
fifo.save() fifo.save()
# 出库条目 暂时不校验是否军检
# spds = SaleProduct.objects.filter(sale=obj)
# for i in spds:
# if i.is_mtested and i.is_mtestok:
# pass
# else:
# raise exceptions.APIException('存在未军检产品')
# 创建出库条目 # 创建出库条目
ips = IProduct.objects.filter(sale_iproduct__sale=obj) ips = IProduct.objects.filter(sale_iproduct__sale=obj)
items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id')) items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id'))

View File

@ -1,11 +1,10 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from .models import User from .models import User
from utils.mixins import DynamicFieldsFilterMixin
class UserFilter(filters.FilterSet): class UserFilter(DynamicFieldsFilterMixin, filters.FilterSet):
name = filters.CharFilter(field_name='name', lookup_expr='contains')
class Meta: class Meta:
model = User model = User
fields = { fields = ['name', 'is_active', 'is_atwork']
'name': ['exact', 'contains'],
'is_active': ['exact'],
}

View File

@ -5,6 +5,7 @@ from rest_framework import serializers
from .models import (Dict, DictType, File, Organization, Permission, Position, from .models import (Dict, DictType, File, Organization, Permission, Position,
Role, User) Role, User)
from utils.mixins import DynamicFieldsSerializerMixin
class IntervalSerializer(serializers.ModelSerializer): class IntervalSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -132,7 +133,7 @@ class UserSimpleSerializer(serializers.ModelSerializer):
# fields = ['id', 'username', 'name', 'is_active', 'dept_name', 'dept'] # fields = ['id', 'username', 'name', 'is_active', 'dept_name', 'dept']
class UserListSerializer(serializers.ModelSerializer): class UserListSerializer(DynamicFieldsSerializerMixin, serializers.ModelSerializer):
""" """
用户列表序列化 用户列表序列化
""" """

View File

@ -48,12 +48,6 @@ class TaskList(APIView):
tasks = list(sorted(name for name in celery_app.tasks if not name.startswith('celery.'))) tasks = list(sorted(name for name in celery_app.tasks if not name.startswith('celery.')))
return Response(tasks) return Response(tasks)
class LogoutView(APIView):
permission_classes = []
def get(self, request, *args, **kwargs): # 可将token加入黑名单
return Response(status=status.HTTP_200_OK)
class PTaskViewSet(OptimizationMixin, ModelViewSet): class PTaskViewSet(OptimizationMixin, ModelViewSet):
perms_map = {'get': '*', 'post': 'ptask_create', perms_map = {'get': '*', 'post': 'ptask_create',
'put': 'ptask_update', 'delete': 'ptask_delete'} 'put': 'ptask_update', 'delete': 'ptask_delete'}

View File

@ -2,6 +2,7 @@ from django_filters import rest_framework as filters
from apps.mtm.models import Material, Step from apps.mtm.models import Material, Step
from apps.wpm.services import WpmServies from apps.wpm.services import WpmServies
from utils.mixins import DynamicFieldsFilterMixin
from .models import Operation, OperationMaterial, OperationRecord, WMaterial, WProduct from .models import Operation, OperationMaterial, OperationRecord, WMaterial, WProduct
@ -28,11 +29,15 @@ class WMaterialFilterSet(filters.FilterSet):
return queryset return queryset
class WProductFilterSet(filters.FilterSet): class WProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
tag = filters.CharFilter(method='filter_tag') tag = filters.CharFilter(method='filter_tag')
production_plan = filters.NumberFilter( production_plan = filters.NumberFilter(
field_name='subproduction_plan__production_plan') field_name='subproduction_plan__production_plan')
def filter_fields(self, queryset, name, value):
return queryset
def filter_omit(self, queryset, name, value):
return queryset
class Meta: class Meta:
model = WProduct model = WProduct
fields = ['step', 'subproduction_plan', 'material', fields = ['step', 'subproduction_plan', 'material',

View File

@ -120,7 +120,6 @@ class WProduct(CommonAModel):
on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_ticket') on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_ticket')
to_order = models.ForeignKey('sam.order', verbose_name='指派的订单', null=True, blank=True, on_delete = models.CASCADE) to_order = models.ForeignKey('sam.order', verbose_name='指派的订单', null=True, blank=True, on_delete = models.CASCADE)
is_mtested = models.BooleanField('是否军检', default=False)
is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True) is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True)
remark_mtest = models.TextField('军检备注', null=True, blank=True) remark_mtest = models.TextField('军检备注', null=True, blank=True)
last_test_result = models.BooleanField('最后一次检验结果', null=True, blank=True) last_test_result = models.BooleanField('最后一次检验结果', null=True, blank=True)
@ -194,7 +193,6 @@ class WproductFlow(CommonAModel):
ticket = models.ForeignKey('wf.ticket', verbose_name='当前工单', ticket = models.ForeignKey('wf.ticket', verbose_name='当前工单',
on_delete=models.SET_NULL, null=True, blank=True) on_delete=models.SET_NULL, null=True, blank=True)
to_order = models.ForeignKey('sam.order', verbose_name='指派的订单', null=True, blank=True, on_delete = models.CASCADE) to_order = models.ForeignKey('sam.order', verbose_name='指派的订单', null=True, blank=True, on_delete = models.CASCADE)
is_mtested = models.BooleanField('是否军检', default=False)
is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True) is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True)
remark_mtest = models.TextField('军检备注', null=True, blank=True) remark_mtest = models.TextField('军检备注', null=True, blank=True)
last_test_result = models.BooleanField('最后一次检验结果', null=True, blank=True) last_test_result = models.BooleanField('最后一次检验结果', null=True, blank=True)

View File

@ -20,6 +20,7 @@ from apps.system.serializers import UserSimpleSerializer
from apps.wpm.models import Operation, OperationEquip, OperationMaterial, OperationWproduct, Pick, WMaterial, WProduct, OperationRecord, OperationRecordItem, WprouctTicket from apps.wpm.models import Operation, OperationEquip, OperationMaterial, OperationWproduct, Pick, WMaterial, WProduct, OperationRecord, OperationRecordItem, WprouctTicket
from django.db import transaction from django.db import transaction
from apps.sam.models import Order from apps.sam.models import Order
from utils.mixins import DynamicFieldsSerializerMixin
class PickHalfSerializer(serializers.Serializer): class PickHalfSerializer(serializers.Serializer):
id = serializers.PrimaryKeyRelatedField(queryset=SubProductionProgress.objects.all(), label='子计划进度ID') id = serializers.PrimaryKeyRelatedField(queryset=SubProductionProgress.objects.all(), label='子计划进度ID')
@ -147,7 +148,7 @@ class WProductBaseSerializer(serializers.ModelSerializer):
model = WProduct model = WProduct
fields = '__all__' fields = '__all__'
class WProductListSerializer(serializers.ModelSerializer): class WProductListSerializer(DynamicFieldsSerializerMixin, serializers.ModelSerializer):
""" """
半成品列表 半成品列表
""" """
@ -157,6 +158,7 @@ class WProductListSerializer(serializers.ModelSerializer):
warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True) warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True)
children = serializers.SerializerMethodField() children = serializers.SerializerMethodField()
to_order_ = OrderSimpleSerializer(source='to_order', read_only=True) to_order_ = OrderSimpleSerializer(source='to_order', read_only=True)
order_ = serializers.SerializerMethodField()
class Meta: class Meta:
model = WProduct model = WProduct
fields = '__all__' fields = '__all__'
@ -166,6 +168,11 @@ class WProductListSerializer(serializers.ModelSerializer):
if wps.exists(): if wps.exists():
return WProductBaseSerializer(instance=wps, many=True).data return WProductBaseSerializer(instance=wps, many=True).data
return [] return []
def get_order_(self, obj):
order = Order.objects.select_related('contract', 'customer').filter(
plan_order__subplan_plan__wproduct_subplan=obj).first()
return OrderSimpleSerializer(instance=order).data
class WProductCardBaseSerializer(serializers.ModelSerializer): class WProductCardBaseSerializer(serializers.ModelSerializer):
""" """
@ -224,6 +231,8 @@ class WProductDetailSerializer(serializers.ModelSerializer):
subproduction_plan_ = SubproductionPlanSimpleSerializer(source='subproduction_plan', read_only=True) subproduction_plan_ = SubproductionPlanSimpleSerializer(source='subproduction_plan', read_only=True)
warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True) warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True)
children = serializers.SerializerMethodField() children = serializers.SerializerMethodField()
to_order_ = OrderSimpleSerializer(source='to_order', read_only=True)
order_ = OrderSimpleSerializer(source='subproduction_plan__production_plan__order', read_only=True)
class Meta: class Meta:
model = WProduct model = WProduct
fields = '__all__' fields = '__all__'
@ -231,7 +240,6 @@ class WProductDetailSerializer(serializers.ModelSerializer):
def get_children(self, obj): def get_children(self, obj):
wps = WProduct.objects.filter(child=obj) wps = WProduct.objects.filter(child=obj)
if wps.exists(): if wps.exists():
print(wps)
return WProductBaseSerializer(instance=wps, many=True).data return WProductBaseSerializer(instance=wps, many=True).data
return [] return []

View File

@ -148,7 +148,8 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
""" """
perms_map = {'*': '*'} perms_map = {'*': '*'}
queryset = WProduct.objects.select_related('step', 'material', queryset = WProduct.objects.select_related('step', 'material',
'subproduction_plan', 'warehouse', 'to_order').prefetch_related('wproduct_child') 'subproduction_plan', 'warehouse', 'subproduction_plan__production_plan__order',
'to_order').prefetch_related('wproduct_child')
serializer_class = WProductListSerializer serializer_class = WProductListSerializer
filterset_class = WProductFilterSet filterset_class = WProductFilterSet
search_fields = ['number'] search_fields = ['number']
@ -398,12 +399,11 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
军检 军检
""" """
obj = self.get_object() obj = self.get_object()
if obj.is_mtested: if obj.is_mtestok is None:
raise exceptions.APIException('已进行军检') raise exceptions.APIException('已进行军检')
if obj.material.type != Material.MA_TYPE_GOOD: if obj.material.type != Material.MA_TYPE_GOOD:
raise exceptions.APIException('军检必须是成品') raise exceptions.APIException('军检必须是成品')
obj.remark_mtest = request.data.get('remark_mtest', None) obj.remark_mtest = request.data.get('remark_mtest', None)
obj.is_mtested = True
is_mtestok = request.data.get('is_mtestok') is_mtestok = request.data.get('is_mtestok')
obj.is_mtestok = is_mtestok obj.is_mtestok = is_mtestok
if is_mtestok: if is_mtestok:

View File

@ -48,6 +48,7 @@ INSTALLED_APPS = [
'simple_history', 'simple_history',
'apps.system', 'apps.system',
'apps.monitor', 'apps.monitor',
'apps.auth1',
'apps.pum', 'apps.pum',
'apps.em', 'apps.em',
'apps.hrm', 'apps.hrm',
@ -194,7 +195,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 +204,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 # 启动时区设置

View File

@ -13,7 +13,7 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path 1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from apps.system.views import FileViewSet, LogoutView from apps.system.views import FileViewSet
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
@ -23,8 +23,6 @@ from drf_yasg import openapi
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
from rest_framework import routers from rest_framework import routers
from rest_framework.documentation import include_docs_urls from rest_framework.documentation import include_docs_urls
from rest_framework_simplejwt.views import (TokenObtainPairView,
TokenRefreshView)
from django.views.generic import TemplateView from django.views.generic import TemplateView
from utils.view import GenSignature, UpdateDevelop from utils.view import GenSignature, UpdateDevelop
@ -53,12 +51,10 @@ urlpatterns = [
path('api/redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), path('api/redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
# api # api
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/token/black/', LogoutView.as_view(), name='token_black'),
path('api/file/', include(router.urls)), path('api/file/', include(router.urls)),
path('api/system/', include('apps.system.urls')), path('api/system/', include('apps.system.urls')),
path('api/monitor/', include('apps.monitor.urls')), path('api/monitor/', include('apps.monitor.urls')),
path('api/auth/', include('apps.auth1.urls')),
path('api/pum/', include('apps.pum.urls')), path('api/pum/', include('apps.pum.urls')),
path('api/em/', include('apps.em.urls')), path('api/em/', include('apps.em.urls')),
path('api/hrm/', include('apps.hrm.urls')), path('api/hrm/', include('apps.hrm.urls')),

96
hb_server/utils/mixins.py Normal file
View File

@ -0,0 +1,96 @@
"""
Mixin to dynamically select only a subset of fields per DRF resource.
"""
# import warnings
from django_filters import rest_framework as filters
# from django.conf import settings
class DynamicFieldsFilterMixin(object):
fields = filters.CharFilter(method='filter_fields')
omit = filters.CharFilter(method='filter_omit')
def filter_fields(self, queryset, name, value):
return queryset
def filter_omit(self, queryset, name, value):
return queryset
@property
def fields(self):
fields = super(DynamicFieldsFilterMixin, self).fields
fields.extend(['fields', 'omit'])
return fields
class DynamicFieldsSerializerMixin(object):
"""
A serializer mixin that takes an additional `fields` argument that controls
which fields should be displayed.
"""
@property
def fields(self):
"""
Filters the fields according to the `fields` query parameter.
A blank `fields` parameter (?fields) will remove all fields. Not
passing `fields` will pass all fields individual fields are comma
separated (?fields=id,name,url,email).
"""
fields = super(DynamicFieldsSerializerMixin, self).fields
if not hasattr(self, '_context'):
# We are being called before a request cycle
return fields
# Only filter if this is the root serializer, or if the parent is the
# root serializer with many=True
is_root = self.root == self
parent_is_list_root = self.parent == self.root and getattr(self.parent, 'many', False)
if not (is_root or parent_is_list_root):
return fields
try:
request = self.context['request']
except KeyError:
# conf = getattr(settings, 'DRF_DYNAMIC_FIELDS', {})
# if not conf.get('SUPPRESS_CONTEXT_WARNING', False) is True:
# warnings.warn('Context does not have access to request. '
# 'See README for more information.')
return fields
# NOTE: drf test framework builds a request object where the query
# parameters are found under the GET attribute.
params = getattr(
request, 'query_params', getattr(request, 'GET', None)
)
# if params is None:
# warnings.warn('Request object does not contain query paramters')
try:
filter_fields = params.get('fields', None).split(',')
except AttributeError:
filter_fields = None
try:
omit_fields = params.get('omit', None).split(',')
except AttributeError:
omit_fields = []
# Drop any fields that are not specified in the `fields` argument.
existing = set(fields.keys())
if filter_fields is None:
# no fields param given, don't filter.
allowed = existing
else:
allowed = set(filter(None, filter_fields))
# omit fields in the `omit` argument.
omitted = set(filter(None, omit_fields))
for field in existing:
if field not in allowed:
fields.pop(field, None)
if field in omitted:
fields.pop(field, None)
return fields