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

This commit is contained in:
““shijing 2021-09-06 11:12:24 +08:00
commit 71474afd1f
9 changed files with 368 additions and 19 deletions

30
hb_client/src/api/inm.js Normal file
View File

@ -0,0 +1,30 @@
import request from '@/utils/request'
export function getWarehouseList(query) {
return request({
url: '/inm/warehouse/',
method: 'get',
params: query
})
}
export function createWarehouse(data) {
return request({
url: '/inm/warehouse/',
method: 'post',
data
})
}
export function updateWarehouse(id, data) {
return request({
url: `/inm/warehouse/${id}/`,
method: 'put',
data
})
}
export function deleteWarehouse(id, data) {
return request({
url: `/inm/warehouse/${id}/`,
method: 'delete',
data
})
}

View File

@ -87,6 +87,12 @@ export const asyncRoutes = [
name: 'equipment', name: 'equipment',
meta: { title: '设备管理', icon: 'example', perms: ['equipment_set'] }, meta: { title: '设备管理', icon: 'example', perms: ['equipment_set'] },
children: [ children: [
{
path: 'index',
name: 'index',
component: () => import('@/views/equipment/index'),
meta: { title: '设备台账', icon: 'example', perms: ['index_manage'] }
},
{ {
path: 'index', path: 'index',
name: 'index', name: 'index',
@ -95,14 +101,34 @@ export const asyncRoutes = [
} }
] ]
}, },
{
path: '/inm',
component: Layout,
redirect: '/inm/warehouse',
name: 'inm',
meta: { title: '库存管理', icon: 'example', perms: ['equipment_set'] },
children: [
{
path: 'warehouse',
name: 'warehouse',
component: () => import('@/views/inm/warehouse'),
meta: { title: '仓库', icon: 'example', perms: ['index_manage'] }
},
{
path: 'warehouse',
name: 'warehouse',
component: () => import('@/views/inm/warehouse'),
meta: { title: '仓库', icon: 'example', perms: ['index_manage'] }
}
]
},
{ {
path: '/mtm', path: '/mtm',
component: Layout, component: Layout,
redirect: '/mtm/material/', redirect: '/mtm/material/',
name: 'mtm', name: 'mtm',
meta: { title: '制造技术管理', icon: 'example', perms: ['procurement_set'] }, meta: { title: '制造管理', icon: 'example', perms: ['procurement_set'] },
children: [ children: [
{ {
path: 'material', path: 'material',
@ -147,6 +173,12 @@ export const asyncRoutes = [
name: 'procurement', name: 'procurement',
meta: { title: '采购管理', icon: 'example', perms: ['procurement_set'] }, meta: { title: '采购管理', icon: 'example', perms: ['procurement_set'] },
children: [ children: [
{
path: 'vendor',
name: 'vendor',
component: () => import('@/views/procurement/vendor'),
meta: { title: '供应商', icon: 'example', perms: ['vendor_manage'] }
},
{ {
path: 'vendor', path: 'vendor',
name: 'vendor', name: 'vendor',

View File

@ -0,0 +1,248 @@
<template>
<div class="app-container">
<el-card>
<div>
<el-input
v-model="listQuery.search"
placeholder="仓库名称/仓库编号"
style="width: 300px"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button
class="filter-item"
type="primary"
icon="el-icon-search"
@click="handleFilter"
>搜索</el-button
>
<el-button
class="filter-item"
type="primary"
icon="el-icon-refresh-left"
@click="resetFilter"
>重置</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
v-loading="listLoading"
:data="warehouseList.results"
border
fit
stripe
highlight-current-row
max-height="600"
>
<el-table-column type="index" width="50" />
<el-table-column label="仓库名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="仓库编号">
<template slot-scope="scope">{{ scope.row.number }}</template>
</el-table-column>
<el-table-column label="具体地点">
<template slot-scope="scope">{{ scope.row.place }}</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
width="220px"
>
<template slot-scope="scope">
<el-link
v-if="checkPermission(['warehouse_update'])"
@click="handleEdit(scope)"
>编辑</el-link
>
<el-link
v-if="checkPermission(['warehouse_delete'])"
type="danger"
@click="handleDelete(scope)"
>删除</el-link
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="warehouseList.count > 0"
:total="warehouseList.count"
:page.sync="listQuery.page"
:limit.sync="listQuery.page_size"
@pagination="getList"
/>
</el-card>
<el-dialog
:visible.sync="dialogVisible"
:title="dialogType === 'edit' ? '编辑仓库' : '新增仓库'"
>
<el-form
ref="Form"
:model="warehouse"
label-width="80px"
label-position="right"
:rules="rule1"
>
<el-form-item label="仓库名称" prop="name">
<el-input v-model="warehouse.name" placeholder="仓库名称" />
</el-form-item>
<el-form-item label="仓库编号" prop="number">
<el-input v-model="warehouse.number" placeholder="仓库编号" />
</el-form-item>
<el-form-item label="具体地点" prop="place">
<el-input v-model="warehouse.place" placeholder="具体地点" />
</el-form-item>
</el-form>
<div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirm('Form')">确认</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getWarehouseList, createWarehouse,updateWarehouse,deleteWarehouse } from "@/api/inm";
import checkPermission from "@/utils/permission";
import { genTree } from "@/utils";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
const defaultewarehouse = {
};
export default {
components: { Pagination },
data() {
return {
warehouse: defaultewarehouse,
warehouseList: {
count: 0,
},
options: [{
value: 0,
label: '运转正常'
}, {
value: 1,
label: '停用'
}, {
value: 2,
label: '报废'
}],
listQuery: {
page: 1,
page_size: 20,
},
keeperOptions:[],
depOptions:[],
listLoading: true,
dialogVisible: false,
dialogType: "new",
rule1: {
name: [{ required: true, message: "请输入", trigger: "blur" }],
number: [{ required: true, message: "请输入", trigger: "blur" }],
place: [{ required: true, message: "请输入", trigger: "blur" }],
},
};
},
computed: {},
watch: {},
created() {
this.getList();
},
methods: {
checkPermission,
//设备列表
getList() {
this.listLoading = true;
getWarehouseList(this.listQuery).then((response) => {
if (response.data) {
this.warehouseList = response.data;
}
this.listLoading = false;
});
},
handleFilter() {
this.listQuery.page = 1;
this.getList();
},
resetFilter() {
this.listQuery = {
page: 1,
page_size: 20,
}
this.getList();
},
handleCreate() {
this.warehouse = Object.assign({}, defaultewarehouse);
this.dialogType = "new";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
handleEdit(scope) {
this.warehouse = Object.assign({}, scope.row); // copy obj
this.dialogType = "edit";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
handleDelete(scope) {
this.$confirm("确认删除?", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error",
})
.then(async () => {
await deleteWarehouse(scope.row.id);
this.getList();
this.$message.success("成功");
})
.catch((err) => {
console.error(err);
});
},
async confirm(form) {
this.$refs[form].validate((valid) => {
if (valid) {
const isEdit = this.dialogType === "edit";
if (isEdit) {
updateWarehouse(this.warehouse.id, this.warehouse).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
}
});
} else {
createWarehouse(this.warehouse).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
}
});
}
} else {
return false;
}
});
},
},
};
</script>

View File

@ -18,7 +18,7 @@ class MaterialDetailSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
def get_processes_(self, obj): def get_processes_(self, obj):
objs = Process.objects.filter(id__in=obj.processes) objs = Process.objects.filter(id__in=obj.processes).order_by('number')
return ProcessSimpleSerializer(instance=objs, many=True).data return ProcessSimpleSerializer(instance=objs, many=True).data

View File

@ -53,16 +53,16 @@ class ProcessViewSet(PageOrNot, CreateUpdateModelAMixin, ModelViewSet):
ordering_fields = ['number'] ordering_fields = ['number']
ordering = ['number'] ordering = ['number']
@action(methods=['get'], detail=True, perms_map={'get':'process_update'}, pagination_class=None, serializer_class=StepSerializer) @action(methods=['get'], detail=True, perms_map={'get':'process_update'}, pagination_class=None, serializer_class=StepDetailSerializer)
def steps(self, request, pk=None): def steps(self, request, pk=None):
""" """
工序下的子工序 工序下的子工序
""" """
process = self.get_object() process = self.get_object()
serializer = self.serializer_class(instance=Step.objects.filter(process=process, is_deleted=False), many=True) serializer = self.serializer_class(instance=Step.objects.prefetch_related('equipments').filter(process=process, is_deleted=False), many=True)
return Response(serializer.data) return Response(serializer.data)
class StepViewSet(CreateUpdateModelAMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): class StepViewSet(OptimizationMixin, CreateUpdateModelAMixin, CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
""" """
子工序-增删改查 子工序-增删改查
""" """
@ -156,6 +156,15 @@ class RecordFormViewSet(OptimizationMixin, CreateUpdateModelAMixin, ModelViewSet
elif self.action == 'update': elif self.action == 'update':
return RecordFormUpdateSerializer return RecordFormUpdateSerializer
return RecordFormSerializer return RecordFormSerializer
@action(methods=['get'], detail=True, perms_map={'get':'*'}, pagination_class=None, serializer_class=RecordFormFieldSerializer)
def fields(self, request, pk=None):
"""
表格下的字段
"""
instance = self.get_object()
serializer = self.serializer_class(instance=RecordFormField.objects.filter(form=instance, is_deleted=False), many=True)
return Response(serializer.data)
class RecordFormFieldViewSet(OptimizationMixin, CreateUpdateModelAMixin, ModelViewSet): class RecordFormFieldViewSet(OptimizationMixin, CreateUpdateModelAMixin, ModelViewSet):

View File

@ -79,6 +79,9 @@ class Transition(CommonAModel):
""" """
工作流流转定时器条件(允许跳过) 条件流转与定时器不可同时存在 工作流流转定时器条件(允许跳过) 条件流转与定时器不可同时存在
""" """
TRANSITION_ATTRIBUTE_TYPE_ACCEPT = 1 # 同意
TRANSITION_ATTRIBUTE_TYPE_REFUSE = 2 # 拒绝
TRANSITION_ATTRIBUTE_TYPE_OTHER = 3 # 其他
attribute_type_choices = ( attribute_type_choices = (
(1, '同意'), (1, '同意'),
(2, '拒绝'), (2, '拒绝'),

View File

@ -1,7 +1,8 @@
from apps.system.serializers import UserSimpleSerializer
import rest_framework import rest_framework
from rest_framework import serializers from rest_framework import serializers
from .models import State, Ticket, Workflow, Transition, CustomField from .models import State, Ticket, TicketFlow, Workflow, Transition, CustomField
class WorkflowSerializer(serializers.ModelSerializer): class WorkflowSerializer(serializers.ModelSerializer):
@ -42,6 +43,10 @@ class CustomFieldSerializer(serializers.ModelSerializer):
model = CustomField model = CustomField
fields = '__all__' fields = '__all__'
class TicketSimpleSerializer(serializers.ModelSerializer):
class Meta:
model = Ticket
fields = '__all__'
class TicketCreateSerializer(serializers.ModelSerializer): class TicketCreateSerializer(serializers.ModelSerializer):
transition = serializers.IntegerField(label='流转ID') transition = serializers.IntegerField(label='流转ID')
@ -65,3 +70,12 @@ class TicketSerializer(serializers.ModelSerializer):
def setup_eager_loading(queryset): def setup_eager_loading(queryset):
queryset = queryset.select_related('workflow','state') queryset = queryset.select_related('workflow','state')
return queryset return queryset
class TicketFlowSerializer(serializers.ModelSerializer):
participant_ = UserSimpleSerializer(source='participant', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True)
class Meta:
model = TicketFlow
fields = '__all__'

View File

@ -1,3 +1,4 @@
from apps.wf.serializers import TicketSerializer, TicketSimpleSerializer
from typing import Tuple from typing import Tuple
from apps.system.models import User from apps.system.models import User
from apps.wf.models import CustomField, State, Ticket, Transition, Workflow from apps.wf.models import CustomField, State, Ticket, Transition, Workflow
@ -198,7 +199,7 @@ class WfService(object):
:return: :return:
""" """
# 获取工单基础表中的字段中的字段信息 # 获取工单基础表中的字段中的字段信息
field_info_dict = ticket.get_dict() field_info_dict = TicketSimpleSerializer(instance=ticket).data
# 获取自定义字段的值 # 获取自定义字段的值
custom_fields_queryset = cls.get_workflow_custom_fields(ticket.workflow) custom_fields_queryset = cls.get_workflow_custom_fields(ticket.workflow)
for i in custom_fields_queryset: for i in custom_fields_queryset:

View File

@ -2,7 +2,7 @@ from django.core.exceptions import AppRegistryNotReady
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import serializers from rest_framework import serializers
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketFlowSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action, api_view from rest_framework.decorators import action, api_view
@ -116,7 +116,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
if value == State.STATE_FIELD_REQUIRED: if value == State.STATE_FIELD_REQUIRED:
if key not in ticket_data or not ticket_data[key]: if key not in ticket_data or not ticket_data[key]:
raise APIException('字段{}必填'.format(key)) raise APIException('字段{}必填'.format(key))
ticket = serializer.save(state=start_state) # 先创建出来 ticket = serializer.save(state=start_state, create_by=request.user) # 先创建出来
next_state = WfService.get_next_state_by_transition_and_ticket_info(ticket=ticket, transition=transition) 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) 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_type = participant_info.get('destination_participant_type', 0)
@ -134,7 +134,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
if title_template: if title_template:
all_ticket_data = {**rdata, **rdata['ticket_data']} all_ticket_data = {**rdata, **rdata['ticket_data']}
title = title_template.format(**all_ticket_data) title = title_template.format(**all_ticket_data)
# 更新工单 # 更新一下信息
ticket.sn=sn ticket.sn=sn
ticket.title=title ticket.title=title
ticket.state=next_state ticket.state=next_state
@ -146,7 +146,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
# 新增流转记录 # 新增流转记录
TicketFlow.objects.create(ticket=ticket, state=start_state, ticket_data=WfService.get_ticket_all_field_value(ticket), 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, suggestion=rdata.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=ticket.create_by, transition=transition, create_by=request.user) participant=ticket.create_by, transition=transition)
return Response(TicketSerializer(instance=ticket).data) return Response(TicketSerializer(instance=ticket).data)
@ -204,14 +204,14 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
ticket.participant = destination_participant ticket.participant = destination_participant
ticket.multi_all_person = multi_all_person ticket.multi_all_person = multi_all_person
if destination_state.type == State.STATE_TYPE_END: if destination_state.type == State.STATE_TYPE_END:
ticket.act_state = State.TICKET_ACT_STATE_FINISH ticket.act_state = Ticket.TICKET_ACT_STATE_FINISH
elif destination_state.type == State.STATE_TYPE_START: elif destination_state.type == State.STATE_TYPE_START:
ticket.act_state = State.TICKET_ACT_STATE_DRAFT ticket.act_state = Ticket.TICKET_ACT_STATE_DRAFT
else: else:
ticket.act_state = State.TICKET_ACT_STATE_ONGOING ticket.act_state = Ticket.TICKET_ACT_STATE_ONGOING
if transition.attribute_type == State.TRANSITION_ATTRIBUTE_TYPE_REFUSE: if transition.attribute_type == Transition.TRANSITION_ATTRIBUTE_TYPE_REFUSE:
transition.act_state = State.TICKET_ACT_STATE_BACK transition.act_state = Ticket.TICKET_ACT_STATE_BACK
# 只更新必填和可选的字段 # 只更新必填和可选的字段
for key, value in ticket.state.state_fields.items(): for key, value in ticket.state.state_fields.items():
@ -223,7 +223,7 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
# 更新工单流转记录 # 更新工单流转记录
TicketFlow.objects.create(ticket=ticket, state=source_state, ticket_data=WfService.get_ticket_all_field_value(ticket), 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, suggestion=data.get('suggestion',''), participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=request.user, transition=transition, create_by=request.user) participant=request.user, transition=transition)
return Response(TicketSerializer(instance=ticket).data) return Response(TicketSerializer(instance=ticket).data)
@ -243,4 +243,16 @@ class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin
""" """
ticket = self.get_object() ticket = self.get_object()
transitions = WfService.get_ticket_transitions(ticket) transitions = WfService.get_ticket_transitions(ticket)
return Response(TransitionSerializer(instance=transitions, many=True).data) return Response(TransitionSerializer(instance=transitions, many=True).data)
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
"""
工单日志
"""
perms_map = {'*':'*'}
queryset = TicketFlow.objects.all()
serializer_class = TicketFlowSerializer
search_fields = ['suggestion']
filterset_fields = ['paticipant', 'state', 'ticket']
ordering = ['-create_time']