制造技术管理表

This commit is contained in:
caoqianming 2021-08-25 10:42:49 +08:00
parent c97045112a
commit 4bdf6382d5
19 changed files with 660 additions and 20 deletions

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 MtmConfig(AppConfig):
name = 'apps.mtm'
verbose_name = '制造技术管理'

View File

@ -0,0 +1,161 @@
# Generated by Django 3.2.6 on 2021-08-24 06:52
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0003_auto_20210812_0909'),
]
operations = [
migrations.CreateModel(
name='Material',
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='删除标记')),
('name', models.CharField(max_length=100, unique=True, verbose_name='物料名称')),
('number', models.CharField(max_length=100, unique=True, verbose_name='编号')),
('type', models.CharField(choices=[(1, '成品'), (2, '半成品'), (3, '原材料')], default=1, max_length=20, verbose_name='物料类型')),
('sort_str', models.CharField(blank=True, max_length=100, null=True, verbose_name='排序字符')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='material_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
],
options={
'verbose_name': '物料表',
'verbose_name_plural': '物料表',
},
),
migrations.CreateModel(
name='Process',
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='删除标记')),
('name', models.CharField(max_length=100, unique=True, verbose_name='工序名称')),
('number', models.CharField(max_length=100, unique=True, verbose_name='编号')),
('instruction_content', models.TextField(blank=True, null=True, verbose_name='指导书内容')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='process_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('instruction', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.file', verbose_name='指导书')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='process_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '工序',
'verbose_name_plural': '工序',
},
),
migrations.CreateModel(
name='Step',
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='删除标记')),
('name', models.CharField(max_length=100, verbose_name='工序步骤名称')),
('number', models.CharField(blank=True, max_length=100, null=True, verbose_name='步骤编号')),
('instruction_content', models.TextField(blank=True, null=True, verbose_name='相应操作指导')),
('sort', models.IntegerField(default=1, verbose_name='排序号')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='step_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('process', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.process', verbose_name='所属工序')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='step_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '工序步骤',
'verbose_name_plural': '工序步骤',
},
),
migrations.CreateModel(
name='StepOperationItem',
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='删除标记')),
('field_type', models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('textarea', '文本域')], max_length=50, verbose_name='类型')),
('field_key', models.CharField(help_text='字段类型请尽量特殊,避免与系统中关键字冲突', max_length=50, verbose_name='字段标识')),
('field_name', models.CharField(max_length=50, verbose_name='字段名称')),
('boolean_field_display', models.JSONField(blank=True, default=dict, help_text='当为布尔类型时候,可以支持自定义显示形式。{"1":"","0":""}或{"1":"需要","0":"不需要"},注意数字也需要引号', verbose_name='布尔类型显示名')),
('field_choice', models.JSONField(blank=True, default=dict, help_text='radio,checkbox,select,multiselect类型可供选择的选项格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号', verbose_name='radio、checkbox、select的选项')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stepoperationitem_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('step', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.step', verbose_name='关联步骤')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stepoperationitem_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '操作记录条目',
'verbose_name_plural': '操作记录条目',
},
),
migrations.CreateModel(
name='ProductProcess',
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='删除标记')),
('sort', models.IntegerField(default=1, verbose_name='排序号')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='productprocess_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('process', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.process', verbose_name='工序')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='产品')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='productprocess_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '产品生产工序',
'verbose_name_plural': '产品生产工序',
},
),
migrations.CreateModel(
name='OutputMaterial',
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='删除标记')),
('number', models.FloatField(default=0, verbose_name='产出量')),
('unit', models.CharField(max_length=20, verbose_name='单位')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='outputmaterial_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='输出物料')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='outputmaterial_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '输出物料',
'verbose_name_plural': '输出物料',
},
),
migrations.AddField(
model_name='material',
name='process',
field=models.ManyToManyField(related_name='product_process', through='mtm.ProductProcess', to='mtm.Process'),
),
migrations.AddField(
model_name='material',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='material_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.CreateModel(
name='InputMaterial',
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='删除标记')),
('number', models.FloatField(default=0, verbose_name='消耗量')),
('unit', models.CharField(max_length=20, verbose_name='单位')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inputmaterial_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('material', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='输入物料')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inputmaterial_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'verbose_name': '输入物料',
'verbose_name_plural': '输入物料',
},
),
]

View File

