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

This commit is contained in:
shilixia 2021-10-25 09:02:05 +08:00
commit c378f99965
59 changed files with 1928 additions and 527 deletions

View File

@ -3,4 +3,5 @@ ENV = 'production'
# base api
VUE_APP_BASE_API = 'http://47.95.0.242:2222/api'
#VUE_APP_BASE_API = 'http://127.0.0.1:8000/api'

View File

@ -14,9 +14,19 @@ export default {
},
data(){
return{
isRouterAlive:true
isRouterAlive:true,
timer:null
}
},
mounted(){
// this.$store.dispatch("user/getCount", {})
this.timer = window.setInterval(() => {
setTimeout(() => {
this.$store.dispatch("user/getCount", {})
},0)
},30000)
},
methods:{
reload(){
this.isRouterAlive=false;
@ -24,6 +34,9 @@ export default {
this.isRouterAlive=true;
})
},
},
destroyed() {
clearInterval(this.timer)
}
}
</script>
@ -36,4 +49,9 @@ export default {
.el-step__title.is-process{
color: #409EFF;
}
.navbarBadge .el-badge__content.is-fixed{
top: 15px;
right: 18px;
}
</style>

View File

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

View File

@ -163,13 +163,45 @@ export function ticketAccpet(id,data) {
})
}
//撤回工单,允许创建人在指定状态撤回工单至初始状态
export function getTicketRetreat(id,data) {
export function ticketRetreat(id,data) {
return request({
url: `/wf/ticket/${id}/retreat/`,
method: 'post',
data
})
}
//关闭工单,仅允许创建人在初始状态关闭工单
export function ticketAddNode(id,data) {
return request({
url: `/wf/ticket/${id}/add_node/`,
method: 'post',
data
})
}
//加签
export function ticketClose(id,data) {
return request({
url: `/wf/ticket/${id}/close/`,
method: 'post',
data
})
}
//加签
export function ticketAddNodeEnd(id,data) {
return request({
url: `/wf/ticket/${id}/add_node_end/`,
method: 'post',
data
})
}
//工单删除
export function ticketDestory(data) {
return request({
url: `/wf/ticket/destory/`,
method: 'post',
data
})
}
//工单详情
export function getTicketDetail(id) {
return request({
@ -193,3 +225,18 @@ export function getTicketFlowlog(id) {
method: 'get'
})
}
//工单代办数量
export function getCount(data) {
return request({
url: `/wf/ticket/duty_agg/`,
method: 'get',
params:data
})
}
//工单代办数量
export function getCodes() {
return request({
url: `/wf/participant_from_code`,
method: 'get'
})
}

View File

@ -6,6 +6,9 @@
<div class="right-menu">
<template>
<el-badge v-if="count.total_count>0" :value="count.total_count" class="item right-menu-item navbarBadge" @click.native="gotoTicketPage">
<el-icon class="el-icon-s-management" style="font-size: 25px;color: #409EFF;padding-top: 12px;cursor: pointer;"></el-icon>
</el-badge>
<search id="header-search" class="right-menu-item" />
<el-tooltip content="全局组件大小" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
@ -69,9 +72,18 @@ export default {
'sidebar',
'avatar',
'name',
'count',
])
},
methods: {
gotoTicketPage(){
let path = this.$route.path;
if(path==='/workflow/ticket'){
this.$message.success("已在当前页面");
}else{
this.$router.push({name:'ticket',params:{}})
}
},
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},

View File