@ -0,0 +1,131 @@
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
class Material(CommonAModel):
"""
物料
"""
type_choices=(
(1, '成品'),
(2, '半成品'),
(3, '原材料')
)
name = models.CharField('物料名称', max_length=100, unique=True)
number = models.CharField('编号', max_length=100, unique=True)
type = models.CharField('物料类型', choices= type_choices, max_length=20, default=1)
sort_str = models.CharField('排序字符', max_length=100, null=True, blank=True)
process = models.ManyToManyField('mtm.process', through='mtm.ProductProcess', related_name='product_process')
class Meta:
verbose_name = '物料表'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Process(CommonAModel):
"""
工序
"""
name = models.CharField('工序名称', max_length=100, unique=True)
number = models.CharField('编号', max_length=100, unique=True)
instruction = models.ForeignKey(File, verbose_name='指导书', on_delete=models.SET_NULL, null=True, blank=True)
instruction_content = models.TextField('指导书内容', null=True, blank=True)
class Meta:
verbose_name = '工序'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Step(CommonAModel):
"""
工序步骤
"""
process = models.ForeignKey(Process, on_delete=models.CASCADE, verbose_name='所属工序')
name = models.CharField('工序步骤名称', max_length=100)
number = models.CharField('步骤编号', max_length=100, null=True, blank=True)
instruction_content = models.TextField('相应操作指导', null=True, blank=True)
sort = models.IntegerField('排序号', default=1)
class Meta:
verbose_name = '工序步骤'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class StepOperationItem(CommonAModel):
"""
操作记录条目
"""
field_type_choices = (
('string', '字符串'),
('int', '整型'),
('float', '浮点'),
('boolean', '布尔'),
('date', '日期'),
('datetime', '日期时间'),
('radio', '单选'),
('checkbox', '多选'),
('select', '单选下拉'),
('selects', '多选下拉'),
('textarea', '文本域'),
)
step = models.ForeignKey(Step, 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='字段类型请尽量特殊,避免与系统中关键字冲突')
field_name = models.CharField('字段名称', max_length=50)
boolean_field_display = models.JSONField('布尔类型显示名', default=dict, blank=True,
help_text='当为布尔类型时候,可以支持自定义显示形式。{"1":"","0":""}或{"1":"需要","0":"不需要"},注意数字也需要引号')
field_choice = models.JSONField('radio、checkbox、select的选项', default=dict, blank=True,
help_text='radio,checkbox,select,multiselect类型可供选择的选项格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号')
class Meta:
verbose_name = '操作记录条目'
verbose_name_plural = verbose_name
def __str__(self):
return self.field_key + '-' + self.field_name
class ProductProcess(CommonAModel):
"""
产品生产工艺
"""
product = models.ForeignKey(Material, verbose_name='产品', on_delete=models.CASCADE)
process = models.ForeignKey(Process, verbose_name='工序', on_delete=models.CASCADE)
sort = models.IntegerField('排序号', default=1)
class Meta:
verbose_name = '产品生产工序'
verbose_name_plural = verbose_name
class InputMaterial(CommonAModel):
"""
输入物料
"""
material = models.ForeignKey(Material, verbose_name='输入物料', on_delete=models.CASCADE)
number = models.FloatField('消耗量', default=0)
unit = models.CharField('单位', max_length=20)
class Meta:
verbose_name = '输入物料'
verbose_name_plural = verbose_name
class OutputMaterial(CommonAModel):
"""
输出物料
"""
material = models.ForeignKey(Material, verbose_name='输出物料', on_delete=models.CASCADE)
number = models.FloatField('产出量', default=0)
unit = models.CharField('单位', max_length=20)
class Meta:
verbose_name = '输出物料'
verbose_name_plural = verbose_name

View File

@ -0,0 +1,19 @@
from rest_framework.serializers import ModelSerializer
from .models import Material, Process, Step
class MaterialSerializer(ModelSerializer):
class Meta:
model = Material
fields = '__all__'
class ProcessSerializer(ModelSerializer):
class Meta:
model = Process
fields = '__all__'
class StepSerializer(ModelSerializer):
class Meta:
model = Step
fields = '__all__'

View File

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

View File

@ -0,0 +1,14 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.mtm.views import MaterialViewSet, ProcessViewSet, StepViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('material', MaterialViewSet, basename='material')
router.register('process', ProcessViewSet, basename='process')
router.register('step', StepViewSet, basename='step')
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -0,0 +1,54 @@
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin
from apps.mtm.models import Material, Process, Step
from apps.mtm.serializers import MaterialSerializer, ProcessSerializer, StepSerializer
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from rest_framework.decorators import action
from rest_framework.response import Response
# Create your views here.
class MaterialViewSet(CreateUpdateModelAMixin, ModelViewSet):
"""
物料表-增删改查
"""
perms_map = {'get': '*', 'post': 'material_create',
'put': 'material_update', 'delete': 'material_delete'}
queryset = Material.objects.all()
serializer_class = MaterialSerializer
search_fields = ['name', 'number']
filterset_fields = ['type']
ordering_fields = ['number', 'sort_str']
ordering = ['number']
class ProcessViewSet(CreateUpdateModelAMixin, ModelViewSet):
"""
工序表-增删改查
"""
perms_map = {'get': '*', 'post': 'process_create',
'put': 'process_update', 'delete': 'process_delete'}
queryset = Process.objects.all()
serializer_class = ProcessSerializer
search_fields = ['name', 'number']
filterset_fields = ['number']
ordering_fields = ['number']
ordering = ['number']
@action(methods=['get'], detail=True, perms_map={'get':'process_update'}, pagination_class=None, serializer_class=StepSerializer)
def steps(self, request, pk=None):
"""
工序下的子工序
"""
process = self.get_object()
serializer = self.serializer_class(instance=Step.objects.filter(process=process, is_deleted=True), many=True)
return Response(serializer.data)
class StepViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
perms_map = {'*':'process_update'}
queryset = Step.objects.all()
serializer_class = StepSerializer
search_fields = ['name', 'number']
filterset_fields = ['process']
ordering = ['sort']

View File