@ -8,6 +8,15 @@
<tags-view />
</div>
<app-main />
<div class="floatDiv" @click="gotoTicketPage" v-if="count.total_count>0">
<el-badge :value="count.total_count" class="item ">
<el-icon class="el-icon-s-management" style="font-size: 30px;color: #409EFF;padding-top: 12px;"></el-icon>
</el-badge>
<div class="typeWrap">
<div class="detailItem" v-for="item in count.details" :key="item.workflow">{{item.workflow__name}}{{item.count}}</div>
</div>
</div>
</div>
</div>
</template>
@ -15,6 +24,7 @@
<script>
import { Navbar, Sidebar, AppMain,TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapGetters } from 'vuex'
export default {
name: 'Layout',
@ -26,6 +36,9 @@ export default {
},
mixins: [ResizeMixin],
computed: {
...mapGetters([
'count',
]),
sidebar() {
return this.$store.state.app.sidebar
},
@ -47,7 +60,16 @@ export default {
methods: {
handleClickOutside() {
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
},
gotoTicketPage(){
let path = this.$route.path;
if(path==='/workflow/ticket'){
this.$message.success("已在当前页面");
}else{
this.$router.push({name:'ticket',params:{}})
}
},
}
}
</script>
@ -92,4 +114,35 @@ export default {
.mobile .fixed-header {
width: 100%;
}
.floatDiv{
position: fixed;
z-index: 3000;
bottom: 10vh;
right: 5vh;
width: 50px;
height: 50px;
cursor: pointer;
text-align: center;
line-height: 50px;
border-radius: 26px;
border: 2px solid #409EFF;
}
.typeWrap{
display: none;
background: #ffffff;
padding: 10px;
box-shadow: 0 0 7px 2px #d3dce6;
position: absolute;
right: 60px;
bottom: 0;
}
.floatDiv:hover>.typeWrap{
display: block;
}
.detailItem{
height: 30px;
color: #888888;
line-height: 30px;
width: max-content;
}
</style>

View File

@ -86,13 +86,13 @@ export const asyncRoutes = [
component: Layout,
redirect: '/mtm/material/',
name: 'mtm',
meta: { title: '制造管理', icon: 'example', perms: ['procurement_set'] },
meta: { title: '制造管理', icon: 'example', perms: ['mtm_manage'] },
children: [
{
path: 'material',
name: 'material',
component: () => import('@/views/mtm/material'),
meta: { title: '物料清单', icon: 'example', perms: ['vendor_manage'] }
meta: { title: '物料清单', icon: 'example', perms: ['mtm_material'] }
}
,
{
@ -106,7 +106,7 @@ export const asyncRoutes = [
path: 'process',
name: 'process',
component: () => import('@/views/mtm/process'),
meta: { title: '工序管理', icon: 'example', perms: ['vendor_manage'] }
meta: { title: '工序管理', icon: 'example', perms: ['mtm_process'] }
},
{
path: 'step/:id',
@ -127,7 +127,7 @@ export const asyncRoutes = [
path: '/mtm/productprocess/',
name: 'productprocess',
component: () => import('@/views/mtm/productprocess'),
meta: { title: '产品管理', icon: 'example', perms: ['vendor_manage'] }
meta: { title: '产品管理', icon: 'example', perms: ['mtm_productprocess'] }
},
]
}
@ -137,7 +137,7 @@ export const asyncRoutes = [
component: Layout,
redirect: '/pm/plan',
name: 'pm',
meta: { title: '生产管理', icon: 'example', perms: ['equipment_set'] },
meta: { title: '生产管理', icon: 'example', perms: ['pm_manage'] },
children: [
{
path: 'plan',
@ -157,13 +157,13 @@ export const asyncRoutes = [
path: 'resources',
name: 'resources',
component: () => import('@/views/pm/resources'),
meta: { title: '生产资源配置', icon: 'example', perms: ['index_manage'] }
meta: { title: '生产资源配置', icon: 'example', perms: ['pm_resources'] }
},
{
path: 'testitem',
name: 'testitem',
component: () => import('@/views/pm/plan'),
meta: { title: '生产作业管理', icon: 'example', perms: ['index_manage'] }
meta: { title: '生产作业管理', icon: 'example', perms: ['pm_testitem'] }
}
]
}
@ -194,31 +194,31 @@ export const asyncRoutes = [
component: Layout,
redirect: '/em/equipment',
name: 'em',
meta: { title: '设备管理', icon: 'example', perms: ['equipment_set'] },
meta: { title: '设备管理', icon: 'example', perms: ['em_manage'] },
children: [
{
path: 'equipment',
name: 'equipment',
component: () => import('@/views/em/equipment'),
meta: { title: '生产设备', icon: 'example', perms: ['index_manage'] }
meta: { title: '生产设备', icon: 'example', perms: ['em_equipment'] }
},
{
path: 'detection ',
name: 'detection ',
component: () => import('@/views/em/detection'),
meta: { title: '监视和测量设备', icon: 'example', perms: ['index_manage'] }
meta: { title: '监视和测量设备', icon: 'example', perms: ['em_detection'] }
},
{
path: 'record',
name: 'record',
component: () => import('@/views/em/record'),
meta: { title: '校准检定记录', icon: 'example', perms: ['index_manage'] }
meta: { title: '校准检定记录', icon: 'example', perms: ['em_record'] }
},
{
path: 'detection ',
name: 'detection ',
component: () => import('@/views/em/detection'),
meta: { title: '运维记录', icon: 'example', perms: ['index_manage'] }
meta: { title: '运维记录', icon: 'example', perms: ['em_detection'] }
}
]
},
@ -349,32 +349,39 @@ export const asyncRoutes = [
component: Layout,
redirect: '/workflow/index',
name: 'workflow',
meta: { title: '工作流', icon: 'example', perms: ['workflow_set'] },
meta: { title: '工作流', icon: 'example', perms: ['workflow_manage'] },
children: [
{
path: 'index',
name: 'index',
component: () => import('@/views/workflow/index'),
meta: { title: '工作流配置', icon: 'example', perms: ['workflow_manage'] }
meta: { title: '工作流配置', icon: 'example', perms: ['workflow_index'] }
},
{
path: 'ticket',
name: 'ticket',
component: () => import('@/views/workflow/ticket'),
meta: { title: '工单管理', icon: 'example', perms: ['workflow_manage'] },
meta: { title: '工单管理', icon: 'example' ,noCache: true, perms: ['workflow_ticket'] },
},
{
path: 'workFlowTickets',
name: 'workFlowTickets',
component: () => import('@/views/workflow/workFlowTickets'),
meta: { title: '工单管理', icon: 'example' ,noCache: true,},
hidden: true
},
{
path: 'configuration',
name: 'configuration',
component: () => import('@/views/workflow/configuration'),
meta: { title: '人员信息详情', icon: 'example', perms: ['workflow_manage'] },
meta: { title: '人员信息详情', icon: 'example' },
hidden: true
},
{
path: 'ticketHandle',
name: 'ticketHandle',
component: () => import('@/views/workflow/ticketHandle'),
meta: { title: '工单处理', icon: 'example', perms: ['workflow_manage'] },
meta: { title: '工单处理', icon: 'example',noCache: true,},
hidden: true
},
]

View File

@ -5,6 +5,7 @@ const getters = {
avatar: state => state.user.avatar,
name: state => state.user.name,
perms: state => state.user.perms,
count: state => state.user.count,
size: state => state.app.size,
permission_routes: state => state.permission.routes,
visitedViews: state => state.tagsView.visitedViews,

View File

@ -1,4 +1,5 @@
import { login, logout, getInfo } from '@/api/user'
import { getCount } from '@/api/workflow'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
@ -7,6 +8,7 @@ const getDefaultState = () => {
token: getToken(),
name: '',
avatar: '',
count: {},
perms: []
}
}
@ -28,6 +30,9 @@ const mutations = {
},
SET_PERMS: (state, perms) => {
state.perms = perms
},
SET_COUNT: (state, count) => {
state.count = count
}
}
@ -90,15 +95,27 @@ const actions = {
},
// remove token
resetToken({ commit }) {
resetToken({ commit },data) {
return new Promise(resolve => {
removeToken() // must remove token first
commit('RESET_STATE')
removeToken(); // must remove token first
commit('RESET_STATE');
commit('SET_TOKEN', data.access);
setToken(data.access);
resolve()
})
},
setSize({ commit }, size) {
commit('SET_SIZE', size)
},
getCount({ commit }) {
return new Promise((resolve, reject) => {
getCount({}).then((res) => {
commit('SET_COUNT', res.data);
resolve()
}).catch(error => {
reject(error)
})
})
}
}

View File

@ -86,21 +86,21 @@ div:focus {
.el-dialog__header {
padding: 10px 10px 6px;
}
// .el-dialog{
// display: flex;
// flex-direction: column;
// margin:0 !important;
// position:absolute;
// top:50%;
// left:50%;
// transform:translate(-50%,-50%);
// /*height:600px;*/
// max-height:calc(100% - 30px);
// max-width:calc(100% - 30px);
// }
.el-dialog{
display: flex;
flex-direction: column;
margin:0 !important;
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
/*height:600px;*/
max-height:calc(100% - 30px);
max-width:calc(100% - 30px);
}
.el-dialog .el-dialog__body{
// flex:1;
// overflow: auto;
flex:1;
overflow: auto;
padding: 8px 12px;
}

View File

@ -1,5 +1,9 @@
<template>
<div class="dashboard-container">
<el-badge v-if="count.total_count>0" :value="count.total_count" class="item" @click.native="gotoTicketPage">
<el-icon class="el-icon-s-management" style="font-size: 70px;color: #d29898"></el-icon>
</el-badge>
<div></div>
<div class="dashboard-text">name: {{ name }}</div>
<div class="dashboard-text">perms: <span v-for="perm in perms" :key="perm">{{ perm }}</span></div>
</div>
@ -13,8 +17,19 @@ export default {
computed: {
...mapGetters([
'name',
'perms'
'perms',
'count'
])
},
methods:{
gotoTicketPage(){
let path = this.$route.path;
if(path==='/workflow/ticket'){
this.$message.success("已在当前页面");
}else{
this.$router.push({name:'ticket',params:{}})
}
},
}
}
</script>

View File

@ -37,6 +37,7 @@
name="password"
tabindex="2"
auto-complete="on"
id="passwordInput"
@keyup.enter.native="handleLogin"
><svg-icon
slot="prefix"
@ -139,6 +140,7 @@
this.$store
.dispatch("user/login", this.loginForm)
.then(() => {
this.$store.dispatch("user/getCount", {})
this.$router.push({ path: this.redirect || "/" });
this.loading = false;
localStorage.setItem("rem_username", this.loginForm.username);
@ -147,6 +149,7 @@
.catch(() => {
this.loading = false;
});
} else {
console.log("error submit!!");
return false;
@ -200,7 +203,9 @@
margin-left: 2px;
}
}
#passwordInput{
padding-right: 35px;
}
.show-pwd {
height: 39px;
margin-right: 2px;

View File

@ -13,13 +13,15 @@
<script>
import {faceLogin} from "@/api/testModel";
import { setToken, removeToken } from '@/utils/auth'
import { login, getInfo } from '@/api/user'
// import "tracking/build/data/face-min.js";
// import "tracking/build/data/mouth-min.js";
// import "tracking/build/data/tracking-min.js";
// import "tracking/examples/assets/stats.min.js";
// import "tracking/examples/assets/stats.min.js";
export default {
props:['src'],
inject:['reload'],
data () {
return {
videoWidth: 500,
@ -69,9 +71,25 @@
let image = new Image();
image = canvas.toDataURL('image/png');
document.getElementById('res').innerHTML = '<img src="'+image+'">';
let imgData = {base64:image};
let img = image.split(',')[1];
let imgData = {base64:img};
faceLogin(imgData).then((res) => {
if (res.code >= 200) {
debugger;
console.log(res);
let data = res.data;
this.$confirm("是否切换登陆人?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
await this.$store.dispatch("user/resetToken",data);
await this.$store.dispatch("user/getInfo",data.access);
window.location.reload();
})
.catch((err) => {
console.error(err);
});
this.$message.success("成功");
}
});

View File

@ -102,7 +102,7 @@
<p>创建时间 {{watchedCreateTime}}</p>
</el-col>
</el-row>
<svg height=800 id="mySvg" style="width: max-content!important">
<svg height=800 id="mySvg" style="width: 100%;">
</svg>
</div>
</div>
@ -278,7 +278,7 @@ export default {
});
},
handleTicket(scope){
this.$router.push({name:"ticket",params:{workflow:scope.row.id}})
this.$router.push({name:"workFlowTickets",params:{workflow:scope.row.id}})
},
async confirm(form) {
debugger;
@ -321,12 +321,12 @@ export default {
that.limitedWatch = true;
that.$nextTick(()=>{
var g = new dagreD3.graphlib.Graph().setGraph({
align: 'DL',
rankdir: 'DL',
nodesep: 100,
edgesep: 100,
ranksep: 50,
marginx: 0,
marginy: 50,
edgesep: 10,//两条线之间的距离
ranksep: 50,//节点之间的距离
marginx: 160,
marginy: 20,
});
//获取state得到节点
getWfStateList(workFlow).then((response) => {
@ -343,8 +343,6 @@ export default {
//节点样式
style: "fill:#fff;stroke:#000",
labelStyle: "fill:#000;",
// width: 83,
// height: 40,
rx :5,//矩形节点圆角度
ry :5
});
@ -356,9 +354,8 @@ export default {
getWfTransitionList(workFlow).then((res)=>{
if(res.data){
let transitionList = res.data;
transitionList.forEach((transitions)=>{
let transition0 = transitions;
if (transition0.condition_expression.length>3){
transitionList.forEach((transition0)=>{
if (transition0.condition_expression.length>0){
debugger;
g.setNode(transition0.source_state_.id+100000, {label: "条件表达式",style: "stroke: #000;fill: #afa", shape: "diamond"});
g.setEdge(transition0.source_state_.id, transition0.source_state_.id+100000, {
@ -366,7 +363,7 @@ export default {
label: transition0.name,
style: "fill:#ffffff;stroke:#c0c1c3;stroke-width:1.5px"
});
let condition_expression = JSON.parse(transition0.condition_expression);
let condition_expression = transition0.condition_expression;
condition_expression.forEach(condition_expression0=>{
g.setEdge(transition0.source_state_.id+100000, condition_expression0.target_state, {
label: condition_expression0.label,
@ -386,7 +383,6 @@ export default {
g.nodes().forEach(function (v) {
console.log("Node " + v + ": " + JSON.stringify(g.node(v)));
});
// 创建渲染器
let render = new dagreD3.render();
// 选择 svg 并添加一个g元素作为绘图容器.
@ -399,6 +395,7 @@ export default {
}
});
})
},
closeMark(){
@ -415,33 +412,25 @@ export default {
this.hasJsonFlag = true
},
onError(value) {
// console.log("json错误了value:", value);
this.hasJsonFlag = false
},
onJsonChange1 (value) {
// console.log('更改value:', value);
// 实时保存
this.onJsonSave1(value)
},
onJsonSave1 (value) {
// console.log('保存value:', value);
this.display_form_str = value
this.hasJsonFlag1 = true
},
onError1(value) {
// console.log("json错误了value:", value);
this.hasJsonFlag1 = false
},
// 检查json
checkJson(){
if (this.hasJsonFlag == false){
// console.log("json验证失败")
// this.$message.error("json验证失败")
alert("限制表达式json验证失败")
return false
} else {
// console.log("json验证成功")
// this.$message.success("json验证成功")
alert("限制表达式json验证成功")
return true
}
@ -449,13 +438,9 @@ export default {
// 检查json
checkJson2(){
if (this.hasJsonFlag1 == false){
// console.log("json验证失败")
// this.$message.error("json验证失败")
alert("展现表单字段json验证失败")
return false
} else {
// console.log("json验证成功")
// this.$message.success("json验证成功")
alert("展现表单字段json1验证成功")
return true
}
@ -464,6 +449,19 @@ export default {
};
</script>
<style scoped>
::-webkit-scrollbar {
width: 15px;
}
::-webkit-scrollbar-track{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.2);
background-color: #fefefe;
border-radius: 7px;
}
::-webkit-scrollbar-thumb{
border-radius: 7px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.5);
background-color: #f5f5f5;
}
.svgMark{
width: 100%;
height: 100%;
@ -483,6 +481,8 @@ export default {
margin: 10vh auto 0;
text-align: center;
border-radius: 2px;
max-height: 75vh;
overflow-y: scroll;
}
.svgItem{
padding: 20px 40px 0 ;

View File

@ -1,51 +1,49 @@
<template>
<div class="app-container">
<el-card>
<div style="margin-top: 10px">
<el-button type="primary" icon="el-icon-plus" @click="handleCreate"
>新增</el-button
>
</div>
<div style="margin-top: 10px">
<el-button type="primary" icon="el-icon-plus" @click="handleCreate">新增
</el-button>
</div>
</el-card>
<el-card style="margin-top: 10px">
<el-table
:data="wfstateList"
style="width: 100%"
>
<el-table-column type="index" width="50" />
<el-table-column width="180" label="名称">
<el-table-column type="index" width="50"/>
<el-table-column width="180" label="名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column width="180" label="是否隐藏">
<template slot-scope="scope">
<el-table-column width="180" label="是否隐藏">
<template slot-scope="scope">
{{ !!(scope.row.is_hidde)?'':'' }}
</template>
</el-table-column>
<el-table-column width="180" label="顺序ID">
<el-table-column width="180" label="顺序ID">
<template slot-scope="scope">{{ scope.row.sort }}</template>
</el-table-column>
<el-table-column width="180" label="类型">
<el-table-column width="180" label="类型">
<template slot-scope="scope">
<el-tag effect="plain" v-if="scope.row.type==0">
普通类型
<el-tag effect="plain" v-if="scope.row.type==0">
普通类型
</el-tag>
<el-tag effect="plain" v-if="scope.row.type==1">
初始状态
<el-tag effect="plain" v-if="scope.row.type==1">
初始状态
</el-tag>
<el-tag effect="plain" v-if="scope.row.type==2">
结束状态
结束状态
</el-tag>
</template>
</el-table-column>
<el-table-column width="180" label="参与人类型">
<el-table-column width="180" label="参与人类型">
<template slot-scope="scope">{{ options_[scope.row.participant_type] }}</template>
</el-table-column>
<el-table-column width="180" label="创建时间">
<el-table-column width="180" label="创建时间">
<template slot-scope="scope">{{ scope.row.create_time }}</template>
</el-table-column>
<el-table-column
align="center"
@ -53,21 +51,19 @@
width="220px"
>
<template slot-scope="scope">
<!-- v-if="checkPermission(['workflow_update'])" -->
<el-link
v-if="checkPermission(['wfstate_update'])"
@click="handleEdit(scope)"
>编辑</el-link
>
>编辑
</el-link>
<el-link
v-if="checkPermission(['wfstate_delete'])"
type="danger"
@click="handleDelete(scope)"
>删除</el-link
>
>删除
</el-link>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog
:visible.sync="dialogVisible"
@ -81,7 +77,7 @@
:rules="rule1"
>
<el-form-item label="名称" prop="name">
<el-input v-model="wfstate.name" placeholder="名称" />
<el-input v-model="wfstate.name" placeholder="名称"/>
</el-form-item>
<el-form-item
label="是否隐藏"
@ -90,61 +86,75 @@
>
<el-switch v-model="wfstate.is_hidden"></el-switch>
</el-form-item>
<el-form-item label="状态顺序" prop="sort">
<el-input v-model="wfstate.sort" type="number" placeholder="状态顺序" />
<el-form-item label="状态顺序" prop="sort">
<el-input v-model="wfstate.sort" type="number" placeholder="状态顺序"/>
</el-form-item>
<el-form-item label="状态类型" prop="type">
<el-select style="width: 100%" v-model="wfstate.type" placeholder="请选择">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
<el-form-item label="状态类型" prop="type">
<el-select style="width: 100%" v-model="wfstate.type" 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-form-item
<el-form-item
label="允许撤回"
prop="enable_retreat"
label-width="120px"
>
<el-switch v-model="wfstate.enable_retreat"></el-switch>
</el-form-item>
<el-form-item label="参与者类型" prop="participant_type">
<el-select style="width: 100%" v-model="wfstate.participant_type" placeholder="请选择" @change="typeChange">
<el-option
v-for="item in typeoptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
<el-form-item label="参与者类型" prop="participant_type">
<el-select style="width: 100%" v-model="wfstate.participant_type" placeholder="请选择" @change="typeChange">
<el-option
v-for="item in typeoptions"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="参与者" prop="participant" v-if="wfstate.participant_type==1">
<el-select style="width: 100%" v-model="participant" placeholder="请选择参与者">
<el-select style="width: 100%" v-model="participant" placeholder="请选择参与者">
<el-option v-for="item in staffs" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="参与者" prop="participant" v-if="wfstate.participant_type==2">
<el-select style="width: 100%" v-model="participants" multiple placeholder="请选择参与者">
<el-select style="width: 100%" v-model="participants" multiple placeholder="请选择参与者">
<el-option v-for="item in staffs" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="分配方式" prop="participant" v-if="wfstate.participant_type==2">
<el-select style="width: 100%" v-model="wfstate.distribute_type" placeholder="请选择分配方式">
<el-select style="width: 100%" v-model="wfstate.distribute_type" placeholder="请选择分配方式">
<el-option label="主动接单" value="1"></el-option>
<el-option label="直接处理" value="2"></el-option>
<el-option label="随机分配" value="3"></el-option>
<el-option label="全部处理" value="4"></el-option>
</el-select>
</el-form-item>
<el-form-item label="部门" prop="participant" v-if="wfstate.participant_type==3">
<el-select style="width: 100%" v-model="participant" placeholder="请选择部门">
<el-option v-for="item in departments" :key="item.id" :label="item.name" :value="item.id">
<el-form-item label="角色" prop="participant" v-if="wfstate.participant_type==4">
<el-select style="width: 100%" v-model="participants" placeholder="请选择角色">
<el-option v-for="item in roles" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="过滤策略" prop="participant" v-if="wfstate.participant_type==4">
<el-select style="width: 100%" v-model="wfstate.filter_policy" placeholder="请选择过滤策略">
<el-option label="" value="0"></el-option>
<el-option label="和工单同属以及上级部门" value="1"></el-option>
<el-option label="和创建人同属以及上级部门" value="2"></el-option>
<el-option label="和上步处理人同属以及上级部门" value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item label="代码选择" prop="participant" v-if="wfstate.participant_type==9">
<el-select style="width: 100%" v-model="participants" placeholder="请选择代码">
<el-option v-for="item in codes" :key="item.func" :label="item.name" :value="item.func">
</el-option>
</el-select>
</el-form-item>
@ -152,21 +162,23 @@
<el-button @click="addWordStatusChange">添加修改</el-button>
<el-row v-for="(item,$index) in statusChange" :key="item+$index" style="margin-top: 10px">
<el-col :span="11">
<el-select style="width: 100%" v-model="item.name" placeholder="请选择字段">
<el-option v-for="item in customfieldList" :key="item.id" :label="item.field_name" :value="item.field_key">
<el-select style="width: 100%" v-model="item.name" placeholder="请选择字段">
<el-option v-for="item in customfieldList" :key="item.id" :label="item.field_name"
:value="item.field_key">
</el-option>
</el-select>
</el-col>
<el-col :span="1" style="height: 1px;"></el-col>
<el-col :span="8">
<el-select style="width: 100%" v-model="item.value" placeholder="请选择状态">
<el-select style="width: 100%" v-model="item.value" placeholder="请选择状态">
<el-option label="只读" value="1"></el-option>
<el-option label="必填" value="2"></el-option>
<el-option label="可选" value="3"></el-option>
</el-select>
</el-col>
<el-col :span="2" style="text-align: center" v-if="$index!==0">
<i class="el-icon-remove-outline" @click.prevent="removeStatusChange($index)" style="color: red;font-size: 16px;"></i>
<i class="el-icon-remove-outline" @click.prevent="removeStatusChange($index)"
style="color: red;font-size: 16px;"></i>
</el-col>
</el-row>
</el-form-item>
@ -179,53 +191,57 @@
</div>
</template>
<script>
import { getWfStateList, createWfState,updateWfState,deleteWfState,getWfCustomfieldList } from "@/api/workflow";
import { getOrganizationList,getUserList } from "@/api/user";
import checkPermission from "@/utils/permission";
const defaultwfstate = {
name: "",
};
export default {
components: { },
name: "State",
props: ["ID"],
data() {
return {
wfstate: {
name:'',
is_hidden:'',
sort:'',
type:'',
enable_retreat:'',
participant_type:'',
participant:'',
distribute_type:'',//分发类型
state_fields:{}//字段状态是否可写
},
statusChange:[],
participant:'',
participants:[],
is_hidden:false,
enable_retreat:false,
staffs:[],
departments:[],
customfieldList:[],
/*wfstateList: {
count:0
},*/
wfstateList:[],
options_:{
"0":'无处理',
"1":'个人',
"2":'多人',
"3":'部门',
"4":'角色',
"5":'变量',
"6":'普通类型',
"7":'工单字段',
"8":'父工单字段',
},
options: [{
import {getWfStateList, createWfState, updateWfState,
deleteWfState, getWfCustomfieldList , getCodes } from "@/api/workflow";
import { getUserList} from "@/api/user";
import { getRoleAll } from "@/api/role";
import checkPermission from "@/utils/permission";
const defaultwfstate = {
name: "",
};
export default {
components: {},
name: "State",
props: ["ID"],
data() {
return {
wfstate: {
name: '',
is_hidden: '',
sort: '',
type: '',
enable_retreat: '',
participant_type: '',
filter_policy: '',
distribute_type: '',//分发类型
state_fields: {}//字段状态是否可写
},
statusChange: [],
participant: '',
participants: [],
is_hidden: false,
enable_retreat: false,
staffs: [],
roles: [],
codes: [],
customfieldList: [],
/*wfstateList: {
count:0
},*/
wfstateList: [],
options_: {
"0": '无处理',
"1": '个人',
"2": '多人',
// "3": '部门',
"4": '角色',
// "5": '变量',
"6": '脚本',
"7": '工单的字段',
// "8": '父工单的字段',
"9": '代码获取',
},
options: [{
value: 0,
label: '普通类型'
}, {
@ -235,7 +251,7 @@ export default {
value: 2,
label: '结束状态'
}],
typeoptions: [{
typeoptions: [{
value: 0,
label: '无处理'
}, {
@ -245,182 +261,193 @@ export default {
value: 2,
label: '多人'
}
, {
value: 3,
label: '部门'
}
, {
value: 4,
label: '角色'
}
, {
value: 5,
label: '变量'
value: 6,
label: '脚本'
}
, {
value: 7,
label: '工单字段'
label: '工单字段'
}
, {
value: 8,
label: '父工单字段'
value: 9,
label: '代码获取'
}],
display_form_str:[],
limit_expression:[],
dialogVisible: false,
dialogType: "new",
rule1: {
name: [{ required: true, message: "请输入", trigger: "blur" }],
sort: [{ required: true, message: "请输入", trigger: "blur" }],
type:[{ required: true, message: "选择", trigger: "blur" }],
display_form_str: [],
limit_expression: [],
dialogVisible: false,
dialogType: "new",
rule1: {
name: [{required: true, message: "请输入", trigger: "blur"}],
sort: [{required: true, message: "请输入", trigger: "blur"}],
type: [{required: true, message: "选择", trigger: "blur"}],
},
};
},
created() {
this.getList();
this.getUser();
this.getDepartment();
},
mounted(){
this.getCodes();
},
methods: {
checkPermission(value){
debugger;
console.log(checkPermission(value))
checkPermission(value);
},
};
},
created() {
this.getList();
this.getUser();
this.getDepartment();
},
methods: {
checkPermission,
getList() {
getWfStateList(this.ID).then((response) => {
if (response.data) {
this.wfstateList = response.data;
}
});
getWfCustomfieldList(this.ID).then((response) => {
if (response.data) {
this.customfieldList = response.data;
}
getList() {
});
},
getUser(){
getUserList({}).then(res=>{
if(res.data){
this.staffs = res.data.results;
}
})
},
getDepartment(){
getOrganizationList().then(res=>{
if(res.data){
this.departments = res.data;
}
})
},
//参与者类型变化
typeChange(){
this.participant = '';
this.participants = [];
},
//打开新建
handleCreate() {
this.wfstate = Object.assign({}, defaultwfstate);
this.dialogType = "new";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
//打开编辑
handleEdit(scope) {
this.wfstate = Object.assign({}, scope.row); // copy obj
this.participants = this.wfstate.participant;
this.participant = this.wfstate.participant;
/////
debugger;
console.log(this.wfstate.state_fields);
let arr = [];
for (let pro in this.wfstate.state_fields) {
let obj = new Object();
obj.name = pro;
obj.value = this.wfstate.state_fields[pro];
arr.push(obj)
}
this.statusChange = arr;
this.wfstate.distribute_type = this.wfstate.distribute_type.toString();
this.dialogType = "edit";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
addWordStatusChange(){
this.statusChange.push({name:'',value:''})
},
removeStatusChange(index){
this.statusChange.splice(index, 1)
},
//编辑新建
async confirm(form) {
this.$refs[form].validate((valid) => {
if (valid) {
const isEdit = this.dialogType === "edit";
// this.wfstate.participant = 1;
let state_fields = {};
if(this.statusChange.length>0){
for(let i=0;i<this.statusChange.length;i++){
state_fields[this.statusChange[i].name] = this.statusChange[i].value;
}
getWfStateList(this.ID).then((response) => {
if (response.data) {
this.wfstateList = response.data;
}
debugger;
console.log(state_fields);
this.wfstate.state_fields = state_fields;
this.wfstate.participant = this.participant!==''?this.participant:this.participants;
if (isEdit) {
updateWfState(this.wfstate.id, this.wfstate).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
this.getList();
}
});
} else {
this.wfstate.workflow=this.ID;
createWfState(this.wfstate).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.getList();
this.$message.success("成功");
}
});
}
} else {
return false;
}
});
},
handleDelete(scope) {
this.$confirm("确认删除?", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error",
})
.then(async () => {
await deleteWfState(scope.row.id);
this.getList();
this.$message.success("成功");
})
.catch((err) => {
console.error(err);
});
getWfCustomfieldList(this.ID).then((response) => {
if (response.data) {
this.customfieldList = response.data;
}
});
},
getUser() {
getUserList({}).then(res => {
if (res.data) {
this.staffs = res.data.results;
}
})
},
getDepartment() {
getRoleAll().then(res => {
if (res.data) {
this.roles = res.data;
}
})
},
getCodes(){
getCodes().then(res=>{
// debugger;
// console.log(res);
this.codes = res.data;
})
},
//参与者类型变化
typeChange() {
this.participant = '';
this.participants = [];
},
//打开新建
handleCreate() {
this.wfstate = Object.assign({}, defaultwfstate);
this.dialogType = "new";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
//打开编辑
handleEdit(scope) {
this.wfstate = Object.assign({}, scope.row); // copy obj
this.participants = this.wfstate.participant;
this.participant = this.wfstate.participant;
/////
debugger;
console.log(this.wfstate.state_fields);
let arr = [];
for (let pro in this.wfstate.state_fields) {
let obj = new Object();
obj.name = pro;
obj.value = this.wfstate.state_fields[pro];
arr.push(obj)
}
this.statusChange = arr;
this.wfstate.distribute_type = this.wfstate.distribute_type.toString();
this.wfstate.filter_policy = this.wfstate.filter_policy.toString();
this.dialogType = "edit";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
addWordStatusChange() {
this.statusChange.push({name: '', value: ''})
},
removeStatusChange(index) {
this.statusChange.splice(index, 1)
},
//编辑新建
async confirm(form) {
this.$refs[form].validate((valid) => {
if (valid) {
const isEdit = this.dialogType === "edit";
// this.wfstate.participant = 1;
let state_fields = {};
if (this.statusChange.length > 0) {
for (let i = 0; i < this.statusChange.length; i++) {
state_fields[this.statusChange[i].name] = this.statusChange[i].value;
}
}
debugger;
console.log(state_fields);
this.wfstate.state_fields = state_fields;
this.wfstate.participant = this.wfstate.participant_type === 1 ? this.participant : this.participants;
if (isEdit) {
updateWfState(this.wfstate.id, this.wfstate).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
this.getList();
}
});
} else {
this.wfstate.workflow = this.ID;
createWfState(this.wfstate).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.getList();
this.$message.success("成功");
}
});
}
} else {
return false;
}
});
},
handleDelete(scope) {
this.$confirm("确认删除?", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error",
})
.then(async () => {
await deleteWfState(scope.row.id);
this.getList();
this.$message.success("成功");
})
.catch((err) => {
console.error(err);
});
},
},
},
};
};
</script>

View File

@ -29,12 +29,15 @@
</el-card>
<el-tabs v-model="pageForm.category" type="border-card" @tab-click="handleClick">
<el-tab-pane label="待处理" name="duty">
<el-table :data="tickets"
border fit stripe
style="width: 100%"
height="100"
highlight-current-row
v-el-height-adaptive-table="{bottomOffset: 60}">
<el-table
v-loading="listLoading"
:data="tickets"
border fit stripe
style="width: 100%"
height="100"
highlight-current-row
v-el-height-adaptive-table="{bottomOffset: 100}"
>
<el-table-column label="工单标题" min-width="100" prop="title">
</el-table-column>
<el-table-column label="当前状态" min-width="100">
@ -61,10 +64,13 @@
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-link v-if="scope.row.state_.distribute_type==1&&scope.row.participant_type==2" type="danger" @click="handleGetTicket(scope)">接单</el-link>
<el-link v-else-if="(scope.row.act_state==1||scope.row.act_state==3)&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
<!--如果state_.retreat为可退回则显示撤回按钮-->
<el-link v-if="scope.row.state_.enable_retreat&&userId==scope.row.create_by" type="danger" @click="handleRetreat(scope)">撤回</el-link>
<el-link v-else-if="(scope.row.act_state==1||scope.row.act_state==3)&&scope.row.participant_type!==2&&scope.row.state_.type===0" type="primary" @click="handleDetail(scope)">处理</el-link>
<el-link v-if="scope.row.state_.type==1&&userId==1" type="danger" @click="handleClose(scope,'2')">关闭</el-link>
<!--如果state_.retreat为可退回则显示撤回按钮 state_.type==1处于草稿状态 -->
<el-link v-if="scope.row.state_.enable_retreat&&userId==scope.row.create_by&&scope.row.state_.type!==1" type="danger" @click="handleClose(scope,'1')">撤回</el-link>
<el-link type="success" @click="handleDelete(scope)">删除</el-link>
<el-link type="success" @click="handlePicture(scope)">查看流程图</el-link>
<el-link type="success" @click="handleLogs(scope)">工单日志</el-link>
</template>
</el-table-column>
</el-table>
@ -107,7 +113,12 @@
</el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-link type="primary" v-if="scope.row.act_state==4" @click="handlePicture(scope)">查看流程图</el-link>
<!-- <el-link v-if="scope.row.state_.distribute_type==1&&scope.row.participant_type==2" type="danger" @click="handleGetTicket(scope)">接单</el-link>
<el-link v-else-if="(scope.row.act_state==1||scope.row.act_state==3)&&scope.row.participant_type!==2&&scope.row.state_.type===0" type="primary" @click="handleDetail(scope)">处理</el-link>
-->
<el-link v-if="scope.row.state_.type==1&&userId==1" type="danger" @click="handleClose(scope,'2')">关闭</el-link>
<el-link type="success" @click="handlePicture(scope)">查看流程图</el-link>
<el-link type="success" @click="handleLogs(scope)">工单日志</el-link>
</template>
</el-table-column>
</el-table>
@ -150,10 +161,11 @@
</el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope" v-if="scope.row.state_">
<el-link v-if="scope.row.state_.distribute_type==1&&scope.row.participant_type==2" type="danger" @click="handleGetTicket(scope)">接单</el-link>
<el-link v-else-if="(scope.row.act_state==1||scope.row.act_state==3)&&scope.row.participant_type!==2" type="primary" @click="handleDetail(scope)">处理</el-link>
<el-link v-if="scope.row.state_.enable_retreat" type="danger" @click="handleRetreat(scope)">撤回</el-link>
<el-link v-if="scope.row.state_.enable_retreat&&scope.row.state_.type!==1" type="danger" @click="handleClose(scope,'1')">撤回</el-link>
<el-link v-if="scope.row.state_.type==1" type="danger" @click="handleClose(scope,'2')">关闭</el-link>
<el-link type="success" @click="handleDelete(scope)">删除</el-link>
<el-link type="success" @click="handlePicture(scope)">查看流程图</el-link>
<el-link type="success" @click="handleLogs(scope)">工单日志</el-link>
</template>
</el-table-column>
</el-table>
@ -197,7 +209,7 @@
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-link type="danger" @click="handlePicture(scope)">查看流程图</el-link>
<el-link v-if="scope.row.act_state==1" type="danger" @click="handleDetail(scope)">处理</el-link>
<el-link type="success" @click="handleLogs(scope)">工单日志</el-link>
</template>
</el-table-column>
</el-table>
@ -224,25 +236,27 @@
<el-step :title="item.name" v-for="item in flowSteps " :key="item.id">
</el-step>
</el-steps>
<svg height=800 id="mySvg" style="width:100%!important;">
</svg>
<div style="width: 90%;margin: auto;">
<svg height=800 id="mySvg" style="width:100%!important;">
</svg>
</div>
</div>
</div>
<el-dialog :visible.sync="limitedRetreat" title="工单撤回">
<el-dialog :visible.sync="limitedRetreat" :title="handleTitle">
<el-row>
<el-form ref="Form" :model="handleForm" label-width="100px" label-position="right" :rules="handleRule">
<el-col :span="2" style="height: 1px;"></el-col>
<el-col :span="20" style="margin:6vh 0">
<el-form-item label="撤回原因">
<el-form ref="Form" :model="handleForm" label-width="100px" label-position="right">
<el-col :span="1" style="height: 1px;"></el-col>
<el-col :span="22" style="margin:3vh 0">
<el-form-item :label="handleLabel">
<el-input type="textarea" :rows="3" v-model="handleForm.suggestion" placeholder="撤回原因"/>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div style="text-align: center">
<el-button class="filter-item" type="" @click="retreatCancel">取消</el-button>
<el-button class="filter-item" type="primary" @click="retreatSubmit">确定</el-button>
<el-button class="filter-item" type="" @click="handleCancel">取消</el-button>
<el-button class="filter-item" type="primary" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="limitedAdd" title="新增工单">
@ -337,13 +351,49 @@
<el-button type="primary" @click="confirm('Form')">确认</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="limitedFlowLogs" title="工单日志">
<el-table :data="floeLogs" fit stripe
style="width: 100%;border-top:1px solid #EBEEF5;"
height="100"
highlight-current-row
v-el-height-adaptive-table="{bottomOffset: 60}">
<el-table-column label="工单标题" min-width="100">
<template slot-scope="scope" v-if="scope.row.ticket_data">
<span>{{scope.row.ticket_data.title}}</span>
</template>
</el-table-column>
<!-- <el-table-column label="当前状态" min-width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.act_state==0" label="草稿中" value="scope.row.act_state">草稿中</el-tag>
<el-tag v-else-if="scope.row.act_state==1" label="进行中" value="scope.row.act_state">进行中</el-tag>
<el-tag v-else-if="scope.row.act_state==2" label="被退回" value="scope.row.act_state">被退回</el-tag>
<el-tag v-else-if="scope.row.act_state==3" label="被撤回" value="scope.row.act_state">被撤回</el-tag>
<el-tag v-else-if="scope.row.act_state==4" label="已完成" value="scope.row.act_state">已完成</el-tag>
<el-tag v-else-if="scope.row.act_state==5" label="已关闭" value="scope.row.act_state">已关闭</el-tag>
</template>
</el-table-column>-->
<el-table-column label="进行状态" min-width="100">
<template slot-scope="scope" v-if="scope.row.state_">
<span v-if="scope.row.state_.type==0">{{scope.row.state_.name}}</span>
<span v-else>{{scope.row.state_.name}}</span>
</template>
</el-table-column>
<el-table-column label="操作人" min-width="100">
<template slot-scope="scope" v-if="scope.row.participant_">{{ scope.row.participant_.name }}</template>
</el-table-column>
<el-table-column label="操作意见" min-width="100" prop="suggestion">
</el-table-column>
<el-table-column label="更新时间" min-width="100" prop="update_time">
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
import { upUrl, upHeaders } from "@/api/file";
import {getWorkflowList,getWfCustomfieldList,createTicket,getWfStateList,getTickets,ticketAccpet,getWfTransitionList,
ticketHandle,getWfFlowSteps,getTicketDetail,getTicketTransitions,getTicketFlowlog,getTicketRetreat} from "@/api/workflow";
ticketHandle,getWfFlowSteps,getTicketDetail,getTicketTransitions,getTicketFlowlog,ticketRetreat,ticketClose,ticketDestory} from "@/api/workflow";
import Pagination from "@/components/Pagination";
import dagreD3 from 'dagre-d3'
import * as d3 from 'd3'
@ -389,6 +439,8 @@
limitedStep:false,
limitedAdd:false,
limitedRetreat:false,
limitedFlowLogs:false,
floeLogs:[],
nodes: [],
tooltip:null,
edges: [],
@ -397,11 +449,12 @@
operationBtn:[],
customfields:[],
transitions:[],
handleLabel:'撤回原因',
handleTitle:'工单撤回',
rule1:{
title: [{ required: true, message: "请输入", trigger: "blur" }],
workflow: [{ required: true, message: "选择", trigger: "blur" }]
}
,
}
},
@ -416,15 +469,6 @@
this.getStates();
this.getWorkFlow();
},
activated(){
this.workflow = this.$route.params.workflow;
if(this.workflow){//有传参
this.pageForm.workflow = parseInt(this.workflow);
}
this.getList();
this.getStates();
this.getWorkFlow();
},
methods:{
getList(){
this.listLoading = true;
@ -439,16 +483,12 @@
getTicketFlowlogs(id){
getTicketFlowlog(id).then(res=>{
if(res.data){
debugger;
console.log(res)
this.logs = res.data;
}
})
},
handleClick(tab, event) {
console.log(tab, event);
console.log(this.pageForm.category);
debugger;
this.getList();
},
//获取工作流所有状态
@ -563,30 +603,60 @@
}
});
},
retreatCancel(){
handleCancel(){
this.limitedRetreat = false;
},
retreatSubmit(){
this.$confirm("确认撤回吗?", "温馨提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
getTicketRetreat(this.retreatId,this.handleForm).then(res=>{
this.limitedRetreat = false;
this.getList();
this.$message.success("成功");
})
handleSubmit(){
debugger;
if(this.handleTitle === '撤回工单'){
this.$confirm("确认撤回工单吗?", "温馨提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
})
.catch((err) => {
console.error(err);
});
.then(async () => {
ticketRetreat(this.retreatId,this.handleForm).then(res=>{
this.limitedRetreat = false;
this.getList();
this.$store.dispatch("user/getCount", {})
this.$message.success("成功");
})
})
.catch((err) => {
console.error(err);
});
}else{
this.$confirm("确认关闭工单吗?", "温馨提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
ticketClose(this.retreatId,this.handleForm).then(res=>{
this.limitedRetreat = false;
this.getList();
this.$store.dispatch("user/getCount", {})
this.$message.success("成功");
})
})
.catch((err) => {
console.error(err);
});
}
},
handleRetreat(scope){
handleClose(scope,index){
if(index==='1'){
this.handleTitle = '撤回工单';
this.handleLabel='撤回原因';
}else{
this.handleTitle = '关闭工单';
this.handleLabel='关闭原因';
}
this.limitedRetreat = true;
this.retreatId = scope.row.id;
},
handleLogs(){},
handlePicture(scope){
let that = this;
that.dialogVisible = true;
@ -594,8 +664,6 @@
that.watchedCreateTime = scope.row.create_time;
that.pageForm.workflow = scope.row.workflow;
let ticketId = scope.row.id;
// that.getStates();
// that.getTicketFlowlogs(ticketId);
getWfFlowSteps( ticketId).then((res)=>{
if(res.data){
debugger;
@ -616,12 +684,12 @@
that.actives =that.flowSteps.length;
}
var g = new dagreD3.graphlib.Graph().setGraph({
align: 'DL',
rankdir: 'DL',
nodesep: 100,
edgesep: 100,
ranksep: 50,
marginx: 300,
marginy: 50,
edgesep: 10,//两条线之间的距离
ranksep: 50,//节点之间的距离
marginx: 60,
marginy: 20,
});
//获取state得到节点
getWfStateList(that.pageForm.workflow).then((response) => {
@ -629,9 +697,6 @@
let nodes = response.data;
// 添加节点
nodes.forEach((item) => {
/*debugger;
console.log(item.sort)
console.log(that.state)*/
if(item.sort==that.sort){
g.setNode(item.id, {
// 节点标签
@ -645,7 +710,6 @@
rx :5,//矩形节点圆角度
ry :5
});
}else{
g.setNode(item.id, {
// 节点标签
@ -677,12 +741,14 @@
g.setEdge(transition0.source_state_.id, transition0.source_state_.id+100000, {
// 边标签
label: transition0.name,
// curve: d3.curveBasis,
style: "fill:#ffffff;stroke:#c0c1c3;stroke-width:1.5px"
});
let condition_expression = transition0.condition_expression;
condition_expression.forEach(condition_expression0=>{
g.setEdge(transition0.source_state_.id+100000, condition_expression0.target_state, {
label: condition_expression0.label,
// curve: d3.curveBasis,
style: "fill:#ffffff;stroke:#c0c1c3;stroke-width:1.5px"
})
})
@ -690,6 +756,7 @@
g.setEdge(transition0.source_state_.id, transition0.destination_state_.id, {
// 边标签
label: transition0.name,
// curve: d3.curveBasis,
// 边样式
style: "fill:#ffffff;stroke:#c0c1c3;stroke-width:1.5px" // 根据后台数据来改变连线的颜色
});
@ -699,6 +766,10 @@
g.nodes().forEach(function (v) {
console.log("Node " + v + ": " + JSON.stringify(g.node(v)));
});
console.log("1111111111111");
g.edges().forEach(function (e) {
console.log("Edge " + e.v + " -> " + e.w + ": " + JSON.stringify(g.edge(e)));
});
// 创建渲染器
let render = new dagreD3.render();
// 选择 svg 并添加一个g元素作为绘图容器.
@ -726,10 +797,14 @@
if (!filList.length) {
return
}
debugger;
console.log(filList)
filList.map((k) => {
let filte = that.logs.filter(item=>{
return item.state_.id = k.id;
return item.state == k.id;
})
debugger;
console.log(filte)
//每个
let str = '处理人:'+filte[0].participant_.name;
strList.push(str)
@ -800,6 +875,31 @@
}
})
},
handleLogs(scope){
let id = scope.row.id;
this.limitedFlowLogs = true;
getTicketFlowlog(id).then(res=>{
if(res.data){
this.floeLogs = res.data;
}
})
},
handleDelete(scope){
let data = new Object(),ids=[];
ids.push(scope.row.id);
data.ids = ids;
this.$confirm("确认删除该工单吗?", "温馨提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
ticketDestory(data).then(res=>{
if(res.code==200){
this.getList();
}
})
})
},
stepclick(){},
closeMark(){
this.dialogVisible = false;
@ -809,6 +909,19 @@
</script>
<style scoped>
::-webkit-scrollbar {
width: 15px;
}
::-webkit-scrollbar-track{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.2);
background-color: #fefefe;
border-radius: 7px;
}
::-webkit-scrollbar-thumb{
border-radius: 7px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.5);
background-color: #f5f5f5;
}
.svgMark{
width: 100%;
height: 100%;
@ -828,6 +941,8 @@
margin: 10vh auto 0;
text-align: center;
border-radius: 2px;
max-height: 80vh;
overflow-y: scroll;
}
.svgItem{
padding: 20px 40px 0 ;

View File

@ -13,7 +13,7 @@
</el-col>
<el-col :span="16">
<el-card style="margin-left: 10px">
<el-form ref="Form" :model="handleForm" label-width="100px" label-position="right" :rules="handleRule">
<el-form ref="Form" :model="ticketForm" label-width="100px" label-position="right" :rules="ticketFormRule">
<el-col :span="12">
<el-form-item label="标题" style="margin-bottom: 0">
<span>{{ticketDetail.title}}</span>
@ -114,24 +114,102 @@
<el-row>
<el-col :span="12">
<el-form-item label="审批意见">
<el-input type="textarea" :rows="3" v-model="handleForm.suggestion" placeholder="审批意见"/>
<el-input type="textarea" :rows="3" v-model="ticketForm.suggestion" placeholder="审批意见"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div style="text-align: center">
<el-button v-for="item in operationBtn" :key="item.id" class="filter-item" type="primary" @click="operationSubmit(item.id)">{{item.name}}</el-button>
<div style="text-align: center;display: flex;justify-content: space-between;">
<div>
<template v-if="ticketDetail.state_">
<!--state_.type===0普通状态&&act_state==1进行中participant_type!==2处理人不是多人!ticketDetail.in_add_node非加签状态-->
<el-button v-if="ticketDetail.act_state==1&&ticketDetail.state_.type===0&&!ticketDetail.in_add_node" type="primary" @click="handleAdd">加签</el-button>
<el-button v-if="ticketDetail.state_.type===1" type="primary" @click="handleClick('2')">关闭</el-button>
<el-button v-if="ticketDetail.state_.enable_retreat&&(userId==ticketDetail.create_by)&&ticketDetail.state_.type!==1" type="danger" @click="handleClick('1')">撤回</el-button>
</template>
</div>
<div>
<el-button v-if="ticketDetail.in_add_node" class="filter-item" type="primary" @click="handleClick('3')">加签处理</el-button>
<el-button v-for="item in operationBtn" :key="item.id" class="filter-item" type="primary" @click="operationSubmit(item.id)">{{item.name}}</el-button>
</div>
</div>
</el-card>
<el-card style="margin-left: 10px">
<el-table :data="logs" fit stripe
style="width: 100%;border-top:1px solid #EBEEF5;"
height="100"
highlight-current-row
v-el-height-adaptive-table="{bottomOffset: 60}">
<el-table-column label="工单标题" min-width="100">
<template slot-scope="scope" v-if="scope.row.ticket_data">
<span>{{scope.row.ticket_data.title}}</span>
</template>
</el-table-column>
<el-table-column label="进行状态" min-width="100">
<template slot-scope="scope" v-if="scope.row.state_">
<span v-if="scope.row.state_.type==0">{{scope.row.state_.name}}</span>
<span v-else>{{scope.row.state_.name}}</span>
</template>
</el-table-column>
<el-table-column label="操作人" min-width="100">
<template slot-scope="scope" v-if="scope.row.participant_">{{ scope.row.participant_.name }}</template>
</el-table-column>
<el-table-column label="操作意见" min-width="100" prop="suggestion">
</el-table-column>
<el-table-column label="更新时间" min-width="100" prop="update_time">
</el-table-column>
</el-table>
</el-card>
</el-col>
</el-row>
<!--撤回1关闭2加签结束3-->
<el-dialog :visible.sync="limitedHandle" :title="handleTitle">
<el-row>
<el-form ref="Form" :model="handleForm" label-width="100px" label-position="right">
<el-col :span="1" style="height: 1px;"></el-col>
<el-col :span="22" style="margin:3vh 0">
<el-form-item :label="handleLabel">
<el-input type="textarea" :rows="3" v-model="handleForm.suggestion" placeholder="撤回原因"/>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div style="text-align: center">
<el-button class="filter-item" type="" @click="retreatCancel">取消</el-button>
<el-button class="filter-item" type="primary" @click="handleSubmit">确定</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="limitedAdd" title="加签">
<el-row>
<el-form ref="Form" :model="addForm" label-width="100px" label-position="right">
<el-col :span="22">
<el-form-item label="加签操作人">
<el-select style="width: 100%" v-model="addForm.toadd_user" placeholder="加签操作人">
<el-option v-for="item in staffs" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="加签原因">
<el-input type="textarea" :rows="3" v-model="addForm.suggestion" placeholder="加签原因"/>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div style="text-align: center">
<el-button class="filter-item" type="" @click="addCancel">取消</el-button>
<el-button class="filter-item" type="primary" @click="addSubmit">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
import { upUrl, upHeaders } from "@/api/file";
import {getWorkflowList,getWfCustomfieldList,getWfStateList,getWfTransitionList,
ticketHandle,getWfFlowSteps,getTicketDetail,getTicketTransitions,getTicketFlowlog } from "@/api/workflow";
import { getUserList } from "@/api/user";
import {getWorkflowList,getWfCustomfieldList,getWfStateList,getWfTransitionList,ticketHandle
,getWfFlowSteps,getTicketDetail,getTicketTransitions,getTicketFlowlog,ticketRetreat,
ticketAddNode,ticketAddNodeEnd,ticketClose } from "@/api/workflow";
import Pagination from "@/components/Pagination";
import dagreD3 from 'dagre-d3'
import * as d3 from 'd3'
@ -145,19 +223,31 @@
actives:4,
ticketId:0,
stateSteps:0,
handleForm:{
ticketForm:{
transition:'',
ticket_data:{},
suggestion:'',
},
handleType:'1',
handleLabel:'撤回原因',
handleTitle:'工单撤回',
handleForm:{
suggestion:'',
},
addForm:{
toadd_user:'',
suggestion:'',
},
tooltip:null,
handleRule:{},
upUrl: upUrl(),
upHeaders: upHeaders(),
userId:'',
workflow:'',
watchedName:'',
watchedCreateTime:'',
logs:[],
staffs:[],
edges: [],
nodes: [],
tooltip: [],
@ -167,11 +257,15 @@
ticketDetail:{},
operationBtn:[],
customfields:[],
transitions:[]
transitions:[],
limitedAdd:false,
limitedHandle:false,
ticketFormRule:{},
}
},
mounted(){
let that = this;
that.userId = sessionStorage.getItem('userId')
let ticketId = that.ticketId = this.$route.params.ticketId;
let workflow = that.workflow = this.$route.params.workflow;
let handleTicketId = sessionStorage.getItem('handleTicketId');
@ -190,6 +284,7 @@
ticketId = that.ticketId = handleTicketId;
workflow = that.workflow = handleTicketWorkflow;
}
this.getTicketFlowlogs(ticketId);
getTicketTransitions(ticketId).then(res=>{
this.operationBtn = res.data;
})
@ -201,6 +296,9 @@
if(res.data){
that.tooltip = that.createTooltip();
that.ticketDetail = res.data;
console.log(that.ticketDetail.state_.enable_retreat);
debugger;
let state = res.data.state;
let dat = that.flowSteps.filter((item)=>{
return item.id==state;
@ -211,12 +309,12 @@
that.actives =that.flowSteps.length;
}
var g = new dagreD3.graphlib.Graph().setGraph({
align: 'DL',
rankdir: 'DL',
nodesep: 100,
edgesep: 100,
ranksep: 50,
marginx: 50,
marginy: 50,
edgesep: 10,//两条线之间的距离
ranksep: 50,//节点之间的距离
marginx: 60,
marginy: 20,
});
//获取state得到节点
getWfStateList(workflow).then((response) => {
@ -319,7 +417,7 @@
}
filList.map((k) => {
let filte = that.logs.filter(item=>{
return item.state_.id = k.id;
return item.state == k.id;
})
//每个
let str = '处理人:'+filte[0].participant_.name;
@ -345,6 +443,13 @@
activated(){
},
methods:{
getUser(){
getUserList({}).then(res=>{
if(res.data){
this.staffs = res.data.results;
}
})
},
//工单流转记录
getTicketFlowlogs(id){
getTicketFlowlog(id).then(res=>{
@ -403,19 +508,89 @@
.style('display', 'none')
},
operationSubmit(id){
this.handleForm.transition = id;
this.handleForm.ticket_data = this.ticketDetail.ticket_data;
this.ticketForm.transition = id;
this.ticketForm.ticket_data = this.ticketDetail.ticket_data;
let obj = new Object();
obj.transition = id;
obj.ticket_data = this.ticketDetail.ticket_data;
obj.suggestion = this.handleForm.suggestion;
console.log(this.handleForm);
obj.suggestion = this.ticketForm.suggestion;
console.log(this.ticketForm);
ticketHandle(this.ticketId,obj).then(res=>{
if (res.data){
this.$store.dispatch("user/getCount", {})
this.$router.replace({name:"ticket"})
}
})
},
retreatCancel(){
this.limitedHandle = false;
},
handleSubmit(){
let text = '',url='';
if(this.handleType==='1'){
text = "确认撤回工单吗?";
url = ticketRetreat;
}else if(this.handleType==='2'){
text = "确认关闭工单吗?";
url = ticketClose;
}if(this.handleType==='3'){
text = "确认处理工单吗?";
url = ticketAddNodeEnd;
}
this.$confirm(text, "温馨提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
url(this.ticketId,this.handleForm).then(res=>{
this.limitedHandle = false;
this.$store.dispatch("user/getCount", {})
this.$router.replace({name:"ticket"})
this.$message.success("成功");
})
})
.catch((err) => {
console.error(err);
});
},
handleClick(index){
this.limitedHandle = true;
this.handleType = index;
if(index==='1'){
this.handleTitle = '工单撤回';
this.handleLabel = '撤回原因';
}else if(index==='2'){
this.handleTitle = '工单关闭';
this.handleLabel = '关闭原因';
}else if(index==='3'){
this.handleTitle = '加签处理';
this.handleLabel = '加签意见';
}
},
handleAdd(){
this.getUser();
this.limitedAdd = true;
},
addCancel(){
this.limitedAdd = false;
},
addSubmit(){
ticketAddNode(this.ticketId,this.addForm).then(res=>{
if(res.data){
this.limitedAdd = false;
this.$nextTick(function () {
this.limitedAdd = true;
this.$store.dispatch("user/getCount", {})
this.$router.replace({name:"ticket"})
})
}
})
},
addNode(){
},
}
}
</script>

View File

@ -0,0 +1,197 @@
<template>
<div class="app-container">
<el-card style="margin-bottom: 10px">
<div>
<el-select v-model="filter.workflow" placeholder="工作流" clearable style="width: 200px" class="filter-item" @change="handleFilter">
<el-option
v-for="item in workFlows"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
<el-select v-model="filter.act_state" placeholder="工单进行状态" clearable style="width: 200px" class="filter-item" @change="handleFilter">
<el-option label="草稿中" value="0" />
<el-option label="进行中" value="1" />
<el-option label="被退回" value="2" />
<el-option label="被撤回" value="3" />
<el-option label="已完成" value="4" />
<el-option label="已关闭" value="5" />
</el-select>
<el-button
class="filter-item"
type="primary"
icon="el-icon-refresh-left"
@click="resetFilter"
>重置</el-button>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="handleFilter"
>搜索</el-button>
</div>
</el-card>
<el-card>
<el-table :data="tickets"
border fit stripe
style="width: 100%"
height="100"
highlight-current-row
v-el-height-adaptive-table="{bottomOffset: 60}">
<el-table-column label="工单标题" min-width="100" prop="title">
</el-table-column>
<el-table-column label="当前状态" min-width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.act_state==0" label="草稿中" value="scope.row.act_state">草稿中</el-tag>
<el-tag v-else-if="scope.row.act_state==1" label="进行中" value="scope.row.act_state">进行中</el-tag>
<el-tag v-else-if="scope.row.act_state==2" label="被退回" value="scope.row.act_state">被退回</el-tag>
<el-tag v-else-if="scope.row.act_state==3" label="被撤回" value="scope.row.act_state">被撤回</el-tag>
<el-tag v-else-if="scope.row.act_state==4" label="已完成" value="scope.row.act_state">已完成</el-tag>
<el-tag v-else-if="scope.row.act_state==5" label="已关闭" value="scope.row.act_state">已关闭</el-tag>
</template>
</el-table-column>
<el-table-column label="进行状态" min-width="100">
<template slot-scope="scope">
<span v-if="scope.row.state_.type==0">{{scope.row.state_.name}}</span>
<span v-else>{{scope.row.state_.name}}</span>
</template>
</el-table-column>
<el-table-column label="类型" min-width="100">
<template slot-scope="scope">{{ scope.row.workflow_.name }}</template>
</el-table-column>
<el-table-column label="创建时间" min-width="100" prop="create_time">
</el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-link type="success" @click="handleLogs(scope)">工单日志</el-link>
</template>
</el-table-column>
</el-table>
<pagination
:total="total"
:page.sync="filter.page"
:limit.sync="filter.page_size"
@pagination="getList"
/>
</el-card>
<el-dialog :visible.sync="limitedFlowLogs" title="工单日志">
<el-table :data="floeLogs" fit stripe
style="width: 100%;border-top:1px solid #EBEEF5;"
height="100"
highlight-current-row
v-el-height-adaptive-table="{bottomOffset: 60}">
<el-table-column label="工单标题" min-width="100">
<template slot-scope="scope" v-if="scope.row.ticket_data">
<span>{{scope.row.ticket_data.title}}</span>
</template>
</el-table-column>
<!-- <el-table-column label="当前状态" min-width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.act_state==0" label="草稿中" value="scope.row.act_state">草稿中</el-tag>
<el-tag v-else-if="scope.row.act_state==1" label="进行中" value="scope.row.act_state">进行中</el-tag>
<el-tag v-else-if="scope.row.act_state==2" label="被退回" value="scope.row.act_state">被退回</el-tag>
<el-tag v-else-if="scope.row.act_state==3" label="被撤回" value="scope.row.act_state">被撤回</el-tag>
<el-tag v-else-if="scope.row.act_state==4" label="已完成" value="scope.row.act_state">已完成</el-tag>
<el-tag v-else-if="scope.row.act_state==5" label="已关闭" value="scope.row.act_state">已关闭</el-tag>
</template>
</el-table-column>-->
<el-table-column label="进行状态" min-width="100">
<template slot-scope="scope" v-if="scope.row.state_">
<span v-if="scope.row.state_.type==0">{{scope.row.state_.name}}</span>
<span v-else>{{scope.row.state_.name}}</span>
</template>
</el-table-column>
<el-table-column label="操作人" min-width="100">
<template slot-scope="scope" v-if="scope.row.participant_">{{ scope.row.participant_.name }}</template>
</el-table-column>
<el-table-column label="操作意见" min-width="100" prop="suggestion">
</el-table-column>
<el-table-column label="更新时间" min-width="100" prop="update_time">
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script>
import {getWorkflowList,getTickets,getTicketFlowlog} from "@/api/workflow";
import Pagination from "@/components/Pagination";
export default {
name: "workFlowTickets",
components: { Pagination },
inject:['reload'],
data(){
return{
sort:0,
total:0,
filter:{
page:1,
page_size:20,
state:'',
act_state:'',
workflow:'',
category:'all',
search:'',
},
listLoading:false,
limitedFlowLogs:false,
floeLogs:[],
tickets:[],
workFlows:[],
}
},
mounted(){
this.workflow = this.$route.params.workflow;
if(this.workflow){//有传参
this.filter.workflow = parseInt(this.workflow);
}
this.getList();
this.getWorkFlow();
},
methods:{
getList(){
this.listLoading = true;
getTickets( this.filter).then((res)=>{
if(res.data.results){
this.total = res.data.count;
this.tickets = res.data.results;
this.listLoading = false;
}
})
},
//获取工作流
getWorkFlow(){
let listForm = {page:0};
getWorkflowList(listForm).then((response) => {
if (response.data) {
this.workFlows = response.data;
}
});
},
//查询搜索
handleFilter(){
this.filter.page=1;
this.getList();
},
//筛选重置
resetFilter(){
this.filter.workflow = '';
this.filter.act_state = '';
this.getList();
},
handleLogs(scope){
let id = scope.row.id;
this.limitedFlowLogs = true;
getTicketFlowlog(id).then(res=>{
if(res.data){
this.floeLogs = res.data;
}
})
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-10-18 05:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrm', '0002_auto_20210924_1127'),
]
operations = [
migrations.AddField(
model_name='employee',
name='face_data',
field=models.JSONField(blank=True, null=True, verbose_name='人脸识别数据'),
),
]

View File

@ -28,9 +28,12 @@ class Employee(CommonAModel):
academic = models.CharField('学历', max_length=50, null=True, blank=True)
jobstate = models.IntegerField('在职状态', choices=jobstate_choices, default=1)
job = models.ForeignKey(Position, null=True, blank=True, on_delete=models.SET_NULL, verbose_name='岗位')
face_data = models.JSONField('人脸识别数据', null=True, blank=True)
class Meta:
verbose_name = '员工补充信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
return self.name

View File

@ -1,6 +1,6 @@
from apps.system.models import User
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from .models import Employee
from apps.system.serializers import UserListSerializer, UserSimpleSerializer
from django.db.models.query import Prefetch
@ -20,3 +20,6 @@ class EmployeeSerializer(ModelSerializer):
# queryset=User.objects.filter(employee_user__isnull=True))
# )
# return queryset
class FaceLoginSerializer(serializers.Serializer):
base64 = serializers.CharField()

View File

@ -1,12 +1,13 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.hrm.views import EmployeeViewSet
from apps.hrm.views import EmployeeViewSet, FaceLogin
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('employee', EmployeeViewSet, basename='employee')
urlpatterns = [
path('facelogin/', FaceLogin.as_view()),
path('', include(router.urls)),
]

View File

@ -1,9 +1,34 @@
from django.shortcuts import render
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from apps.hrm.models import Employee
from apps.hrm.serializers import EmployeeSerializer
from apps.hrm.serializers import EmployeeSerializer, 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 import status
from rest_framework_simplejwt.tokens import RefreshToken
from apps.system.models import User
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.
class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMixin, RetrieveModelMixin, GenericViewSet):
"""
@ -12,4 +37,73 @@ class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMix
perms_map = {'get': '*', 'put': 'employee_update'}
queryset = Employee.objects.all()
serializer_class = EmployeeSerializer
ordering = ['-pk']
ordering = ['-pk']
def perform_update(self, serializer):
instance = serializer.save(update_by = self.request.user)
try:
photo_path = settings.BASE_DIR + instance.photo
picture_of_me = face_recognition.load_image_file(photo_path)
my_face_encoding = face_recognition.face_encodings(picture_of_me)[0]
instance.face_data = my_face_encoding.tolist()
instance.save()
except:
logger.error('人脸识别出错')
import uuid
import base64
import os
def tran64(s):
missing_padding = len(s) % 4
if missing_padding != 0:
s = s+'='* (4 - missing_padding)
return s
class FaceLogin(CreateAPIView):
authentication_classes = []
permission_classes = []
serializer_class = FaceLoginSerializer
def create(self, request, *args, **kwargs):
"""
人脸识别登录
"""
# serializer = FaceLoginSerializer(data=request.data)
# serializer.is_valid(raise_exception=True)
filename = str(uuid.uuid4())
filepath = settings.BASE_DIR +'/temp/' + filename +'.png'
with open(filepath, 'wb') as f:
data = tran64(request.data.get('base64').replace(' ', '+'))
f.write(base64.urlsafe_b64decode(data))
# picture_of_me = face_recognition.load_image_file(settings.BASE_DIR +'/temp/me.png')
# my_face_encoding = face_recognition.face_encodings(picture_of_me)[0]
#results = face_recognition.compare_faces([my_face_encoding], unknown_face_encoding, tolerance=0.2)
try:
unknown_picture = face_recognition.load_image_file(filepath)
unknown_face_encoding = face_recognition.face_encodings(unknown_picture)[0]
os.remove(filepath)
except:
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

@ -6,7 +6,7 @@ from apps.system.models import CommonAModel, CommonBModel, Organization, User, D
from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords
from apps.mtm.models import Material
from apps.pm.models import SubProductionPlan
class WareHouse(CommonAModel):
@ -57,4 +57,13 @@ class FIFO(CommonAModel):
(3, '采购入库'),
(4, '生产入库')
)
type = models.IntegerField('出入库类型', default=1)
type = models.IntegerField('出入库类型', default=1)
operator = models.ForeignKey(User, verbose_name='操作人', on_delete=models.CASCADE)
subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.DO_NOTHING, null=True, blank=True)
# class FIFODetail(CommonAModel):
# """
# 领料详细记录
# """

View File

@ -0,0 +1,49 @@
# Generated by Django 3.2.6 on 2021-10-18 02:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mtm', '0022_auto_20211014_0944'),
]
operations = [
migrations.AddField(
model_name='recordformfield',
name='high_limit',
field=models.FloatField(blank=True, null=True, verbose_name='上限值'),
),
migrations.AddField(
model_name='recordformfield',
name='high_rule',
field=models.IntegerField(blank=True, choices=[(1, '小于'), (2, '小于等于')], null=True, verbose_name='上限规则'),
),
migrations.AddField(
model_name='recordformfield',
name='low_limit',
field=models.FloatField(blank=True, null=True, verbose_name='下限值'),
),
migrations.AddField(
model_name='recordformfield',
name='low_rule',
field=models.IntegerField(blank=True, choices=[(1, '大于'), (2, '大于等于')], null=True, verbose_name='下限规则'),
),
migrations.AddField(
model_name='recordformfield',
name='need_judge',
field=models.BooleanField(default=False, verbose_name='需要判定'),
),
migrations.AddField(
model_name='recordformfield',
name='rule_expression',
field=models.JSONField(default=list, help_text='判定表达式, 格式为[{"expression":"{value} > 3 and {value}<10"}] 其中{}用于填充工单的字段key,运算时会换算成实际的值符合条件返回true,表达式只支持简单的运算或datetime/time运算.以首次匹配成功的条件为准,所以多个条件不要有冲突', verbose_name='判定表达式'),
),
migrations.AlterField(
model_name='usedstep',
name='step',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='usedstep', to='mtm.step', verbose_name='子工序'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-10-19 01:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0023_auto_20211018_1057'),
]
operations = [
migrations.AlterField(
model_name='recordformfield',
name='rule_expression',
field=models.JSONField(default=list, help_text='判定表达式, 格式为[{"expression":"{value} > 3 and {value}<10"}] 其中{}用于填充的字段key,运算时会换算成实际的值符合条件返回true,表达式只支持简单的运算或datetime/time运算.以首次匹配成功的条件为准,所以多个条件不要有冲突', verbose_name='判定表达式'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-10-21 08:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0024_alter_recordformfield_rule_expression'),
]
operations = [
migrations.AddField(
model_name='outputmaterial',
name='is_main',
field=models.BooleanField(default=True, verbose_name='是否主产出'),
),
]

View File

@ -110,6 +110,14 @@ class RecordFormField(CommonAModel):
('selects', '多选下拉'),
('textarea', '文本域'),
)
high_rule_choices = (
(1, '小于'),
(2, '小于等于'),
)
low_rule_choices = (
(1, '大于'),
(2, '大于等于'),
)
form = models.ForeignKey(RecordForm, on_delete=models.CASCADE, verbose_name='关联表格')
field_type = models.CharField('类型', max_length=50, choices=field_type_choices)
field_key = models.CharField('字段标识', max_length=50, help_text='字段类型请尽量特殊,避免与系统中关键字冲突')
@ -119,6 +127,13 @@ class RecordFormField(CommonAModel):
field_choice = models.JSONField('radio、checkbox、select的选项', default=dict, blank=True, null=True,
help_text='radio,checkbox,select,multiselect类型可供选择的选项格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号')
sort = models.IntegerField('排序号', default=1)
need_judge = models.BooleanField('需要判定', default=False)
high_limit = models.FloatField('上限值', null=True, blank=True)
high_rule = models.IntegerField('上限规则', choices=high_rule_choices, null=True, blank=True)
low_limit = models.FloatField('下限值', null=True, blank=True)
low_rule = models.IntegerField('下限规则', choices=low_rule_choices, null=True, blank=True)
rule_expression = models.JSONField('判定表达式', default=list, help_text='判定表达式, 格式为[{"expression":"{value} > 3 and {value}<10"}] 其中{}用于填充的字段key,运算时会换算成实际的值符合条件返回true,表达式只支持简单的运算或datetime/time运算.以首次匹配成功的条件为准,所以多个条件不要有冲突' )
class Meta:
verbose_name = '记录表格字段'
verbose_name_plural = verbose_name
@ -162,6 +177,7 @@ class OutputMaterial(CommonAModel):
输出物料
"""
material = models.ForeignKey(Material, verbose_name='输出物料', on_delete=models.CASCADE, related_name='outputmaterial')
is_main = models.BooleanField('是否主产出', default=True) # 以该产品完成度计算进度
count = models.FloatField('产出量', default=1)
subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE)
sort = models.IntegerField('排序号', default=1)
@ -174,7 +190,7 @@ class UsedStep(CommonAModel):
"""
涉及的生产子工序
"""
step = models.ForeignKey(Step, verbose_name='子工序', on_delete=models.CASCADE, related_name='usedsteps')
step = models.ForeignKey(Step, verbose_name='子工序', on_delete=models.CASCADE, related_name='usedstep')
remark = models.TextField('生产备注', null=True, blank=True)
subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE)

View File

@ -98,9 +98,11 @@ class InputMaterialUpdateSerializer(serializers.ModelSerializer):
class OutputMaterialSerializer(serializers.ModelSerializer):
class Meta:
model = OutputMaterial
fields = ['count', 'sort', 'material', 'subproduction']
fields = ['count', 'sort', 'material', 'subproduction', 'is_main']
def create(self, validated_data):
if OutputMaterial.objects.filter(subproduction=validated_data['subproduction'], is_deleted=False, is_main=True).exists():
raise ValidationError('主产出只能有1个')
if OutputMaterial.objects.filter(material=validated_data['material'], subproduction=validated_data['subproduction'], is_deleted=False).exists():
raise ValidationError('该物料已存在')
return super().create(validated_data)
@ -108,7 +110,7 @@ class OutputMaterialSerializer(serializers.ModelSerializer):
class OutputMaterialUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = OutputMaterial
fields = ['count', 'sort']
fields = ['count', 'sort', 'is_main']
class UsedStepCreateSerializer(serializers.ModelSerializer):
"""
@ -175,7 +177,7 @@ class RecordFormFieldSerializer(serializers.ModelSerializer):
class RecordFormFieldCreateSerializer(serializers.ModelSerializer):
class Meta:
model = RecordFormField
fields = ['form', 'field_type', 'field_key', 'field_name', 'boolean_field_display', 'field_choice', 'sort']
fields = ['form', 'field_type', 'field_key', 'field_name', 'boolean_field_display', 'field_choice', 'sort', 'need_judge', 'high_limit', 'high_rule', 'low_limit', 'low_rule', 'rule_expression']
def validate(self, data):
if RecordFormField.objects.filter(field_key=data['field_key'], form=data['form'], is_deleted=False).exists():
@ -185,7 +187,7 @@ class RecordFormFieldCreateSerializer(serializers.ModelSerializer):
class RecordFormFieldUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = RecordFormField
fields = ['field_type', 'field_name', 'boolean_field_display', 'field_choice', 'sort']
fields = ['field_type', 'field_name', 'boolean_field_display', 'field_choice', 'sort', 'need_judge', 'high_limit', 'high_rule', 'low_limit', 'low_rule', 'rule_expression']
class RecordFormFieldSimpleSerializer(serializers.ModelSerializer):
class Meta:

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-10-15 02:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pm', '0003_auto_20211014_1503'),
]
operations = [
migrations.AddField(
model_name='subproductionplan',
name='steps',
field=models.JSONField(default=list, verbose_name='工艺步骤'),
),
]

View File

@ -0,0 +1,38 @@
# Generated by Django 3.2.6 on 2021-10-19 01:44
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('mtm', '0024_alter_recordformfield_rule_expression'),
('pm', '0004_subproductionplan_steps'),
]
operations = [
migrations.AddField(
model_name='subproductionplan',
name='state',
field=models.IntegerField(default=0, verbose_name='状态'),
),
migrations.CreateModel(
name='SubProductionProgress',
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.IntegerField(default=1, verbose_name='物料应用类型')),
('count', models.IntegerField(verbose_name='应出入数')),
('count_real', models.IntegerField(verbose_name='实际出入数')),
('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='关联物料')),
('subproduction_plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='progress_subplan', to='pm.subproductionplan', verbose_name='关联子生产计划')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-10-19 01:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pm', '0005_auto_20211019_0944'),
]
operations = [
migrations.AlterField(
model_name='subproductionprogress',
name='count_real',
field=models.IntegerField(default=0, verbose_name='实际出入数'),
),
]

View File

@ -31,20 +31,35 @@ class SubProductionPlan(CommonAModel):
"""
子生产计划
"""
state_choices=(
(0, '制定中'),
(1, '已下达'),
(2, '已接收'),
(3, '生产中'),
(4, '已完成')
)
production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE)
subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE)
start_date = models.DateField('计划开工日期')
end_date = models.DateField('计划完工日期')
workshop = models.ForeignKey(Organization, verbose_name='生产车间', on_delete=models.CASCADE)
process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE)
# steps = models.JSONField('工艺步骤', default=list)
steps = models.JSONField('工艺步骤', default=list)
state = models.IntegerField('状态', default=0)
class Meta:
verbose_name = '子生产计划'
verbose_name_plural = verbose_name
# class ProductionProgress(BaseModel):
# """
# 子计划生产进度
# """
# subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE)
# material = models.
class SubProductionProgress(BaseModel):
"""
子计划生产进度统计表
"""
type_choices=(
(1, '输入物料'),
(2, '输出物料')
)
subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE, related_name='progress_subplan')
material = models.ForeignKey(Material, verbose_name='关联物料', on_delete=models.CASCADE)
type = models.IntegerField('物料应用类型', default=1)
count = models.IntegerField('应出入数')
count_real = models.IntegerField('实际出入数', default=0)

View File

@ -1,4 +1,4 @@
from apps.pm.models import ProductionPlan, SubProductionPlan
from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress
from rest_framework import serializers
from apps.sam.serializers import OrderSerializer
from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer
@ -35,3 +35,12 @@ class SubProductionPlanUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = SubProductionPlan
fields = ['start_date', 'end_date']
class GenSubPlanSerializer(serializers.Serializer):
pass
class SubProductionProgressSerializer(serializers.ModelSerializer):
material_ = MaterialSimpleSerializer(source='material', read_only=True)
class Meta:
model = SubProductionProgress
fields = '__all__'

View File

@ -2,17 +2,18 @@ from rest_framework import serializers
from rest_framework.views import APIView
from apps.em.models import Equipment
from apps.em.serializers import EquipmentSerializer
from apps.mtm.models import InputMaterial, Step, SubProduction, UsedStep
from apps.mtm.models import InputMaterial, OutputMaterial, Step, SubProduction, UsedStep
from apps.system.mixins import CreateUpdateModelAMixin
from apps.pm.serializers import ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer
from apps.pm.serializers import GenSubPlanSerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer
from rest_framework.mixins import CreateModelMixin, ListModelMixin, UpdateModelMixin
from apps.pm.models import ProductionPlan, SubProductionPlan
from apps.pm.models import ProductionPlan, SubProductionProgress, SubProductionPlan
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from django.shortcuts import render
from apps.sam.models import Order
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.decorators import action
from django.db.models import F
# Create your views here.
def updateOrderPlanedCount(order):
@ -59,7 +60,7 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel
updateOrderPlanedCount(instance.order)
return Response()
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=serializers.Serializer)
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=GenSubPlanSerializer)
def gen_subplan(self, request, pk=None):
"""
生成子计划
@ -69,9 +70,16 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel
raise APIException('已生成子计划')
subps = SubProduction.objects.filter(product=production_plan.product).order_by('process__number')
for i in subps:
SubProductionPlan.objects.create(production_plan=production_plan, subproduction=i,
steps = Step.objects.filter(usedstep__subproduction=i, usedstep__subproduction__is_deleted=False,
usedstep__is_deleted=False, is_deleted=False).values('id', 'number', 'name', 'usedstep__remark')
instance = SubProductionPlan.objects.create(production_plan=production_plan, subproduction=i,
start_date=production_plan.start_date, end_date=production_plan.end_date,
workshop=i.process.workshop, process=i.process, create_by=request.user)
workshop=i.process.workshop, process=i.process, create_by=request.user,
steps = list(steps))
for m in InputMaterial.objects.filter(subproduction=i, is_deleted=False).order_by('sort'):
SubProductionProgress.objects.create(material=m.material, type=1, count=m.count, subproduction_plan=instance)
for m in OutputMaterial.objects.filter(subproduction=i, is_deleted=False).order_by('sort'):
SubProductionProgress.objects.create(material=m.material, type=2, count=m.count, subproduction_plan=instance)
production_plan.is_planed=True
production_plan.save()
return Response()
@ -92,7 +100,28 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
return SubProductionPlanListSerializer
elif self.action == 'update':
return SubProductionPlanUpdateSerializer
return SubProductionPlanListSerializer
@action(methods=['get'], detail=True, perms_map={'get':'*'}, serializer_class=SubProductionProgressSerializer)
def progress(self, request, pk=None):
"""
生产进度详情
"""
obj = self.get_object()
serializer = SubProductionProgressSerializer(instance=obj.progress_subplan, many=True)
return Response(serializer.data)
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=serializers.Serializer)
def issue(self, request, pk=None):
"""
下达任务
"""
obj = self.get_object()
if obj.state == 0:
obj.state = 1
obj.save()
return Response()
raise APIException('计划状态有误')
class ResourceViewSet(GenericViewSet):
@ -135,8 +164,8 @@ class ResourceViewSet(GenericViewSet):
for i in rdata:
rdata_l.append(i['id'])
subproductions = SubProduction.objects.filter(product__id__in=rdata_l, is_deleted=False)
steps = Step.objects.filter(usedsteps__is_deleted=False, usedsteps__subproduction__in=subproductions)
equips = Equipment.objects.filter(step_equips__in=steps, is_deleted=False)
steps = Step.objects.filter(usedstep__is_deleted=False, usedstep__subproduction__in=subproductions)
equips = Equipment.objects.filter(step_equips__in=steps, is_deleted=False).distinct()
serializer = EquipmentSerializer(instance=equips, many=True)
return Response(serializer.data)

View File

@ -199,5 +199,3 @@ class UserCreateSerializer(serializers.ModelSerializer):
return phone
class FaceLoginSerializer(serializers.Serializer):
base64 = serializers.CharField()

View File

@ -1,5 +1,5 @@
from django.urls import path, include
from .views import FaceLogin, TaskList, UserViewSet, OrganizationViewSet, PermissionViewSet, RoleViewSet, PositionViewSet, TestView, DictTypeViewSet, DictViewSet, PTaskViewSet
from .views import TaskList, UserViewSet, OrganizationViewSet, PermissionViewSet, RoleViewSet, PositionViewSet, TestView, DictTypeViewSet, DictViewSet, PTaskViewSet
from rest_framework import routers
@ -15,6 +15,5 @@ router.register('ptask', PTaskViewSet, basename="ptask")
urlpatterns = [
path('', include(router.urls)),
path('task/', TaskList.as_view()),
path('test/', TestView.as_view()),
path('facelogin/', FaceLogin.as_view())
path('test/', TestView.as_view())
]

View File

@ -21,6 +21,7 @@ from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.exceptions import ValidationError, ParseError
from apps.hrm.models import Employee
from utils.queryset import get_child_queryset2
from .filters import UserFilter
@ -29,7 +30,7 @@ from .models import (Dict, DictType, File, Organization, Permission, Position,
Role, User)
from .permission import RbacPermission, get_permission_list
from .permission_data import RbacFilterSet
from .serializers import (DictSerializer, DictTypeSerializer, FaceLoginSerializer, FileSerializer,
from .serializers import (DictSerializer, DictTypeSerializer, FileSerializer,
OrganizationSerializer, PermissionSerializer,
PositionSerializer, RoleSerializer, PTaskSerializer,PTaskCreateUpdateSerializer,
UserCreateSerializer, UserListSerializer,
@ -352,43 +353,3 @@ class FileViewSet(CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListM
instance.save()
#import face_recognition
import uuid
import base64
import os
def tran64(s):
missing_padding = len(s) % 4
if missing_padding != 0:
s = s+'='* (4 - missing_padding)
return s
class FaceLogin(CreateAPIView):
authentication_classes = []
permission_classes = []
serializer_class = FaceLoginSerializer
def create(self, request, *args, **kwargs):
"""
人脸识别登录
"""
# serializer = FaceLoginSerializer(data=request.data)
# serializer.is_valid(raise_exception=True)
filename = str(uuid.uuid4())
filepath = settings.BASE_DIR +'/temp/' + filename +'.png'
with open(filepath, 'wb') as f:
data = tran64(request.data.get('base64').replace(' ', '+'))
f.write(base64.urlsafe_b64decode(data))
# picture_of_me = face_recognition.load_image_file(settings.BASE_DIR +'/temp/me.png')
# my_face_encoding = face_recognition.face_encodings(picture_of_me)[0]
# unknown_picture = face_recognition.load_image_file(filepath)
# unknown_face_encoding = face_recognition.face_encodings(unknown_picture)[0]
#results = face_recognition.compare_faces([my_face_encoding], unknown_face_encoding, tolerance=0.2)
os.remove(filepath)
# if results[0] == True:
# return Response('这是曹前明')
# else:
# return Response('这不是曹前明')

View File

@ -16,7 +16,9 @@ class TicketFilterSet(filters.FilterSet):
elif value == 'duty':
queryset = queryset.filter(participant__contains=user.id).exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
elif value == 'worked':
queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user)
queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user).order_by('-update_time').distinct()
elif value == 'cc':
queryset = queryset.filter(ticketflow_ticket__participant_cc__contains=user.id).exclude(create_by=user).order_by('-update_time').distinct()
elif value == 'all':
pass
else:

View File

@ -0,0 +1,36 @@
# Generated by Django 3.2.6 on 2021-10-19 01:58
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),
('wf', '0013_alter_ticketflow_transition'),
]
operations = [
migrations.CreateModel(
name='WfScript',
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='删除标记')),
('usage', models.IntegerField(choices=[(1, '获取处理人'), (2, '执行操作')], default=1, verbose_name='脚本用途')),
('wait', models.BooleanField(default=True, verbose_name='是否等待执行完成')),
('name', models.CharField(max_length=100, verbose_name='脚本名称')),
('content', models.TextField(verbose_name='脚本内容')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wfscript_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='wfscript_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('workflow', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wf.workflow', verbose_name='关联工作流')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,40 @@
# Generated by Django 3.2.6 on 2021-10-21 02:49
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0003_auto_20210812_0909'),
('wf', '0014_wfscript'),
]
operations = [
migrations.AddField(
model_name='state',
name='filter_policy',
field=models.IntegerField(choices=[(0, ''), (1, '和工单同属一及上级部门'), (2, '和创建人同属一及上级部门'), (3, '和上步处理人同属一及上级部门')], default=0, verbose_name='参与人过滤策略'),
),
migrations.AddField(
model_name='ticket',
name='belong_dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticket_belong_dept', to='system.organization', verbose_name='所属部门'),
),
migrations.AlterField(
model_name='state',
name='participant_type',
field=models.IntegerField(blank=True, choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (5, '变量'), (6, '脚本'), (7, '工单的字段'), (8, '父工单的字段'), (9, '代码获取')], default=1, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填create_by', verbose_name='参与者类型'),
),
migrations.AlterField(
model_name='ticket',
name='participant_type',
field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (5, '变量'), (6, '脚本'), (7, '工单的字段'), (8, '父工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='当前处理人类型'),
),
migrations.AlterField(
model_name='ticketflow',
name='participant_type',
field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (3, '部门'), (4, '角色'), (5, '变量'), (6, '脚本'), (7, '工单的字段'), (8, '父工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='处理人类型'),
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 3.2.6 on 2021-10-24 15:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wf', '0015_auto_20211021_1049'),
]
operations = [
migrations.AddField(
model_name='state',
name='participant_cc',
field=models.JSONField(blank=True, default=list, help_text='抄送给(userid列表)', verbose_name='抄送给'),
),
migrations.AddField(
model_name='ticketflow',
name='participant_cc',
field=models.JSONField(blank=True, default=list, help_text='抄送给(userid列表)', verbose_name='抄送给'),
),
migrations.AddField(
model_name='wfscript',
name='func_str',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='函数名'),
),
migrations.AlterField(
model_name='state',
name='participant_type',
field=models.IntegerField(blank=True, choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=1, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填create_by', verbose_name='参与者类型'),
),
migrations.AlterField(
model_name='ticket',
name='participant_type',
field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='当前处理人类型'),
),
migrations.AlterField(
model_name='ticketflow',
name='intervene_type',
field=models.IntegerField(choices=[(0, '正常处理'), (1, '转交'), (2, '加签'), (3, '加签处理完成'), (4, '接单'), (5, '评论'), (6, '删除'), (7, '强制关闭'), (8, '强制修改状态'), (9, 'hook操作'), (10, '撤回'), (11, '抄送')], default=0, help_text='流转类型', verbose_name='干预类型'),
),
migrations.AlterField(
model_name='ticketflow',
name='participant_type',
field=models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='处理人类型'),
),
migrations.AlterField(
model_name='wfscript',
name='usage',
field=models.IntegerField(choices=[(1, '获取处理人'), (2, '执行操作')], default=2, verbose_name='脚本用途'),
),
]

View File

@ -1,3 +1,4 @@
from random import choice
from django.db import models
from django.db.models.base import Model
import django.utils.timezone as timezone
@ -39,16 +40,18 @@ class State(CommonAModel):
PARTICIPANT_TYPE_ROBOT = 6
PARTICIPANT_TYPE_FIELD = 7
PARTICIPANT_TYPE_PARENT_FIELD = 8
PARTICIPANT_TYPE_FORMCODE = 9
state_participanttype_choices = (
(0, '无处理人'),
(PARTICIPANT_TYPE_PERSONAL, '个人'),
(PARTICIPANT_TYPE_MULTI, '多人'),
(PARTICIPANT_TYPE_DEPT, '部门'),
# (PARTICIPANT_TYPE_DEPT, '部门'),
(PARTICIPANT_TYPE_ROLE, '角色'),
(PARTICIPANT_TYPE_VARIABLE, '变量'),
# (PARTICIPANT_TYPE_VARIABLE, '变量'),
(PARTICIPANT_TYPE_ROBOT, '脚本'),
(PARTICIPANT_TYPE_FIELD, '工单的字段'),
(PARTICIPANT_TYPE_PARENT_FIELD, '父工单的字段')
# (PARTICIPANT_TYPE_PARENT_FIELD, '父工单的字段'),
(PARTICIPANT_TYPE_FORMCODE, '代码获取')
)
STATE_DISTRIBUTE_TYPE_ACTIVE = 1 # 主动接单
STATE_DISTRIBUTE_TYPE_DIRECT = 2 # 直接处理(当前为多人的情况,都可以处理,而不需要先接单)
@ -64,6 +67,13 @@ class State(CommonAModel):
STATE_FIELD_READONLY= 1 # 字段只读
STATE_FIELD_REQUIRED = 2 # 字段必填
STATE_FIELD_OPTIONAL = 3 # 字段可选
state_filter_choices=(
(0, ''),
(1, '和工单同属一及上级部门'),
(2, '和创建人同属一及上级部门'),
(3, '和上步处理人同属一及上级部门'),
)
name = models.CharField('名称', max_length=50)
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
is_hidden = models.BooleanField('是否隐藏', default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)')
@ -74,6 +84,8 @@ class State(CommonAModel):
participant = models.JSONField('参与者', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\部门id\角色id\变量(create_by,create_by_tl)\脚本记录的id等包含子工作流的需要设置处理人为loonrobot')
state_fields = models.JSONField('表单字段', default=dict, help_text='json格式字典存储,包括读写属性1只读2必填3可选. 示例:{"create_time":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称') # json格式存储,包括读写属性1只读2必填3可选4不显示, 字典的字典
distribute_type = models.IntegerField('分配方式', default=1, choices=state_distribute_choices, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
filter_policy = models.IntegerField('参与人过滤策略', default=0, choices=state_filter_choices)
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
class Transition(CommonAModel):
"""
@ -97,6 +109,7 @@ class Transition(CommonAModel):
TRANSITION_INTERVENE_TYPE_ALTER_STATE = 8 # 强制修改状态操作
TRANSITION_INTERVENE_TYPE_HOOK = 9 # hook操作
TRANSITION_INTERVENE_TYPE_RETREAT = 10 # 撤回
TRANSITION_INTERVENE_TYPE_CC = 11 # 抄送
intervene_type_choices = (
(0, '正常处理'),
@ -109,7 +122,8 @@ class Transition(CommonAModel):
(TRANSITION_INTERVENE_TYPE_CLOSE, '强制关闭'),
(TRANSITION_INTERVENE_TYPE_ALTER_STATE, '强制修改状态'),
(TRANSITION_INTERVENE_TYPE_HOOK, 'hook操作'),
(TRANSITION_INTERVENE_TYPE_RETREAT, '撤回')
(TRANSITION_INTERVENE_TYPE_RETREAT, '撤回'),
(TRANSITION_INTERVENE_TYPE_CC, '抄送')
)
name = models.CharField('操作', max_length=50)
@ -155,7 +169,7 @@ class CustomField(CommonAModel):
help_text='radio,checkbox,select,multiselect类型可供选择的选项格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号')
label = models.JSONField('标签', blank=True, default=dict, help_text='自定义标签json格式调用方可根据标签自行处理特殊场景逻辑loonflow只保存文本内容')
class Ticket(CommonAModel):
class Ticket(CommonBModel):
"""
工单
"""
@ -177,9 +191,9 @@ class Ticket(CommonAModel):
category_choices =(
('all', '全部'),
('owner', '我创建的'),
('duty', ''),
('duty', ''),
('worked', '我处理的'),
('relation', '抄送我的')
('cc', '抄送我的')
)
title = models.CharField('标题', max_length=500, blank=True, default='', help_text="工单标题")
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='关联工作流')
@ -207,4 +221,21 @@ class TicketFlow(BaseModel):
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant')
state = models.ForeignKey(State, verbose_name='当前状态', default=0, blank=True, on_delete=models.CASCADE)
ticket_data = models.JSONField('工单数据', default=dict, blank=True, help_text='可以用于记录当前表单数据json格式')
intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices)
intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices)
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
class WfScript(CommonAModel):
"""
执行脚本
"""
usage_choices =(
(1, '获取处理人'),
(2, '执行操作'),
)
usage = models.IntegerField('脚本用途', default=2, choices=usage_choices)
wait = models.BooleanField('是否等待执行完成', default=True)
name = models.CharField('脚本名称', max_length=100)
workflow = models.ForeignKey(Workflow, verbose_name='关联工作流', null=True, blank=True, on_delete=models.SET_NULL)
func_str = models.CharField('函数名', max_length=50, null=True, blank=True)
content = models.TextField('脚本内容')

View File

@ -0,0 +1,14 @@
class GetParticipants:
all_funcs = [
{'func':'get_create_by', 'name':'获取工单创建人'}
]
# def all_funcs(self):
# # return list(filter(lambda x: x.startswith('get_') and callable(getattr(self, x)), dir(self)))
# return [(func, getattr(self, func).__doc__) for func in dir(self) if callable(getattr(self, func)) and func.startswith('get_')]
def get_create_by(self, state:dict={}, ticket:dict={}, ticket_data:dict={}, request={}):
"""工单创建人"""
participant = ticket.create_by.id
return participant

View File

@ -136,7 +136,10 @@ class TicketCloseSerializer(serializers.Serializer):
class TicketAddNodeSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="加签说明", required = False)
add_node_man = serializers.IntegerField(label='加签')
toadd_user = serializers.IntegerField(label='发送给谁去加签')
class TicketAddNodeEndSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="加签意见", required = False)
suggestion = serializers.CharField(label="加签意见", required = False)
class TicketDestorySerializer(serializers.Serializer):
ids = serializers.ListField(child=serializers.IntegerField(), label='工单ID列表')

View File

@ -7,6 +7,9 @@ from rest_framework.exceptions import APIException
from django.utils import timezone
from datetime import timedelta
import random
from .scripts import GetParticipants
from utils.queryset import get_parent_queryset
class WfService(object):
@staticmethod
def get_worlflow_states(workflow:Workflow):
@ -103,7 +106,7 @@ class WfService(object):
@classmethod
def get_next_state_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition, ticket_data:dict={})->object:
def get_next_state_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition, ticket_data:dict={}, request:dict={})->object:
"""
获取下个节点状态
"""
@ -111,16 +114,19 @@ class WfService(object):
destination_state = transition.destination_state
ticket_all_value = cls.get_ticket_all_field_value(ticket)
ticket_all_value.update(**ticket_data)
for key, value in ticket_all_value.items():
if isinstance(ticket_all_value[key], str):
ticket_all_value[key] = "'" + ticket_all_value[key] + "'"
if transition.condition_expression:
for i in transition.condition_expression:
expression = i['expression'].format(**ticket_all_value)
import datetime, time # 用于支持条件表达式中对时间的操作
if eval(expression):
if eval(expression, {"__builtins__":None}, {'datetime':datetime, 'time':time}):
destination_state = State.objects.get(pk=i['target_state'])
return destination_state
@classmethod
def get_ticket_state_participant_info(cls, state:State, ticket:Ticket, ticket_data:dict={}):
def get_ticket_state_participant_info(cls, state:State, ticket:Ticket, ticket_data:dict={}, request={}):
"""
获取工单目标状态实际的处理人, 处理人类型
"""
@ -141,17 +147,34 @@ class WfService(object):
multi_all_person_dict = {}
destination_participant_type, destination_participant = state.participant_type, state.participant
if destination_participant_type == State.PARTICIPANT_TYPE_FIELD:
destination_participant = ticket_data.get(destination_participant, None) if destination_participant in ticket_data else Ticket.ticket_data.get(destination_participant, None)
destination_participant = ticket_data.get(destination_participant, 0) if destination_participant in ticket_data \
else Ticket.ticket_data.get(destination_participant, 0)
elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT:#单部门
destination_participant = list(User.objects.filter(dept=destination_participant).values_list('id', flat=True))
elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE:#代码获取
destination_participant = getattr(GetParticipants, destination_participant)(state=state, ticket=ticket, ticket_data=ticket_data, request=request)
elif destination_participant_type == State.PARTICIPANT_TYPE_ROLE:#单角色
destination_participant = list(User.objects.filter(roles=destination_participant).values_list('id', flat=True))
elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT:#部门
destination_participant = list(User.objects.filter(dept__in=destination_participant).values_list('id', flat=True))
elif destination_participant_type == State.PARTICIPANT_TYPE_ROLE:#角色
user_queryset = User.objects.filter(roles__in=destination_participant)
# 如果选择了角色, 需要走过滤策略
if ticket.filter_policy == 1:
depts = get_parent_queryset(ticket.belong_dept)
user_queryset = user_queryset.filter(dept__in=depts)
elif ticket.filter_policy == 2:
depts = get_parent_queryset(ticket.create_by.dept)
user_queryset = user_queryset.filter(dept__in=depts)
elif ticket.filter_policy == 3:
depts = get_parent_queryset(request.user.dept)
user_queryset = user_queryset.filter(dept__in=depts)
destination_participant = list(user_queryset.values_list('id', flat=True))
if type(destination_participant) == list:
destination_participant_type = State.PARTICIPANT_TYPE_MULTI
destination_participant = list(set(destination_participant))
if len(destination_participant) == 1: # 如果只有一个人
destination_participant_type = State.PARTICIPANT_TYPE_PERSONAL
destination_participant = destination_participant[0]
else:
destination_participant_type = State.PARTICIPANT_TYPE_PERSONAL
if destination_participant_type == State.PARTICIPANT_TYPE_MULTI:

View File

@ -1,6 +1,6 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.wf.views import CustomFieldViewSet, StateViewSet, TicketFlowViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet
from apps.wf.views import CustomFieldViewSet, FromCodeListView, StateViewSet, TicketFlowViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
@ -12,6 +12,7 @@ router.register('customfield', CustomFieldViewSet, basename='wf_customfield')
router.register('ticket', TicketViewSet, basename='wf_ticket')
router.register('ticketflow', TicketFlowViewSet, basename='wf_ticketflow')
urlpatterns = [
path('participant_from_code', FromCodeListView.as_view()),
path('', include(router.urls)),
]

View File

@ -1,10 +1,12 @@
from django.db.models import query
from rest_framework.views import APIView
from apps.system.models import User
from apps.wf.filters import TicketFilterSet
from django.core.exceptions import AppRegistryNotReady
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
from django.shortcuts import get_object_or_404, render
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action, api_view
@ -13,8 +15,18 @@ from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin,
from apps.wf.services import WfService
from rest_framework.exceptions import APIException, PermissionDenied
from rest_framework import status
from django.db.models import Count
from .scripts import GetParticipants
# Create your views here.
class FromCodeListView(APIView):
def get(self, request, format=None):
"""
获取处理人代码列表
"""
return Response(GetParticipants.all_funcs)
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
perms_map = {'get': '*', 'post': 'workflow_create',
'put': 'workflow_update', 'delete': 'workflow_delete'}
@ -132,11 +144,12 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
if value == State.STATE_FIELD_REQUIRED:
if key not in ticket_data or not ticket_data[key]:
raise APIException('字段{}必填'.format(key))
ticket = serializer.save(state=start_state, create_by=request.user, act_state=Ticket.TICKET_ACT_STATE_DRAFT) # 先创建出来
ticket = serializer.save(state=start_state, create_by=request.user, act_state=Ticket.TICKET_ACT_STATE_DRAFT, belong_dept=request.user.dept) # 先创建出来
next_state = WfService.get_next_state_by_transition_and_ticket_info(ticket=ticket, transition=transition)
participant_info = WfService.get_ticket_state_participant_info(state=next_state, ticket=ticket, ticket_data=ticket.ticket_data)
destination_participant_type = participant_info.get('destination_participant_type', 0)
destination_participant = participant_info.get('destination_participant', None)
destination_participant = participant_info.get('destination_participant', 0)
multi_all_person = participant_info.get('multi_all_person', {}) # 多人需要全部处理情况
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
if next_state.type == State.STATE_TYPE_END:
@ -163,8 +176,29 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
TicketFlow.objects.create(ticket=ticket, state=start_state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=rdata.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=ticket.create_by, transition=transition)
# 开始状态需要抄送
if start_state.participant_cc:
TicketFlow.objects.create(ticket=ticket, state=ticket.start_state,
participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
participant=None, participant_cc=start_state.participant_cc)
# 目标状态需要抄送
if next_state.participant_cc:
TicketFlow.objects.create(ticket=ticket, state=ticket.next_state,
participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
participant=None, participant_cc=next_state.participant_cc)
return Response(TicketSerializer(instance=ticket).data)
@action(methods=['get'], detail=False, perms_map={'get':'*'})
def duty_agg(self, request, pk=None):
"""
工单待办聚合
"""
ret = {}
queryset = Ticket.objects.filter(participant__contains=request.user.id, is_deleted=False)\
.exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
ret['total_count'] = queryset.count()
ret['details'] = list(queryset.values('workflow', 'workflow__name').annotate(count = Count('workflow')))
return Response(ret)
@action(methods=['post'], detail=True, perms_map={'post':'*'})
def handle(self, request, pk=None):
@ -189,7 +223,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
if value == State.STATE_FIELD_REQUIRED:
if key not in ticket_data or not ticket_data[key]:
raise APIException('字段{}必填'.format(key))
destination_state = WfService.get_next_state_by_transition_and_ticket_info(ticket, transition, ticket_data)
destination_state = WfService.get_next_state_by_transition_and_ticket_info(ticket, transition, ticket_data, request)
multi_all_person = ticket.multi_all_person
if multi_all_person:
multi_all_person[request.user.id] =dict(transition=transition.id)
@ -197,7 +231,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
if WfService.check_dict_has_all_same_value(multi_all_person):
participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data'])
destination_participant_type = participant_info.get('destination_participant_type', 0)
destination_participant = participant_info.get('destination_participant', None)
destination_participant = participant_info.get('destination_participant', 0)
multi_all_person = {}
else:
# 处理人没有没有全部处理完成或者处理动作不一致
@ -206,12 +240,12 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
destination_participant = []
for key, value in multi_all_person.items():
if not value:
destination_participant.push(key)
destination_participant.append(key)
else:
# 当前处理人类型非全部处理
participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, data['ticket_data'])
destination_participant_type = participant_info.get('destination_participant_type', 0)
destination_participant = participant_info.get('destination_participant', None)
destination_participant = participant_info.get('destination_participant', 0)
multi_all_person = participant_info.get('multi_all_person', {})
# 更新工单信息:基础字段及自定义字段, add_relation字段 需要下个处理人是部门、角色等的情况
@ -240,6 +274,11 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
TicketFlow.objects.create(ticket=ticket, state=source_state, ticket_data=WfService.get_ticket_all_field_value(ticket),
suggestion=data.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=request.user, transition=transition)
# 目标状态需要抄送
if destination_state.participant_cc:
TicketFlow.objects.create(ticket=ticket, state=ticket.destination_state,
participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
participant=None, participant_cc=destination_state.participant_cc)
return Response(TicketSerializer(instance=ticket).data)
@ -321,7 +360,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
"""
ticket = self.get_object()
data = request.data
add_user = User.objects.get(pk=data['add_node_man'])
add_user = User.objects.get(pk=data['toadd_user'])
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
ticket.participant = add_user.id
ticket.in_add_node = True
@ -342,8 +381,8 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
ticket = self.get_object()
ticket.participant_type = State.PARTICIPANT_TYPE_PERSONAL
ticket.in_add_node = False
ticket.add_node_man = None
ticket.participant = ticket.add_node_man.id
ticket.add_node_man = None
ticket.save()
# 更新流转记录
suggestion = request.data.get('suggestion', '') # 加签意见
@ -375,6 +414,15 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
else:
return Response('工单不可关闭', status=status.HTTP_400_BAD_REQUEST)
@action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=TicketDestorySerializer)
def destory(self, request, pk=None):
"""
批量物理删除
"""
Ticket.objects.filter(id__in=request.data.get('ids', [])).delete(soft=False)
return Response()
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
"""

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class WpmConfig(AppConfig):
name = 'apps.wpm'
verbose_name = '车间生产'

View File

@ -0,0 +1,55 @@
from django.db import models
from django.db.models.base import Model
import django.utils.timezone as timezone
from django.db.models.query import QuerySet
from apps.system.models import CommonAModel, CommonBModel, Organization, User, Dict, File
from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords
from apps.mtm.models import Material, Step, RecordForm
class Product(CommonAModel):
"""
产品(所有生产过程中出现过的)
"""
act_state_choices=(
(0, '待执行'),
(1, '进行中'),
(2, '已完成')
)
number = models.CharField('物品编号', primary_key=True, null=True, blank=True, max_length=50)
m_state = models.ForeignKey(Material, verbose_name='所属物料状态', on_delete=models.CASCADE)
p_state = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True)
act_state = models.IntegerField('进行状态', default=0)
remark = models.CharField('备注', max_length=200, null=True, blank=True)
class ProductForm(CommonAModel):
"""
记录表格
"""
record_form = models.ForeignKey(RecordForm, verbose_name='所用表格', on_delete=models.CASCADE)
data = models.JSONField('记录的数据', default=dict, blank=True)
class ProductFlow(BaseModel):
"""
产品流转日志
"""
product = models.ForeignKey(Product, verbose_name='产品', on_delete=models.CASCADE)
class Vendor(CommonAModel):
"""
供应商信息
"""
name = models.CharField('供应商名称', max_length=50, unique=True)
contact = models.CharField('联系人', max_length=20)
contact_phone = models.CharField('联系电话', max_length=11, unique=True)
address = models.CharField('地址', max_length=200, null=True, blank=True)
description = models.CharField('描述', max_length=200, blank=True, null=True)
material = models.CharField('供应的物料', max_length=200, blank=True, null=True)
class Meta:
verbose_name = '供应商信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.name

View File

@ -0,0 +1,9 @@
from rest_framework.serializers import ModelSerializer
from .models import Vendor
class VendorSerializer(ModelSerializer):
class Meta:
model = Vendor
fields = '__all__'

View File

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

View File

@ -0,0 +1,12 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.pum.views import VendorViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('vendor', VendorViewSet, basename='vendor')
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -0,0 +1,23 @@
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from apps.pum.models import Vendor
from apps.pum.serializers import VendorSerializer
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
# Create your views here.
class VendorViewSet(CreateUpdateModelAMixin, ModelViewSet):
"""
供应商-增删改查
"""
perms_map = {'get': '*', 'post': 'vendor_create',
'put': 'vendor_update', 'delete': 'vendor_delete'}
queryset = Vendor.objects.all()
serializer_class = VendorSerializer
search_fields = ['name', 'contact']
filterset_fields = []
ordering_fields = ['create_time']
ordering = ['-create_time']

View File

@ -10,3 +10,4 @@ drf-yasg==1.20.0
psutil==5.8.0
pillow==8.3.1
opencv-python==4.5.3.56
django-celery-results==2.2.0

View File

@ -41,6 +41,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'corsheaders',
'django_celery_beat',
'django_celery_results',
'drf_yasg',
'rest_framework',
"django_filters",
@ -55,7 +56,8 @@ INSTALLED_APPS = [
'apps.inm',
'apps.sam',
'apps.qm',
'apps.pm'
'apps.pm',
# 'apps.wpm'
]
MIDDLEWARE = [
@ -202,6 +204,7 @@ CELERY_BROKER_URL = "redis://redis:6379/0" # 任务存储
CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行300个任务就会被销毁可防止内存泄露
CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区
CELERY_ENABLE_UTC = True # 启动时区设置
CELERY_RESULT_BACKEND = 'django-db'
# swagger配置
SWAGGER_SETTINGS = {

View File

@ -57,4 +57,14 @@ def get_child_queryset2(obj, hasParent=True):
while child_queryset:
queryset = queryset | child_queryset
child_queryset = cls.objects.filter(parent__in=child_queryset)
return queryset
return queryset
def get_parent_queryset(obj, hasSelf=True):
cls = type(obj)
ids = []
if hasSelf:
ids.append(obj.id)
while obj.parent:
obj = obj.parent
ids.append(obj.id)
return cls.objects.filter(id__in=ids)