@ -0,0 +1,111 @@
# Generated by Django 3.2.6 on 2021-08-23 07:46
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', '0003_alter_customfield_field_type'),
]
operations = [
migrations.CreateModel(
name='Ticket',
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='删除标记')),
('title', models.CharField(blank=True, default='', help_text='工单标题', max_length=500, verbose_name='标题')),
('sn', models.CharField(help_text='工单的流水号', max_length=25, verbose_name='流水号')),
('ticket_data', models.JSONField(default=dict, help_text='工单所有字段内容', verbose_name='工单数据')),
('in_add_node', models.BooleanField(default=False, help_text='是否处于加签状态下', verbose_name='加签状态中')),
('add_node_man', models.CharField(blank=True, default='', help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效', max_length=50, verbose_name='加签人')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticket_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wf.ticket', verbose_name='父工单')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='customfield',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customfield_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AddField(
model_name='customfield',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customfield_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AddField(
model_name='state',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='state_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AddField(
model_name='state',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='state_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AddField(
model_name='transition',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transition_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AddField(
model_name='transition',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transition_update_by', to=settings.AUTH_USER_MODEL, 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, '父工单的字段')], default=1, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填creator', verbose_name='参与者类型'),
),
migrations.CreateModel(
name='TicketFlow',
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='删除标记')),
('suggestion', models.CharField(blank=True, default='', max_length=10000, verbose_name='处理意见')),
('participant', models.CharField(blank=True, default='', max_length=50, verbose_name='处理人')),
('ticket_data', models.JSONField(blank=True, default=dict, help_text='可以用于记录当前表单数据json格式', verbose_name='工单数据')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticketflow_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('state', models.ForeignKey(blank=True, default=0, on_delete=django.db.models.deletion.CASCADE, to='wf.state', verbose_name='当前状态')),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.ticket', verbose_name='关联工单')),
('transition', models.ForeignKey(help_text='与worklow.Transition关联 为0时表示认为干预的操作', on_delete=django.db.models.deletion.CASCADE, to='wf.transition', verbose_name='流转id')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticketflow_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='ticket',
name='parent_state',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_parent_state', to='wf.state', verbose_name='父工单状态'),
),
migrations.AddField(
model_name='ticket',
name='state',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_state', to='wf.state', verbose_name='当前状态'),
),
migrations.AddField(
model_name='ticket',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticket_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AddField(
model_name='ticket',
name='workflow',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.workflow', verbose_name='关联工作流'),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.6 on 2021-08-23 07:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('wf', '0004_auto_20210823_1546'),
]
operations = [
migrations.RemoveField(
model_name='ticketflow',
name='create_by',
),
migrations.RemoveField(
model_name='ticketflow',
name='update_by',
),
]

View File

@ -19,14 +19,16 @@ class Workflow(CommonAModel):
title_template = models.CharField('标题模板', max_length=50, default='你有一个待办工单:{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')
content_template = models.CharField('内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
class State(BaseModel):
class State(CommonAModel):
"""
状态记录
"""
STATE_TYPE_START = 1
STATE_TYPE_END = 2
type_choices = (
(0, '普通类型'),
(1, '初始状态'),
(2, '结束状态')
(1, STATE_TYPE_START),
(2, STATE_TYPE_END)
)
type2_choices = (
(0, '无处理人'),
@ -45,10 +47,10 @@ class State(BaseModel):
sort = models.IntegerField('状态顺序', default=0, help_text='用于工单步骤接口时step上状态的顺序(因为存在网状情况,所以需要人为设定顺序),值越小越靠前')
type = models.IntegerField('状态类型', default=0, choices=type_choices, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)')
enable_retreat = models.BooleanField('允许撤回', default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态')
participant_type = models.IntegerField('参与者类型', default=1, blank=True, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填creator')
participant_type = models.IntegerField('参与者类型', choices=type2_choices, default=1, blank=True, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填creator')
class Transition(BaseModel):
class Transition(CommonAModel):
"""
工作流流转定时器条件(允许跳过) 条件流转与定时器不可同时存在
"""
@ -67,7 +69,7 @@ class Transition(BaseModel):
field_require_check = models.BooleanField('是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容')
class CustomField(BaseModel):
class CustomField(CommonAModel):
"""自定义字段, 设定某个工作流有哪些自定义字段"""
field_type_choices = (
('string', '字符串'),
@ -100,7 +102,7 @@ class CustomField(BaseModel):
help_text='radio,checkbox,select,multiselect类型可供选择的选项格式为json如:{"1":"中国", "2":"美国"},注意数字也需要引号')
label = models.JSONField('标签', blank=True, default=dict, help_text='自定义标签json格式调用方可根据标签自行处理特殊场景逻辑loonflow只保存文本内容')
class Ticket(CommonBModel):
class Ticket(CommonAModel):
"""
工单
"""
@ -110,7 +112,7 @@ class Ticket(CommonBModel):
state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='当前状态', related_name='ticket_state')
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单')
parent_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单状态', related_name='ticket_parent_state')
formdata = models.JSONField('工单表单', default=dict, help_text='工单所有字段内容')
ticket_data = models.JSONField('工单数据', default=dict, help_text='工单所有字段内容')
in_add_node = models.BooleanField('加签状态中', default=False, help_text='是否处于加签状态下')
add_node_man = models.CharField('加签人', max_length=50, default='', blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
@ -118,3 +120,9 @@ class TicketFlow(BaseModel):
"""
工单流转日志
"""
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单')
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联 为0时表示认为干预的操作', on_delete=models.CASCADE)
suggestion = models.CharField('处理意见', max_length=10000, default='', blank=True)
participant = models.CharField('处理人', max_length=50, default='', blank=True)
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格式')

View File

@ -1,6 +1,6 @@
from rest_framework.serializers import ModelSerializer, SerializerMethodField, CharField
from .models import State, Workflow, Transition, CustomField
from .models import State, Ticket, Workflow, Transition, CustomField
class WorkflowSerializer(ModelSerializer):
@ -13,6 +13,11 @@ class StateSerializer(ModelSerializer):
model = State
fields = '__all__'
class WorkflowSimpleSerializer(ModelSerializer):
class Meta:
model = Workflow
fields = ['id', 'name']
class StateSimpleSerializer(ModelSerializer):
class Meta:
model = State
@ -35,3 +40,22 @@ class CustomFieldSerializer(ModelSerializer):
class Meta:
model = CustomField
fields = '__all__'
class TicketCreateSerializer(ModelSerializer):
class Meta:
model=Ticket
fields=['title','workflow','ticket_data']
class TicketSerializer(ModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True)
class Meta:
model = Ticket
fields = '__all__'
@staticmethod
def setup_eager_loading(queryset):
queryset = queryset.select_related('workflow','state')
return queryset

View File

@ -0,0 +1,54 @@
from apps.wf.models import State, Ticket, Transition, Workflow
from rest_framework.exceptions import APIException
class WfService(object):
@staticmethod
def get_wf_states(wf:Workflow):
"""
获取工作流状态列表
"""
return State.objects.filter(workflow=wf, is_deleted=False).order_by('sort')
@staticmethod
def get_wf_transitions(wf:Workflow):
"""
获取工作流流转列表
"""
return Transition.objects.filter(workflow=wf, is_deleted=False)
@staticmethod
def get_wf_start_state(wf:Workflow):
"""
获取工作流初始状态
"""
try:
wf_state_obj = State.objects.get(workflow=wf, type=State.STATE_TYPE_START, is_deleted=False)
return wf_state_obj
except:
raise Exception('工作流初始状态配置错误')
@classmethod
def get_ticket_transitions(cls, ticket:Ticket):
"""
获取工单当前状态下可用的流转条件
"""
return cls.get_state_transitions(ticket.state)
@classmethod
def get_state_transitions(cls, state:State):
"""
获取状态可执行的操作
"""
return Transition.objects.filter(is_deleted=False, source_state=state).all()
@classmethod
def get_ticket_next_state(cls, ticket:Ticket)->object:
transitions = Transition.objects.filter(source_state=ticket.state, is_deleted=False)
count = transitions.count()
if count == 0:
raise Exception('未配置流转条件')
elif count == 1:
return transitions.first()
else:
for i in transitions:
pass

View File

@ -1,6 +1,6 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.wf.views import CustomFieldViewSet, StateViewSet, TransitionViewSet, WorkflowViewSet
from apps.wf.views import CustomFieldViewSet, StateViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
@ -9,6 +9,7 @@ router.register('workflow', WorkflowViewSet, basename='wf')
router.register('state', StateViewSet, basename='wf_state')
router.register('transition', TransitionViewSet, basename='wf_transitions')
router.register('customfield', CustomFieldViewSet, basename='wf_customfield')
router.register('ticket', TicketViewSet, basename='wf_ticket')
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -1,13 +1,13 @@
from rest_framework.response import Response
from rest_framework import serializers
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TransitionSerializer, WorkflowSerializer
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from apps.wf.serializers import CustomFieldSerializer, StateSerializer, TicketCreateSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer
from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action
from apps.wf.models import CustomField, Workflow, State, Transition
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from apps.wf.models import CustomField, Ticket, Workflow, State, Transition
from apps.system.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin
from apps.wf.services import WfService
# Create your views here.
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
@ -26,7 +26,7 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
工作流下的状态节点
"""
wf = self.get_object()
serializer = self.serializer_class(instance=State.objects.filter(workflow=wf), many=True)
serializer = self.serializer_class(instance=WfService.get_wf_states(wf), many=True)
return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=TransitionSerializer)
@ -35,7 +35,7 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
工作流下的流转规则
"""
wf = self.get_object()
serializer = self.serializer_class(instance=Transition.objects.filter(workflow=wf), many=True)
serializer = self.serializer_class(instance=WfService.get_wf_transitions(wf), many=True)
return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'workflow_update'}, pagination_class=None, serializer_class=CustomFieldSerializer)
@ -47,6 +47,18 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
serializer = self.serializer_class(instance=CustomField.objects.filter(workflow=wf), many=True)
return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'workflow_init'})
def init(self, request, pk=None):
"""
新建工单初始化
"""
ret={}
wf = self.get_object()
start_state = WfService.get_wf_start_state(wf)
transitions = WfService.get_state_transitions(start_state)
ret['workflow'] = pk
return Response(ret)
class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
perms_map = {'*':'*'}
queryset = State.objects.all()
@ -70,3 +82,19 @@ class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin,
search_fields = ['field_name']
filterset_fields = ['workflow', 'field_type']
ordering = ['sort']
class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet):
perms_map = {'*':'*'}
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
search_fields = ['title']
filterset_fields = ['workflow', 'state']
ordering = ['-create_time']
def get_serializer_class(self):
if self.action == 'create':
return TicketCreateSerializer
return super().get_serializer_class()
def create(self, request, *args, **kwargs):
return super().create(request, *args, **kwargs)

View File

@ -50,7 +50,8 @@ INSTALLED_APPS = [
'apps.pum',
'apps.em',
'apps.hrm',
'apps.wf'
'apps.wf',
'apps.mtm'
]
MIDDLEWARE = [

View File

@ -61,7 +61,7 @@ urlpatterns = [
path('api/em/', include('apps.em.urls')),
path('api/hrm/', include('apps.hrm.urls')),
path('api/wf/', include('apps.wf.urls')),
path('api/mtm/', include('apps.mtm.urls')),
# 工具
path('api/utils/signature/', GenSignature.as_view()),